如何在WebGPU中创建2D裁剪遮罩?
WebGPU实现2D裁剪遮罩的正确方案
你当前的代码只是先后绘制了红色三角形和绿色线条,完全没有启用任何裁剪逻辑,自然无法实现内容被路径裁剪的效果。下面提供WebGPU中实现2D形状裁剪的常用方案——**模板缓冲(Stencil Buffer)**方案,这是实现区域裁剪的标准做法。
方案:使用模板缓冲实现裁剪
核心逻辑
- 配置带模板缓冲的渲染环境,用于标记裁剪区域
- 先绘制填充的裁剪路径,将对应区域标记到模板缓冲中
- 绘制待裁剪内容时,通过模板测试限制仅渲染标记区域内的像素
修改后的完整代码示例
// 假设已初始化device、format、basicShaderModule等通用变量 // 1. 创建用于模板测试的纹理 const stencilTexture = device.createTexture({ size: [canvas.width, canvas.height], format: "depth24plus-stencil8", usage: GPUTextureUsage.RENDER_ATTACHMENT, }); // 2. 配置包含颜色+模板附件的渲染通道 const renderPassDescriptor = { colorAttachments: [ { view: context.getCurrentTexture().createView(), clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, loadOp: "clear", storeOp: "store", }, ], depthStencilAttachment: { view: stencilTexture.createView(), depthClearValue: 1.0, depthLoadOp: "clear", depthStoreOp: "store", stencilClearValue: 0, stencilLoadOp: "clear", stencilStoreOp: "store", }, }; const commandEncoder = device.createCommandEncoder(); const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); // -------------------------- // 第一步:绘制裁剪路径到模板缓冲 // -------------------------- const stencilPipeline = device.createRenderPipeline({ vertex: { module: basicShaderModule, entryPoint: 'vertex_main', buffers: [{ attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' }], arrayStride: 8, stepMode: 'vertex' }], }, fragment: { module: basicShaderModule, entryPoint: 'fragment_main', targets: [{ format }] }, primitive: { topology: 'triangle-strip', // 必须用填充图形,线条无法形成连续裁剪区域 }, depthStencil: { stencilFront: { compare: "always", // 无条件通过测试 passOp: "replace", // 用参考值覆盖模板缓冲对应位置 }, stencilReference: 1, // 模板标记值设为1 stencilReadMask: 0xff, stencilWriteMask: 0xff, }, layout: 'auto', }); passEncoder.setPipeline(stencilPipeline); // 裁剪路径顶点(对应你预期的裁剪区域) const stencilVertices = new Float32Array([0, -1, 1, -1, -1, 1, 0, -1]); const stencilBuffer = device.createBuffer({ size: stencilVertices.byteLength, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, }); device.queue.writeBuffer(stencilBuffer, 0, stencilVertices); passEncoder.setVertexBuffer(0, stencilBuffer); passEncoder.draw(4); // 绘制后,模板缓冲中裁剪区域的像素值为1 // -------------------------- // 第二步:绘制待裁剪内容,启用模板测试 // -------------------------- const contentPipeline = device.createRenderPipeline({ vertex: { module: basicShaderModule, entryPoint: 'vertex_main', buffers: [{ attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' }], arrayStride: 8, stepMode: 'vertex' }], }, fragment: { module: basicShaderModule, entryPoint: 'fragment_main', targets: [{ format }] }, primitive: { topology: 'triangle-strip', }, depthStencil: { stencilFront: { compare: "equal", // 仅渲染模板值等于1的区域 passOp: "keep", // 保持模板缓冲值不变 }, stencilReference: 1, stencilReadMask: 0xff, stencilWriteMask: 0x00, // 禁止修改模板缓冲 }, layout: 'auto', }); passEncoder.setPipeline(contentPipeline); const uniformValues = new Float32Array(4); uniformValues.set([1, 0, 0, 1], 0); const uniformBuffer = device.createBuffer({ size: uniformValues.byteLength, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }); device.queue.writeBuffer(uniformBuffer, 0, uniformValues); passEncoder.setBindGroup(0, device.createBindGroup({ layout: contentPipeline.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer: uniformBuffer } }], })); const contentVertices = new Float32Array([-1, -1, 1, -1, 1, 1]); const contentBuffer = device.createBuffer({ size: contentVertices.byteLength, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, }); device.queue.writeBuffer(contentBuffer, 0, contentVertices); passEncoder.setVertexBuffer(0, contentBuffer); passEncoder.draw(3); // 仅裁剪区域内的内容会被渲染 passEncoder.end(); device.queue.submit([commandEncoder.finish()]);
关键注意点
- 裁剪路径必须使用填充图形(如
triangle-strip),线条无法在模板缓冲中形成连续的可裁剪区域 - 如果需要反向裁剪(仅显示区域外内容),只需将模板测试的
compare参数改为not-equal即可 - 模板缓冲的格式需选择支持深度+模板的类型,如
depth24plus-stencil8
结果对比
| 当前结果 | 预期结果 |
|---|---|
![]() | ![]() |
内容的提问来源于stack exchange,提问作者plantain
相关产品推荐
相关产品推荐



