skip to Main Content

I am trying to set color per triangle.

But it is not working.

Only setting color per vertex works!

This is what I made:

<!DOCTYPE html><title>Color Per Triangle</title><style>
  body{ background-color: #000 }
canvas{ display: block; width: 600px; height: 400px; outline: 1px solid #343438 }
</style><canvas width=900 height=600></canvas><script type=module>
const canvas=document.body.firstChild, C=canvas.getContext(`webgpu`),

red=[.9,.3,.3,1], yel=[.7,.7,.3,1],

tri=[
  0,0,0,...red,  0, 1,0,...red,   1,0,0,...red, // ⚠️ Wish: avoid color repeating
  0,0,0,...yel,  0,-1,0,...yel,  -1,0,0,...yel,
];

/* ♻️ WANT THIS:
tri=[
  0,0,0,  0, 1,0,  1,0,0, ...red, // set color per triangle
  0,0,0,  0,-1,0, -1,0,0, ...yel,
]
*/

VB_$=window.VB_$=new Float32Array(tri); // Vertex Buffer Source

let code=`
struct _V {
  @location(0) p: vec3f,
  @location(1) c: vec4f,
};

struct V_ {
  @builtin(position) p: vec4f,
  @location(0)       c: vec4f,
};

@vertex fn vs(_v: _V) -> V_ {
  var v_: V_;
  v_.p = vec4f(_v.p, 1);
  v_.c = _v.c;
  return v_;
}

@fragment fn fs(v_: V_) -> @location(0) vec4f {
  return v_.c;
}
`,

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

 module = device.createShaderModule({ code }),
     PO = { layout: `auto`,
            vertex: { module, entryPoint: `vs`,
              buffers:[
                {
                  arrayStride: 28, // (3+4)*4.; (xyz+rgba)*4.
                   attributes: [
                    { shaderLocation:0, offset:0,  format:`float32x3` }, // p: vec3f [xyz]
                    { shaderLocation:1, offset:12, format:`float32x4` }, // c: vec4f [rgba]; 3*4.
                  ]
                }
              ]
            },
            fragment: { module, entryPoint: `fs`, targets: [{ format }] }
          },
      P = device.createRenderPipeline( PO ),

E,R, VB = device.createBuffer({ size: VB_$.byteLength, usage: 40 }); // VERTEX | COPY_DST

function draw(){
  A.view=C.getCurrentTexture().createView();
  
  E=device.createCommandEncoder();
  R=E.beginRenderPass(O);
  R.setPipeline(P);
  R.setVertexBuffer(0,VB);
  
  R.draw(6); // 2*3 = 2 triangles with 3 vertices each
  R.end();
  Q.submit([ E.finish() ])
}

C.configure({ device, format })
Q.writeBuffer(VB,0,VB_$);

draw()

</script>

Also, it need to do without using hardcoded colors.
Colors and vertex positions must be passed to shader using Vertex Buffer !

PS – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
I can’t use Uniform or Storage buffers,
because browser says: "Unsupported bit-flag set (descriptor range flags 10002).

D3D12 serialize root signature failed with E_INVALIDARG (0x80070057)
at CheckHRESULTImpl (….third_partydawnsrcdawnnatived3dD3DError.cpp:96)
at Initialize (….third_partydawnsrcdawnnatived3d12PipelineLayoutD3D12.cpp:350)"

2

Answers


  1. You can’t, all data is per-vertex. Even if you use something like SSBO, you still need to provide the lookup index in every vertex (and then of course pay the cost of the extra indirection).

    Login or Signup to reply.
  2. You can’t access vertex buffer data per triangle vs per vertex.
    You can use storage buffers. If you got an error you must have set your bits set wrong

    You can see using storage buffers for vertex data here

    Copying that example here and modifying to color per triangle

    // WebGPU Storage Buffer vertices
    // from https://webgpufundamentals.org/webgpu/webgpu-storage-buffer-vertices.html
    
    // A random number between [min and max)
    // With 1 argument it will be [0 to min)
    // With no arguments it will be [0 to 1)
    const rand = (min, max) => {
      if (min === undefined) {
        min = 0;
        max = 1;
      } else if (max === undefined) {
        max = min;
        min = 0;
      }
      return min + Math.random() * (max - min);
    };
    
    function createCircleVertices({
      radius = 1,
      numSubdivisions = 24,
      innerRadius = 0,
      startAngle = 0,
      endAngle = Math.PI * 2,
    } = {}) {
      // 2 triangles per subdivision, 3 verts per tri, 2 values (xy) each.
      const numVertices = numSubdivisions * 3 * 2;
      const vertexData = new Float32Array(numSubdivisions * 2 * 3 * 2);
      const colorData = new Uint32Array(numSubdivisions * 2)
    
      let offset = 0;
      const addVertex = (x, y) => {
        vertexData[offset++] = x;
        vertexData[offset++] = y;
      };
      
      let colorOffset = 0;
      const addColor = (r, g, b, a) => {
        colorData[colorOffset++] = 
           ((a | 0) << 24) |
           ((b | 0) << 16) |
           ((g | 0) <<  8) |
           ((r | 0) <<  0) ;
      };
      
      const randColor = () => {
        return [
          rand(256),
          rand(256),
          rand(256),
          255,
        ];
      };
    
      // 2 vertices per subdivision
      //
      // 0--1 4
      // | / /|
      // |/ / |
      // 2 3--5
      for (let i = 0; i < numSubdivisions; ++i) {
        const angle1 = startAngle + (i + 0) * (endAngle - startAngle) / numSubdivisions;
        const angle2 = startAngle + (i + 1) * (endAngle - startAngle) / numSubdivisions;
    
        const c1 = Math.cos(angle1);
        const s1 = Math.sin(angle1);
        const c2 = Math.cos(angle2);
        const s2 = Math.sin(angle2);
    
        // first triangle
        addVertex(c1 * radius, s1 * radius);
        addVertex(c2 * radius, s2 * radius);
        addVertex(c1 * innerRadius, s1 * innerRadius);
    
        addColor(...randColor());
    
        // second triangle
        addVertex(c1 * innerRadius, s1 * innerRadius);
        addVertex(c2 * radius, s2 * radius);
        addVertex(c2 * innerRadius, s2 * innerRadius);
    
        addColor(...randColor());
      }
    
      return {
        vertexData,
        colorData,
        numVertices,
      };
    }
    
    async function main() {
      const adapter = await navigator.gpu?.requestAdapter();
      const device = await adapter?.requestDevice();
      if (!device) {
        fail('need a browser that supports WebGPU');
        return;
      }
    
      // Get a WebGPU context from the canvas and configure it
      const canvas = document.querySelector('canvas');
      const context = canvas.getContext('webgpu');
      const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
      context.configure({
        device,
        format: presentationFormat,
      });
    
      const module = device.createShaderModule({
        code: `
          struct PerVertexData {
            position: vec2f,
          };
    
          struct VSOutput {
            @builtin(position) position: vec4f,
            @location(0) color: vec4f,
          };
          
          struct Uniforms {
            mat: mat4x4f,
          };
    
          @group(0) @binding(0) var<storage, read> perVertData: array<PerVertexData>;
          @group(0) @binding(1) var<storage, read> perTriData: array<u32>;
    
          @vertex fn vs(
            @builtin(vertex_index) vertexIndex : u32,
          ) -> VSOutput {
            let triangleIndex = vertexIndex / 3;
            let vert = perVertData[vertexIndex];
            let tri = perTriData[triangleIndex];
    
            var vsOut: VSOutput;
            vsOut.position = vec4f(vert.position, 0.0, 1.0);
            vsOut.color = unpack4x8unorm(tri);
            return vsOut;
          }
    
          @fragment fn fs(vsOut: VSOutput) -> @location(0) vec4f {
            return vsOut.color;
          }
        `,
      });
    
      const pipeline = device.createRenderPipeline({
        label: 'storage buffer vertices',
        layout: 'auto',
        vertex: {
          module,
          entryPoint: 'vs',
        },
        fragment: {
          module,
          entryPoint: 'fs',
          targets: [{ format: presentationFormat }],
        },
      });
    
      const kNumObjects = 100;
      const objectInfos = [];
    
      // setup a storage buffer with vertex data
      const { vertexData, colorData, numVertices } = createCircleVertices({
        radius: 0.5,
        innerRadius: 0.25,
      });
      const vertexStorageBuffer = device.createBuffer({
        label: 'storage buffer per vert data',
        size: vertexData.byteLength,
        usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
      });
      device.queue.writeBuffer(vertexStorageBuffer, 0, vertexData);
      const colorStorageBuffer = device.createBuffer({
        label: 'storage buffer per color data',
        size: colorData.byteLength,
        usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
      });
      device.queue.writeBuffer(colorStorageBuffer, 0, colorData);
    
      const bindGroup = device.createBindGroup({
        label: 'bind group for objects',
        layout: pipeline.getBindGroupLayout(0),
        entries: [
          { binding: 0, resource: { buffer: vertexStorageBuffer }},
          { binding: 1, resource: { buffer: colorStorageBuffer }},
        ],
      });
    
      const renderPassDescriptor = {
        label: 'our basic canvas renderPass',
        colorAttachments: [
          {
            // view: <- to be filled out when we render
            clearValue: [0.3, 0.3, 0.3, 1],
            loadOp: 'clear',
            storeOp: 'store',
          },
        ],
      };
    
      function render() {
        // Get the current texture from the canvas context and
        // set it as the texture to render to.
        renderPassDescriptor.colorAttachments[0].view =
            context.getCurrentTexture().createView();
    
        const encoder = device.createCommandEncoder();
        const pass = encoder.beginRenderPass(renderPassDescriptor);
        pass.setPipeline(pipeline);
    
        pass.setBindGroup(0, bindGroup);
        pass.draw(numVertices);
    
        pass.end();
    
        const commandBuffer = encoder.finish();
        device.queue.submit([commandBuffer]);
      }
    
      const observer = new ResizeObserver(entries => {
        for (const entry of entries) {
          const canvas = entry.target;
          const width = entry.contentBoxSize[0].inlineSize;
          const height = entry.contentBoxSize[0].blockSize;
          canvas.width = Math.max(1, Math.min(width, device.limits.maxTextureDimension2D));
          canvas.height = Math.max(1, Math.min(height, device.limits.maxTextureDimension2D));
          // re-render
          render();
        }
      });
      observer.observe(canvas);
    }
    
    function fail(msg) {
      alert(msg);
    }
    
    main();
    @import url(https://webgpufundamentals.org/webgpu/resources/webgpu-lesson.css);
    html, body {
      margin: 0;       /* remove the default margin          */
      height: 100%;    /* make the html,body fill the page   */
    }
    canvas {
      display: block;  /* make the canvas act like a block   */
      width: 100%;     /* make the canvas fill its container */
      height: 100%;
    }
    <canvas></canvas>

    Note that the solution above assumed the goal was to have 1 piece of color data per triangle. If the goal is to have 1 color per triangle ignoring extra data then you can repeat the data. You can also use @interpolate(flat) in your inter-stage color variable to have it not interpolate the colors.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search