Skip to content

WGSL manual page #749

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jun 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions docs/user-manual/graphics/shaders/glsl-specifics.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ The following sections outline key aspects of writing GLSL shaders for PlayCanva

### Attributes

Attributes define per-vertex input data. They must be declared using the following syntax:
Attributes define per-vertex input data, and can only be used in the vertex shader. They must be declared using the following syntax:

```glsl
attribute vec2 aUv0;
```

The attribute name must match the name specified in the `attributes` property when creating the [ShaderMaterial][1].
The attribute names must match the names specified in the `attributes` property when creating the [ShaderMaterial][1].

:::note

Expand All @@ -39,6 +39,12 @@ uniform vec3 view_position;

The engine automatically sets appropriate uniform values when rendering.

:::note

Currently, our uniform system supports only simple types, including `float`, `int`, `uint`, as well as vectors and matrices (e.g., `vec4`, `mat4`). Structs are not supported at this time, so all uniform values must be declared as individual variables of basic types.

:::

### Varyings

Varyings are used to pass values from the vertex shader to the fragment shader. They must be declared using standard GLSL syntax:
Expand Down
46 changes: 29 additions & 17 deletions docs/user-manual/graphics/shaders/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,36 @@ To create an instance of `ShaderMaterial`, these are the steps:

Create a description of your shader:

``` javascript
```javascript
const shaderDesc = {
uniqueName: 'MyShader',
shaderLanguage: pc.SHADERLANGUAGE_GLSL,
vertexCode: `
// write your vertex shader source code
`,
fragmentCode: `
// write your fragment shader source code
`,
attributes: {
aPosition: pc.SEMANTIC_POSITION,
aUv0: pc.SEMANTIC_TEXCOORD0
}
};
uniqueName: 'MyShader',
vertexGLSL: `
// write your vertex shader source code in GLSL language
`,
fragmentGLSL: `
// write your fragment shader source code in GLSL language
`,
vertexWGSL: `
// write your vertex shader source code in WGSL language
`,
fragmentWGSL: `
// write your fragment shader source code in WGSL language
`,
attributes: {
aPosition: pc.SEMANTIC_POSITION,
aUv0: pc.SEMANTIC_TEXCOORD0
}
};

```

Then create instances of your material, which you can use for rendering:

``` javascript
```javascript
const material = new pc.ShaderMaterial(shaderDesc);
```

The shader source code can be written in GLSL if you're targeting the WebGL2 or WebGPU platforms, or in WGSL if you're targeting WebGPU only.
The shader source code can be written in GLSL if you're targeting the WebGL2 or WebGPU platforms, or in WGSL if you're targeting WebGPU only, or both.

:::note

Expand All @@ -48,7 +54,9 @@ Before the shader is used, a preprocessing step is applied, allowing you to mana

This preprocessing step follows a typical C-like preprocessor structure, handling directives such as `#define`, `#if`, `#else`, `#endif`, and similar. This gives you fine-grained control over how the shader code is compiled and customized for different use cases.

### Material Shader Defines {#material-shader-defines}
You can also use a `#include` directive to include one of the registered shader chunks. For example: `#include "screenDepthPS"`

### Material Shader Defines

Shader defines can be set on a per-material basis, allowing dynamic customization of shader behavior. For example:

Expand Down Expand Up @@ -207,4 +215,8 @@ And Each created shader will be logged in the browser console, where you can ins

