跳转到内容

渲染管线

渲染管线描述 GPU 如何把顶点变成像素:用哪个着色器、图元怎么组装、结果写进哪种纹理格式。它在绘制之前创建,绘制时只是被绑定,然后调用 draw()。

三角形实例已经跑通过一条管线。这一章把那几步拆开。

WGSL 源码先编译成 GPUShaderModule。编译错误不会让 createShaderModule() 抛异常,需要再读 getCompilationInfo():

const shaderModule = device.createShaderModule({
label: "三角形着色器",
code: wgslSource,
});
const info = await shaderModule.getCompilationInfo();
const errors = info.messages.filter((message) => message.type === "error");
if (errors.length > 0) {
throw new Error(errors.map((message) => message.message).join("\n"));
}

顶点和片元可以放在同一个模块里,用不同的 entryPoint。也可以分成两个模块。

const pipeline = device.createRenderPipeline({
label: "三角形管线",
layout: "auto",
vertex: {
module: shaderModule,
entryPoint: "vs_main",
},
fragment: {
module: shaderModule,
entryPoint: "fs_main",
targets: [{ format: canvasFormat }],
},
primitive: {
topology: "triangle-list",
},
});

各字段的含义:

  • layout: "auto" 让实现根据着色器里的 @group / @binding 生成管线布局。没有外部资源时,这样最省事。显式布局在绑定组里再写。
  • vertex.entryPoint 必须是一个 @vertex 函数。
  • fragment.targets[0].format 必须和颜色附件的格式一致。画到画布上时,用 navigator.gpu.getPreferredCanvasFormat() 的返回值,一般是 "bgra8unorm" 或 "rgba8unorm"。
  • primitive.topology 决定顶点如何组成图元。默认是 "triangle-list":每 3 个顶点一个三角形。

常用拓扑:

topology含义
"triangle-list"每 3 个顶点一个独立三角形
"triangle-strip"相邻三角形共用一条边
"line-list"每 2 个顶点一条独立线段
"line-strip"折线,相邻顶点连成一段
"point-list"每个顶点一个点

frontFace 默认 "ccw"(逆时针为正面),cullMode 默认 "none"(不剔除)。需要背面剔除时再改。

管线不会自己画。每帧要录制命令:

  1. context.getCurrentTexture() 取出画布当前要显示的纹理,再 createView()。这个视图不能跨帧复用。
  2. beginRenderPass() 声明颜色附件。loadOp: "clear" 用 clearValue 清屏,storeOp: "store" 把结果留下。
  3. setPipeline()、必要时 setVertexBuffer() 和 setBindGroup(),然后 draw(vertexCount)。
  4. pass.end(),encoder.finish(),最后 device.queue.submit()。
const encoder = device.createCommandEncoder();
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",
},
],
});
pass.setPipeline(pipeline);
pass.draw(3);
pass.end();
device.queue.submit([encoder.finish()]);

draw(3) 的 3 是顶点个数,不是三角形个数。"triangle-list" 下 3 个顶点刚好一个三角形。顶点坐标仍然可以像三角形示例那样写在着色器的数组里;下一章改成从缓冲区读取。

同一个三角形,左边按 "triangle-list" 填充,右边用 "line-strip" 把三个顶点连回去。线框多画了一次起点,所以是 4 个顶点。

triangle-list

line-strip

坐标还写在着色器里。换成 缓冲区 之后,网格数据就可以从 JavaScript 上传。管线对象的字段说明见 GPURenderPipeline。