playcanvas / engine

JavaScript game engine built on WebGL, WebGPU, WebXR and glTF

Home Page:https://playcanvas.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Implement material level uniform buffer (WebGPU initially)

mvaligursky opened this issue · comments

Current status

On WebGPU, the plan is to have 3 levels of uniform buffers:

  • view (implemented) - constant uniforms for a camera / layer combination, sets up for each forward renderer looping over mesh on that layer
  • material (missing) - details below
  • mesh (implemented) - all uniforms not part of the other two existing uniform buffers. This updates for each draw call, and we want to minimise the amount of uniforms stored here

Material Uniform buffer(s)

  • It will store material properties supplied by both the Material instance, as well as overrides specified on the meshInstance.
  • Material has a single shader assigned to it, but internally stores this shader compiled multiple times in variants map. This handles rendering of the same material with different sets of lights, or color buffer vs shadow map, or different meshInstance definitions (lightmapping, or UV1 attribute ..). When a meshInstance is rendered, it uses a matching shader from this map.

There are two ways to implement this:

  1. Material has a single uniform buffer (unlikely option)
  • We create a single Uniform Buffer, which would contain all material properties used by all variations. The list can be obtained from material, but it lacks the parameter types. Types would need to be known / added for this to work. Currently those are collected when the shader finishes compilation.
  • This is complicated / impractical to do, as we do not know all shader variants that we end up generating for a material.
  1. Material has multiple uniform buffers (most likely implementation)
  • Material.variants will store an instance of UniformBuffer (a new uniform buffer for each compiled version of the shader).
  • When the material properties are updated, the Material’s version will be incremented. When a shader variant with a uniform buffer is used for rendering, it will be updated with material properties if the version is non-matching.
  • If meshInstance has some override material properties, it needs its unique Material level UniformBuffer for this mesh. MeshInstance._shader array stores shader variants used by the mesh instance, and this will be extended to store matching array of UniformBuffer instances. When its version does not match material’s UB’s version, it will memcopy it, and apply meshInstance properties on top.

There are sometimes even global uniform, e.g. time or windDirection that would be re-used by multiple shaders.

There are sometimes even global uniform, e.g. time or windDirection that would be re-used by multiple shaders.

Yep that already works fine. Whatever is not in the view / material uniform blocks is automatically added to the mesh uniform block .. so that would be the case with those global uniforms.

At some point we might enable users to add global uniforms to the view UB, but for now they'd be per mesh.

In terms of architecture, I would very much like a Material (or derived class) be just the material properties (and state) plus the update function to mark the material as dirty. But all details relating to uniforms/rendering should ideally be owned/managed by the renderer. This would help simplify our material classes massively.