Writing shaders for WebGPU in WGSL
Currently, all shaders used by Babylon.js are written in GLSL and are converted to WGSL (the only shader language that WebGPU knows about) by some special tools.
So, even in WebGPU, if you use a CustomMaterial
or a PBRCustomMaterial
to inject some custom shader code, you must write it in GLSL.
If you want to write shader code in the WGSL language, you can either write a compute shader or use the ShaderMaterial class to wrap a vertex/fragment shader. The latter is the subject of this page.
Using ShaderMaterial to write WGSL code
You can use the ShaderMaterial
class to write WGSL code in much the same way you use it to write GLSL but with some small differences.
Setting the right shader language
You must set the shaderLanguage
property to BABYLON.ShaderLanguage.WGSL
in the options
parameter you pass to the constructor.
For eg:
const mat = new BABYLON.ShaderMaterial("shader", scene, { vertex: "myShader", fragment: "myShader", }, { attributes: ["position", "uv", "normal"], uniformBuffers: ["Scene", "Mesh"], shaderLanguage: BABYLON.ShaderLanguage.WGSL, });
Using the WGSL shader store
If using the shader store, you must put your WGSL code in BABYLON.ShaderStore.ShadersStoreWGSL
instead of BABYLON.ShaderStore.ShadersStore
.
For eg:
BABYLON.ShaderStore.ShadersStoreWGSL["myShaderVertexShader"]=` #include<sceneUboDeclaration> #include<meshUboDeclaration> ...`;
BABYLON.ShaderStore.ShadersStoreWGSL["myShaderFragmentShader"]=` varying vPositionW : vec3<f32>; varying vUV : vec2<f32>; ...`;
Declaration of the entry points
You must also declare the entry point for the vertex and fragment shader in a special way.
Vertex:
@stage(vertex)fn main(input : VertexInputs) -> FragmentInputs { ...}
Fragment:
@stage(fragment)fn main(input : FragmentInputs) -> FragmentOutputs { ...}
Using pre-defined uniforms
To use the pre-defined uniforms of the scene (view
, viewProjection
, projection
, vEyePosition
) and mesh (world
, visibility
), you must include the appropriate file(s) in the shader code:
#include<sceneUboDeclaration>#include<meshUboDeclaration>
and add the uniform buffer name(s) to the uniformBuffers
option of the ShaderMaterial
class constructor:
const mat = new BABYLON.ShaderMaterial("shader", scene, { vertex: "myShader", fragment: "myShader", }, { attributes: ["position", "uv", "normal"], uniformBuffers: ["Scene", "Mesh"], shaderLanguage: BABYLON.ShaderLanguage.WGSL, });
In the WGSL code, you access a uniform by prefixing its name by scene.
or mesh.
for the scene or mesh uniforms, respectively:
@stage(vertex)fn main(input : VertexInputs) -> FragmentInputs { gl_Position = scene.viewProjection * mesh.world * vec4<f32>(position, 1.0);}
Special syntax used in WGSL code
Contrary to compute shaders that are using plain WGSL code, shader code you write for ShaderMaterial
must use some special syntax to work with the existing workflow. To ease developers' job, the syntax is the same one used in GLSL:
- declaring a varying variable:
varying varName : varType;
- declaring an attribute variable:
attribute varName : varType;
- declaring a uniform variable:
uniform varName : varType;
Notes:
- When using the
uniform varName : varType
syntax, you access the variable by doinguniforms.varName
, not simplyvarName
. The variables declared that way can be set from the javascript code by using the regular methods of theShaderMaterial
class (setFloat
,setInt
, etc) as with GLSL varType
must use a WGSL syntax, not GLSL! For eg:varying vUV : vec2<f32>;
- you must NOT add the
@group(X) @binding(Y)
decoration! The system will add them automatically
You can also use some built-ins that have the same names than in GLSL:
- in vertex shaders:
gl_VertexID
,gl_InstanceID
,gl_Position
- in fragment shaders:
gl_FragCoord
,gl_FrontFacing
,gl_FragDepth
,gl_FragColor
Using new objects available in WGSL
You can use the standard WGSL syntax to declare:
- custom uniform buffers:
struct MyUBO { scale: f32,};
var<uniform> myUBO: MyUBO;
- storage textures:
var storageTexture : texture_storage_2d<rgba8unorm,write>;
- storage buffers:
struct Buffer { items: array<f32>,};var<storage,read_write> storageBuffer : Buffer;
- external textures:
var videoTexture : texture_external;
On the javascript side, you have the corresponding methods to set a value to these variables:
- uniform buffer:
setUniformBuffer(name, buffer)
- storage texture: same method than for regular textures (
setTexture(name, texture)
) - storage buffer:
setStorageBuffer(name, buffer)
- external texture:
setExternalTexture(name, buffer)
Examples
This playground is a basic example of using WGSL in a ShaderMaterial
: Basic example of WGSL with ShaderMaterial
As when using GLSL, ShaderMaterial
supports morphs, bones and instancing in WGSL. You will need to add the appropriate includes in your code to support these features. See how it is done in this playground (this example also demonstrates how to use a storage texture and a storage buffer): Advanced usage of the ShaderMaterial class
You can also use the new in 5.0 baked vertex animation feature as well as clip planes. See: Using BVA and clip planes in WGSL
Playing videos with the regular VideoTexture is slow in WebGPU because there are a lot of texture copies that occur behind the scene in the browser. The texture_external
type object is meant for fast video playing in WebGPU. This playground shows how to use the ShaderMaterial
class to implement video playing with texture_external
: Video playing with the ShaderMaterial class
Sampling a depth texture is not always possible, see Sampling a depth texture for more details. This playground will let you sample a depth texture both in WebGL and WebGPU: Sampling a depth texture