For further information, refer to the [ShaderMaterial API documentation](https://api.playcanvas.com/engine/classes/ShaderMaterial.html).

### Compute shaders

Compute shaders are currently supported by the engine when using WebGPU, but their integration is still evolving. Full documentation will be provided as the system matures and stabilizes. In the meantime, we recommend studying the available engine examples, which demonstrate how compute shaders can be written, dispatched, and used within the current framework.

[1]: /user-manual/graphics/physical-rendering/physical-materials/
7 changes: 4 additions & 3 deletions docs/user-manual/graphics/shaders/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ The debug version of the Engine will report any API changes to the runtime conso

![Console output](/img/user-manual/graphics/shader-chunk-migrations/console-warning.png)

Once an application's chunks have been updated to the latest API they must be flagged as such. For example, after updating a material's custom chunks to the latest engine release (say v1.55), specify this in the chunks object as follows:
Once an application's chunks have been updated to the latest API they must be flagged as such. For example, after updating a material's custom chunks to the latest engine release (say v2.8), specify this in the chunks object as follows:

```javascript
material.chunks.diffusePS = '...';
material.chunks.APIVersion = pc.CHUNKAPI_1_55;
const materialChunksGLSL = material.getShaderChunks(pc.SHADERLANGUAGE_GLSL);
materialChunksGLSL.set('diffusePS', '...');
material.shaderChunksVersion = '2.8';
```

By doing this you will no longer see warning messages in the console.
Expand Down
271 changes: 270 additions & 1 deletion docs/user-manual/graphics/shaders/wgsl-specifics.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,273 @@ title: WGSL Specifics
sidebar_position: 2
---

Support for WGSL in the PlayCanvas engine is under active development. Detailed information on WGSL shader requirements and integration will be provided once the implementation is finalized.
WGSL shaders used by the PlayCanvas engine must satisfy certain requirements. These requirements allow the engine to correctly integrate shaders, ensuring they receive the necessary resources such as attributes, uniforms, and varyings.

The following sections outline key aspects of writing WGSL shaders for PlayCanvas.

### Simplified Shader Interface Syntax

In standard WGSL (WebGPU Shading Language), declaring uniforms, attributes, and varyings requires explicitly specifying a `@group` and `@binding` index for each resource. This can be verbose and error-prone, especially for common patterns.

To improve usability and streamline shader development, we adopt a simplified syntax similar to GLSL. In this model, you do not need to specify `@group` or `@binding` attributes manually—these are automatically assigned and managed by the engine based on a predefined layout.

#### Example Comparison

Standard WGSL:

```wgsl
struct Uniforms {
uTime: f32,
};

struct FragmentInput {
@location(0) uv0: vec2f,
@builtin(position) position: vec4f
};

@group(0) @binding(0) var<uniform> ub: Uniforms;

@fragment fn fragmentMain(FragmentInput) -> @location(0) vec4f {
// body
}
```

In contrast, the simplified syntax avoids a lot of the boilerplate.

```wgsl
uniform uTime: f32;
varying uv0: vec2f;

@fragment fn fragmentMain(input: FragmentInput) -> FragmentOutput {
// body
}
```

### Attributes

Attributes define per-vertex input data, and can only be used in the vertex shader. They must be declared using the following syntax:

```wgsl
attribute aUv0: vec2f;
```

Internally, a `VertexInput` struct is automatically created and populated with all the attributes. Attributes can be accessed from the structure passed to the main function, but also in the global scope.

```wgsl
attribute aUv0: vec2f;

@vertex fn vertexMain(input: VertexInput) -> VertexOutput {

// access it using input passed to the main function
var myUv1 = input.aUv0;

// but also as a global variable (particularly useful inside other functions)
var myUv2 = aUv0;
}
```

As part of the `VertexInput` structure, and also in the global scope, these built-in attributes are automatically available:

```wgsl
vertexIndex: @builtin(vertex_index)
instanceIndex: @builtin(instance_index)
```

The attribute names must match the names specified in the `attributes` property when creating the [ShaderMaterial][1].

### Uniforms

Uniforms are used to pass *numerical resources* from the engine to the shader.

Uniforms are declared using this simplified syntax:

```wgsl
uniform view_position: vec3f;
uniform tints: array<vec3f, 4>;
uniform weights: array<f32, 8>;
```

Internally, uniforms are automatically placed in uniform buffers, and in the shader code are accessed using a `uniform.` prefix:

```wgsl
var pos = uniform.view_position;
var color = uniform.tints[2];

// f32 and vec2<> types used in an array are due to alignment requirements wrapped
// in an aligned structure, and the value is available as its `element` property.
// struct WrappedF32 { @size(16) element: f32 }
var weight = uniform.weights[3].element;
```

The engine automatically sets appropriate uniform values when rendering.

:::note

Currently, our uniform system supports only simple types, including `f32`, `i32`, `u32`, as well as vectors and matrices (e.g., `vec4f`, `mat4x4f`). Structs are not supported at this time, so all uniform values must be declared as individual variables of basic types.

:::

### Texture Resources

Texture resources are using simplified WGSL syntax, where specifying a `@group` and `@binding` index for each resource has to be omitted.

#### Sampling Textures

In WGSL, textures and samplers are treated as separate objects, unlike in GLSL, where those are combined.

When you want to sample a texture (i.e. retrieve filtered texel values), you must provide a texture object *directly followed* by a sampler.

```wgsl
// 2d texture with a sampler declaration
var diffuseMap: texture_2d<f32>;
var diffuseMapSampler: sampler;

// texture sampling
var texel = textureSample(diffuseMap, diffuseMapSampler, coords);
```

#### Fetching Textures

If you only need to read raw texel data (i.e., without filtering, mipmapping, or addressing modes), you can use `textureLoad` instead of `textureSample`. This is called non-filtered access, or simply texel fetching.

In such cases, no sampler is required or allowed. For example:

```wlsl
// cubemap texture without a sampler
var noSamplerMap: texture_cube<f32>;

// fetching the texel
let texel = textureLoad(noSamplerMap, coords, mipLevel);
```

#### Unfilterable Textures

WebGPU supports unfilterable float textures, which are typically used for specialized purposes such as sampling from depth textures, where filtering is not allowed. However, WGSL does not provide a distinct sample type in the syntax for declaring these unfilterable float textures. To address this limitation and enable proper bind group auto-generation based on shader declarations, we introduce a new sample type called `uff` (unfilterable-float).

Using `uff`, you can explicitly declare an unfilterable-float texture in the shader like this:

```wgsl
// declaration
var colorMap: texture_2d<uff>;

// sampling
let data: vec4f = textureLoad(colorMap, uv, 0);
```

This extension allows the engine to correctly interpret the texture’s sampling capabilities and bind it accordingly.

:::note

Support for `texture_external` is not available yet, and will be added in the future.

:::

### Storage Buffers

Storage buffers are GPU-accessible memory resources that allow shaders to read and write arbitrary data with random access. In WGSL, they are declared using `var<storage>` and are ideal for working with large or structured datasets such as particle systems, compute data, or dynamic geometry. Unlike uniforms, storage buffers support both read and write access (with appropriate access control).

Example of using storage buffer in Vertex Shader:

```wgsl
struct Particle {
position: vec3f,
velocity: vec3f,
}

// particle storage buffer in read-only mode
var<storage, read> particles: array<Particle>;
```

### Varyings

Varyings are used to pass values from the vertex shader to the fragment shader. Declare them in both vertex and fragment shader using this simplified syntax:

```wgsl
varying texCoord: vec2f;
```

Internally, those are parsed, and stored in `VertexOutput` structure in the vertex shader, as well as in `FragmentInput` structure in the fragment shader.

#### Vertex Shader

As part of the `VertexOutput` structure these built-in variables are automatically available:

```wgsl
position: @builtin(position)
```

Example:

```wgsl
varying texCoord: vec2f;

@vertex fn vertexMain(input: VertexInput) -> VertexOutput {
var output: VertexOutput;
output.position = uniform.matrix_viewProjection * pos;
output.texCoord = vec2f(0.0, 1.0);
return output;
}
```

#### Fragment Shader

As part of the `FragmentInput` structure these built-in variables are automatically available:

```wgsl
position: @builtin(position) // interpolated fragment position
frontFacing: @builtin(front_facing) // front-facing
sampleIndex: builtin(sample_index) // sample index for MSAA
```

These built-ins are also available in the global scope using these names:

```wgsl
pcPosition
pcFrontFacing
pcSampleIndex
```

Example:

```wgsl
varying texCoord: vec2f;

@fragment
fn fragmentMain(input: FragmentInput) -> FragmentOutput {
var output: FragmentOutput;
output.color = vec4f(1.0);
return output;
}
```

### Fragment Shader Outputs

The fragment shader is responsible for producing one or more color outputs, which are written to the render targets (color attachments) of the framebuffer.

The engine automatically provides a `FragmentOutput` structure, which includes a predefined set of vec4f fields: `color`, `color1`, `color2` and so on, covering all possible color attachments, up to the limit defined by `GraphicsDevice.maxColorAttachments`.

As part of the `FragmentOutput` structure these built-in variables are automatically available:

```wgsl
fragDepth: @builtin(frag_depth)
```

Example:

```wgsl
@fragment fn fragmentMain(input: FragmentInput) -> FragmentOutput {
var output: FragmentOutput;
output.color = vec4f(1.0);
output.color1 = vec4f(0.5);
output.fragDepth = 0.2;
return output;
}
```

:::note

Support for rendering to integer textures (output format other then `vec4f`) is not available yet, and will be added in the future.

:::

[1]: /user-manual/graphics/shaders/
Loading