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

WebGPU中如何为Triangle Strip的每个子三角形设置不同颜色?

解决WebGPU Triangle Strip中单个三角形独立着色问题

问题根源分析

你当前代码中使用<private>变量fi来区分三角形的方式是错误的:

  • <private>变量属于顶点着色器的实例私有变量,每个顶点执行时会设置该值,而片元着色器阶段会对该值进行插值计算。
  • 第二个三角形由顶点1、2、3组成,其中顶点1/2的fi=1,顶点3的fi=2,片元中的fi会是1到2之间的插值结果,永远不会等于1,导致所有片元都返回绿色。

基础修复方案:使用Primitive Index

正确的做法是在片元着色器中使用**@builtin(primitive_index)**内置变量,它表示当前片元所属的图元(三角形)索引,从0开始计数,且不会被插值,能精准区分每个三角形。

修改后的测试代码:

body{ background-color: #000 }
canvas{ display: block; width: 600px; height: 400px; outline: 1px solid #666 }
<canvas width=900 height=600></canvas>
<script type="module">
const C = document.querySelector('canvas').getContext(`webgpu`);

const code=`
@vertex
fn vs( @builtin(vertex_index) vi: u32 ) -> @builtin(position) vec4f {
  if(vi<3){
    var T = array<vec2f, 3>( vec2f(0,0), vec2f(.4,.7), vec2f(.8,0) );
    return vec4f(T[vi],0,1);
  };
  return vec4f(.6,-.5,0,1);
}

@fragment
fn fs(@builtin(primitive_index) pi: u32) -> @location(0) vec4f {
  if(pi == 0){ // 第一个三角形(索引从0开始)
    return vec4f(.7,.2,.2,.5); // 红色
  };
  return vec4f(.3,.6,.4,.5);   // 绿色
}`;

const format = `bgra8unorm`;
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const Q = device.queue;
const A = {loadOp: `clear`, storeOp: `store`};
const O = {colorAttachments: [ A ]};

const module = device.createShaderModule({ code });
const P = device.createRenderPipeline({ 
  layout: `auto`, 
  primitive: { topology: `triangle-strip` },
  vertex: { module, entryPoint: `vs` },
  fragment: { module, entryPoint: `fs`, targets: [{ format }] }
});

C.configure({ device, format });

function F(){
  A.view = C.getCurrentTexture().createView();
  
  const E = device.createCommandEncoder();
  const R = E.beginRenderPass(O);
  R.setPipeline(P);
  R.draw(4);
  R.end();
  Q.submit([E.finish()]);
  
  requestAnimationFrame(F)
}

F()
</script>

扩展实现:鼠标点击生成带新颜色的Triangle Strip三角形

要实现点击新增带独立颜色的三角形,需要动态维护顶点数据和颜色数据,通过uniform缓冲区存储每个三角形的颜色,再用primitive_index索引取色。

完整实现代码:

body{ background-color: #000 }
canvas{ display: block; width: 600px; height: 400px; outline: 1px solid #666 }
<canvas width=900 height=600></canvas>
<script type="module">
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('webgpu');

// 初始顶点数据:第一个三角形的3个顶点
let vertices = new Float32Array([
  0, 0,     // 顶点0
  0.4, 0.7, // 顶点1
  0.8, 0,   // 顶点2
]);
// 初始颜色数组:对应每个三角形的颜色
let triangleColors = new Float32Array([
  0.7, 0.2, 0.2, 0.5, // 第一个三角形的红色
]);

const format = 'bgra8unorm';
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const queue = device.queue;

// 创建顶点缓冲区(支持动态更新)
const vertexBuffer = device.createBuffer({
  size: vertices.byteLength,
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
queue.writeBuffer(vertexBuffer, 0, vertices);

// 创建颜色uniform缓冲区(预留100个三角形的颜色空间)
const colorBufferSize = 100 * 4 * 4;
const colorBuffer = device.createBuffer({
  size: colorBufferSize,
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
queue.writeBuffer(colorBuffer, 0, triangleColors);

const shaderCode = `
struct VertexInput {
  @location(0) position: vec2f,
};

struct VertexOutput {
  @builtin(position) position: vec4f,
};

@group(0) @binding(0) var<uniform> triangleColors: array<vec4f, 100>;

@vertex
fn vs(input: VertexInput) -> VertexOutput {
  var output: VertexOutput;
  output.position = vec4f(input.position, 0.0, 1.0);
  return output;
}

@fragment
fn fs(@builtin(primitive_index) pi: u32) -> @location(0) vec4f {
  return triangleColors[pi];
}
`;

const shaderModule = device.createShaderModule({ code: shaderCode });

const vertexLayout = {
  arrayStride: 2 * 4, // 每个顶点2个float,共8字节
  attributes: [{
    shaderLocation: 0,
    offset: 0,
    format: 'float32x2',
  }],
};

const pipeline = device.createRenderPipeline({
  layout: 'auto',
  primitive: { topology: 'triangle-strip' },
  vertex: {
    module: shaderModule,
    entryPoint: 'vs',
    buffers: [vertexLayout],
  },
  fragment: {
    module: shaderModule,
    entryPoint: 'fs',
    targets: [{ format }],
  },
});

const bindGroup = device.createBindGroup({
  layout: pipeline.getBindGroupLayout(0),
  entries: [{
    binding: 0,
    resource: { buffer: colorBuffer },
  }],
});

ctx.configure({ device, format });

// 鼠标点击事件处理:添加新顶点和随机颜色
canvas.addEventListener('click', (e) => {
  // 转换鼠标坐标为WebGPU的NDC坐标(范围-1到1)
  const rect = canvas.getBoundingClientRect();
  const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
  const y = ((rect.top - e.clientY) / rect.height) * 2 + 1;

  // 更新顶点数组
  const newVertices = new Float32Array(vertices.length + 2);
  newVertices.set(vertices);
  newVertices[vertices.length] = x;
  newVertices[vertices.length + 1] = y;
  vertices = newVertices;

  // 生成随机颜色并更新颜色数组
  const r = Math.random();
  const g = Math.random();
  const b = Math.random();
  const newColors = new Float32Array(triangleColors.length + 4);
  newColors.set(triangleColors);
  newColors.set([r, g, b, 0.5], triangleColors.length);
  triangleColors = newColors;

  // 写入缓冲区
  queue.writeBuffer(vertexBuffer, 0, vertices);
  queue.writeBuffer(colorBuffer, 0, triangleColors);
});

function render() {
  const textureView = ctx.getCurrentTexture().createView();
  const encoder = device.createCommandEncoder();
  const renderPass = encoder.beginRenderPass({
    colorAttachments: [{
      view: textureView,
      loadOp: 'clear',
      storeOp: 'store',
      clearValue: { r: 0, g: 0, b: 0, a: 1 },
    }],
  });

  renderPass.setPipeline(pipeline);
  renderPass.setBindGroup(0, bindGroup);
  renderPass.setVertexBuffer(0, vertexBuffer);
  renderPass.draw(vertices.length / 2);

  renderPass.end();
  queue.submit([encoder.finish()]);
  requestAnimationFrame(render);
}

render();
</script>

关键要点说明

  1. Primitive Index的作用:@builtin(primitive_index)直接获取当前片元所属的三角形索引,无需插值,是区分Triangle Strip中单个三角形的标准方式。
  2. 动态缓冲区更新:顶点和颜色缓冲区设置COPY_DST用法,支持鼠标点击时动态写入新数据。
  3. 坐标转换:将屏幕鼠标坐标转换为WebGPU的NDC坐标(-1到1范围),确保顶点位置正确映射到画布。

内容的提问来源于stack exchange,提问作者Teamur

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 00:34:57