javascript - JS 中的简单 z-buffer 实现示例?[初学者]
问题描述
我正在寻找 Z 缓冲区的一个非常基本的实现,最好是在 JS 中。我试图看一个非常简单的代码,例如两个多边形重叠,一个隐藏另一个。我找不到这样的基本示例,但我可以找到几个“远高于我当前水平和理解”的示例。是否有您可以推荐入门的资源?
感谢您的帮助和建议!
解决方案
Z 缓冲区(也称为深度缓冲区)只不过是一个 2D 像素阵列(想想图像)。它只在每个像素中存储一个值,而不是 RGB,即与当前视点的距离:
// one value for each pixel in our screen
const depthBuffer = new Array(screenWidth * screenHeight);
它增加了包含您呈现给用户的实际图像的颜色缓冲区:
// create buffer for color output
const numChannels = 3; // R G B
const colorBuffer = new Array(screenWidth * screenHeight * numChannels);
对于您绘制的形状的每个像素,您检查 Z-Buffer 以查看是否有任何靠近相机的东西遮挡当前像素,如果是,您不绘制它。这样,您可以按任何顺序绘制事物,并且它们仍然在每个像素级别上被正确遮挡。
Z-Buffering 不仅可以用于 3D,还可以用于 2D,以实现绘图顺序无关。假设我们要绘制几个盒子,这将是我们的盒子类:
class Box {
/** @member {Object} position of the box storing x,y,z coordinates */
position;
/** @member {Object} size of the box storing width and height */
size;
/** @member {Object} color of the box given in RGB */
color;
constructor (props) {
this.position = props.position;
this.size = props.size;
this.color = props.color;
}
/**
* Check if given point is in box
* @param {Number} px coordinate of the point
* @param {Number} py coordinate of the point
* @return {Boolean} point in box
*/
pointInBox (px,py) {
return this.position.x < px && this.position.x + this.size.width > px
&& this.position.y < py && this.position.y + this.size.height > py;
}
}
有了这个类,我们现在可以创建一些盒子并绘制它们:
const boxes = [
new Box({
position: { x: 50, y: 50, z: 10 },
size: { width: 50, height: 20 },
color: { r: 255, g: 0, b:0 }
}),
// green box
new Box({
position: { x: 80, y: 30, z: 5 },
size: { width: 10, height: 50 },
color: { r: 0, g: 255, b:0 }
}),
// blue
new Box({
position: { x: 60, y: 55, z: 8 },
size: { width: 50, height: 10 },
color: { r: 0, g: 0, b: 255 }
})
];
指定形状后,我们现在可以绘制它们:
for(const box of boxes) {
for(let x = 0; x < screenWidth; x++) {
for(let y = 0; y < screenHeight; y++) {
// check if our pixel is within the box
if (box.pointInBox(x,y)) {
// check if this pixel of our box is covered by something else
// compare depth value in depthbuffer against box position
// this is commonly referred to as "depth-test"
if (depthBuffer[x + y * screenWidth] < box.position.z) {
// something is already closer to the viewpoint than our current primitive, don't draw this pixel:
continue;
}
// we passed the depth test, put our current depth value in the z-buffer
depthBuffer[x + y * screenWidth] = box.position.z;
// put the color in the color buffer, channel by channel
colorBuffer[(x + y * screenWidth)*numChannels + 0] = box.color.r;
colorBuffer[(x + y * screenWidth)*numChannels + 1] = box.color.g;
colorBuffer[(x + y * screenWidth)*numChannels + 2] = box.color.b;
}
}
}
}
请注意,此代码是示例性的,因此为了阐明概念,它过于冗长且效率低下。
const ctx = document.getElementById("output").getContext('2d');
const screenWidth = 200;
const screenHeight = 200;
// one value for each pixel in our screen
const depthBuffer = new Array(screenWidth * screenHeight);
// create buffer for color output
const numChannels = 3; // R G B
const colorBuffer = new Array(screenWidth * screenHeight * numChannels);
/**
* Represents a 2D box
* @class
*/
class Box {
/** @member {Object} position of the box storing x,y,z coordinates */
position;
/** @member {Object} size of the box storing width and height */
size;
/** @member {Object} color of the box given in RGB */
color;
constructor (props) {
this.position = props.position;
this.size = props.size;
this.color = props.color;
}
/**
* Check if given point is in box
* @param {Number} px coordinate of the point
* @param {Number} py coordinate of the point
* @return {Boolean} point in box
*/
pointInBox (px,py) {
return this.position.x < px && this.position.x + this.size.width > px
&& this.position.y < py && this.position.y + this.size.height > py;
}
}
const boxes = [
// red box
new Box({
position: { x: 50, y: 50, z: 10 },
size: { width: 150, height: 50 },
color: { r: 255, g: 0, b:0 }
}),
// green box
new Box({
position: { x: 80, y: 30, z: 5 },
size: { width: 10, height: 150 },
color: { r: 0, g: 255, b:0 }
}),
// blue
new Box({
position: { x: 70, y: 70, z: 8 },
size: { width: 50, height: 40 },
color: { r: 0, g: 0, b: 255 }
})
];
const varyZ = document.getElementById('varyz');
varyZ.onchange = draw;
function draw () {
// clear depth buffer of previous frame
depthBuffer.fill(10);
for(const box of boxes) {
for(let x = 0; x < screenWidth; x++) {
for(let y = 0; y < screenHeight; y++) {
// check if our pixel is within the box
if (box.pointInBox(x,y)) {
// check if this pixel of our box is covered by something else
// compare depth value in depthbuffer against box position
if (depthBuffer[x + y * screenWidth] < box.position.z) {
// something is already closer to the viewpoint that our current primitive, don't draw this pixel:
if (!varyZ.checked) continue;
if (depthBuffer[x + y * screenWidth] < box.position.z + Math.sin((x+y))*Math.cos(x)*5) continue;
}
// we passed the depth test, put our current depth value in the z-buffer
depthBuffer[x + y * screenWidth] = box.position.z;
// put the color in the color buffer, channel by channel
colorBuffer[(x + y * screenWidth)*numChannels + 0] = box.color.r;
colorBuffer[(x + y * screenWidth)*numChannels + 1] = box.color.g;
colorBuffer[(x + y * screenWidth)*numChannels + 2] = box.color.b;
}
}
}
}
// convert to rgba for presentation
const oBuffer = new Uint8ClampedArray(screenWidth*screenHeight*4);
for (let i=0,o=0; i < colorBuffer.length; i+=3,o+=4) {
oBuffer[o]=colorBuffer[i];
oBuffer[o+1]=colorBuffer[i+1];
oBuffer[o+2]=colorBuffer[i+2];
oBuffer[o+3]=255;
}
ctx.putImageData(new ImageData(oBuffer, screenWidth, screenHeight),0,0);
}
document.getElementById('redz').oninput = e=>{boxes[0].position.z=parseInt(e.target.value,10);draw()};
document.getElementById('greenz').oninput = e=>{boxes[1].position.z=parseInt(e.target.value,10);draw()};
document.getElementById('bluez').oninput = e=>{boxes[2].position.z=parseInt(e.target.value,10);draw()};
draw();
canvas {
border:1px solid black;
float:left;
margin-right: 2rem;
}
label {display:block;}
label span {
display:inline-block;
width: 100px;
}
<canvas width="200" height="200" id="output"></canvas>
<label><span>Red Z</span>
<input type="range" min="0" max="10" value="10" id="redz"/>
</label>
<label><span>Green Z</span>
<input type="range" min="0" max="10" value="5" id="greenz"/>
</label>
<label><span>Blue Z</span>
<input type="range" min="0" max="10" value="8" id="bluez"/>
</label>
<label><span>Vary Z Per Pixel</span>
<input type="checkbox" id="varyz"/>
</label>
推荐阅读
- python - 将 docx 表转换为 html(保留所有格式)或要在 html 中使用的图像
- html - CSS下拉菜单中的边框未隐藏且边框重叠
- jekyll - 在 Cloudcannon 上部署 jekyll 应用程序失败
- zabbix - Zabbix 监控 web 服务器经过 2 或 3 次检查
- c++ - 为什么使用 LLVM 时 std::ifstream 的缓冲会“中断”std::getline?
- apache - 将所有流量转发到一个子文件夹,将一个子文件夹转发到另一个子文件夹
- swift - 反正有没有迅速增加指数?
- maps - heremap 实现中的重叠问题
- asp.net-mvc - 如何在 Asp.net 中修复端口号
- python - Python 中的外部文件 - 未正确写入外部文件