深度缓冲
只靠绘制顺序时,后画的三角形会盖住先画的,不管它在场景里是远是近。深度缓冲为每个像素再存一个深度,测试失败的片元就不再写入颜色。
顶点着色器写到 @builtin(position) 的是裁剪坐标。w 为 1 时,NDC 的 z 就是你写的 z。WebGPU 里 0 是近平面,1 是远平面,和旧的 OpenGL 裁剪空间不同。
演示里蓝色三角形的 z 是 0.3,红色是 0.6。蓝色更近。绘制顺序却是先蓝后红,所以如果没有深度测试,红色会盖住重叠部分。
"depth24plus" 是核心深度格式,实现可以把它做成 24 位或 32 位浮点深度。它能作为渲染附件,不能用 copyBufferToTexture() 去读写深度值。
const depthTexture = device.createTexture({ size: [canvas.width, canvas.height], format: "depth24plus", usage: GPUTextureUsage.RENDER_ATTACHMENT,});尺寸必须和颜色附件一致,也就是画布绘图缓冲区的 canvas.width 和 canvas.height,不是 CSS 像素。
管线声明深度格式和比较函数。附件在渲染通道里清成 1,这样任何更近的片元(z 更小)都能通过 "less" 测试。
const pipeline = device.createRenderPipeline({ layout: "auto", vertex: { module, entryPoint: "vs_main", buffers: [ { arrayStride: 6 * 4, attributes: [ { shaderLocation: 0, offset: 0, format: "float32x3" }, { shaderLocation: 1, offset: 12, format: "float32x3" }, ], }, ], }, fragment: { module, entryPoint: "fs_main", targets: [{ format: canvasFormat }], }, primitive: { topology: "triangle-list" }, depthStencil: { format: "depth24plus", depthWriteEnabled: true, depthCompare: "less", },});const pass = encoder.beginRenderPass({ colorAttachments: [ { view: context.getCurrentTexture().createView(), clearValue: { r: 0.3, g: 0.3, b: 0.3, a: 1 }, loadOp: "clear", storeOp: "store", }, ], depthStencilAttachment: { view: depthTexture.createView(), depthClearValue: 1, depthLoadOp: "clear", depthStoreOp: "discard", },});depthStoreOp: "discard" 表示通道结束后不保留深度纹理的内容。这一帧不再读取深度时可以用它。下一帧如果还要沿用深度,改成 "store",并把 depthLoadOp 改成 "load"。
depthCompare 还可以是 "less-equal"、"greater"、"always" 等。透明物体有时关闭 depthWriteEnabled,只做测试;不透明物体通常两者都打开。
顶点着色器把缓冲里的 vec3f 直接写成裁剪坐标:
struct VertexOut { @builtin(position) position: vec4f, @location(0) color: vec3f,}
@vertexfn vs_main(@location(0) position: vec3f, @location(1) color: vec3f) -> VertexOut { var out: VertexOut; out.position = vec4f(position, 1.0); out.color = color; return out;}两张图使用同一批顶点:先画较近的蓝色三角形,再画较远的红色三角形。左边打开深度测试,重叠处仍是蓝色。右边不使用深度附件,后画的红色盖住蓝色。
depthCompare 为 less
没有深度附件
图形通道之外,GPU 还可以跑没有光栅化的计算着色器。