Custom WebGPU fragment shaders

Hi team,

I was wondering if theres any information on webgpu fragment shaders and how to correctly pass uniforms. The only example right now is the particles example


    const material = new pc.ShaderMaterial({
        uniqueName: 'ParticleRenderShader',
        vertexCode: shaderSource,
        fragmentCode: shaderSource,
        shaderLanguage: pc.SHADERLANGUAGE_WGSL,

        // For now WGSL shaders need to provide their own bind group formats as they aren't processed.
        // This has to match the ub_mesh struct in the shader.
        meshUniformBufferFormat: new pc.UniformBufferFormat(app.graphicsDevice, [
            new pc.UniformFormat('matrix_model', pc.UNIFORMTYPE_MAT4)
        ]),
        meshBindGroupFormat: new pc.BindGroupFormat(app.graphicsDevice, [
            // particle storage buffer in read-only mode
            new pc.BindStorageBufferFormat('particles', pc.SHADERSTAGE_VERTEX | pc.SHADERSTAGE_FRAGMENT, true)
        ])
    });

But I want to understand this comment

       // For now WGSL shaders need to provide their own bind group formats as they aren't processed.
        // This has to match the ub_mesh struct in the shader.

What i want to pass is this:

      meshUniformBufferFormat: new UniformBufferFormat(this.device, [
        new UniformFormat('matrix_viewProjection', UNIFORMTYPE_MAT4),
        new UniformFormat('matrix_viewProjectionInverse', UNIFORMTYPE_MAT4),
        new UniformFormat('cameraPosition', UNIFORMTYPE_MAT4),
      ]),

But I’m struggling to see where the magic “matrix_viewProjection” uniform comes from in the engine code and how exactly to pass my own ones.

This is the last piece of the puzzle!

Thanks in advance!

I have part of an answer (taken from playcanvas source code), I believe this is the contents of ub_view?

so now the question is how do I add the inverseViewProjection to the list? (view_position = camera position correct?)

Yep, it comes from that internal declaration.
If you need other uniforms the engine internally sets (for WebGL), just add them to your mesh UB.

So whats not clear to me is exactly where these are being assigned…


meshUniformBufferFormat: new UniformBufferFormat(this.device, [
        new UniformFormat('matrix_viewProjection', UNIFORMTYPE_MAT4),
        new UniformFormat('matrix_viewProjectionInverse', UNIFORMTYPE_MAT4),
        new UniformFormat('cameraPosition', UNIFORMTYPE_MAT4),
      ]),

When I call meshUniformBuffer Format, how do I know which group they belong to? how exactly does passing my own meshUniformBufferFormat relate to the built in one?

Feels like the current api has too much magic, would prefer it if it was more explicit with a clearer way to understand how to access built in uniforms and which ones are available

We have ‘view’ - this is constant per layer rendering of a camera.
And the rest goes to ‘mesh’ UB.

internally, all is matched by the name. The engine knows how to set up matrix_viewProjectionInverse, and loops over all names in a mesh UB and sets those appropriately.

Mesh group is currently under your control, so you can list them in any order.

So If I have entries that are matched by the “view” ub they go there, everything else goes to the “mesh” ub? These are separate bind grounds then presumably?

I’m assuming this doesn’t apply to the compute shader and it works separately (that part works fine anyway)

Correct. If you need some variable from view UB, you need to add this view UB to your shader, but its content needs to match that declaration from the engine - order, types, all of it, at least up to a point of your variables, and the engine supplies that UB with a fixed format.

The rest stick to mesh UB, no rules there, anything you need.

Yes those are separate bind groups.

As soon as I try and access ubView.view_position I’m getting this seemingly unrelated error

Having tested my shader I can see that view_position is being read correctly so my shader is runnning but it looks like its somehow interferring with the shadow rendering?

Maybe try running this with the debug engine to get more info (labels and so on)

Note that when the mesh is rendered to shadow, a shadow variant of the shader should be created. Shadow renderer has this ‘mesh’ UB format:

It could be that your view UB declaration in the shader has more than just this.

yes that makes sense, setting castShadows: false solves it (its obviously not shadow casting lol)

1 Like