You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在WebGPU中创建2D裁剪遮罩?

WebGPU实现2D裁剪遮罩的正确方案

你当前的代码只是先后绘制了红色三角形和绿色线条,完全没有启用任何裁剪逻辑,自然无法实现内容被路径裁剪的效果。下面提供WebGPU中实现2D形状裁剪的常用方案——**模板缓冲(Stencil Buffer)**方案,这是实现区域裁剪的标准做法。

方案:使用模板缓冲实现裁剪

核心逻辑

  1. 配置带模板缓冲的渲染环境,用于标记裁剪区域
  2. 先绘制填充的裁剪路径,将对应区域标记到模板缓冲中
  3. 绘制待裁剪内容时,通过模板测试限制仅渲染标记区域内的像素

修改后的完整代码示例

// 假设已初始化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 04:53:18