WGSL Basics
WGSL (WebGPU Shading Language) is the language for programs that run on the GPU.
It looks a bit like Rust and a bit like TypeScript. Every value has a type, and the GPU runs your code for many vertices or pixels at the same time.
WGSL Types
WGSL has a small set of basic types:
| Type | Description | Example |
|---|---|---|
f32 | 32-bit floating-point number | 1.5 |
i32 | 32-bit signed integer | -3 |
u32 | 32-bit unsigned integer | 7u |
bool | true or false | true |
vec2f, vec3f, vec4f | 2, 3 or 4 floats | vec3f(1.0, 0.5, 0.0) |
mat4x4f | 4×4 matrix of floats | Used for 3D transforms |
array<T, N> | Fixed-size array | array<f32, 4> |
1.0 + 2 is fine because 2 is a flexible literal, but adding an f32 variable to an i32 variable is an error. Convert explicitly with f32(value).
Variables: let and var
Use let for values that never change, and var for values you will change:
let radius = 0.5;
var total = 0.0;
total = total + radius;
The type is usually worked out from the value. You can also write it:
let count: u32 = 4u;
var color: vec3f = vec3f(1.0, 0.0, 0.0);
Vectors
Vectors are everywhere in shaders: positions, colours and directions. You can read their parts with .x .y .z .w or .r .g .b .a, and combine them freely. This is called swizzling.
let color = vec4f(1.0, 0.5, 0.2, 1.0);
let red = color.r;
let rgb = color.rgb;
let flipped = color.bgr;
let position = vec4f(rgb, 1.0);
Maths works on every component at once: vec2f(1.0, 2.0) * 2.0 is vec2f(2.0, 4.0).
Structs
A struct groups values together. Structs are the usual way to send several values from the vertex shader to the fragment shader.
Fields marked @location(n) are passed along and smoothly blended across the triangle. This blending is called interpolation.
Example
Give each corner its own colour:
struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) color: vec3f,
}
@vertex
fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
let corners = array(vec2f(0.0, 0.7), vec2f(-0.7, -0.6), vec2f(0.7, -0.6));
let colors = array(vec3f(1.0, 0.3, 0.3), vec3f(0.3, 1.0, 0.4), vec3f(0.3, 0.5, 1.0));
var output: VertexOutput;
output.position = vec4f(corners[vertexIndex], 0.0, 1.0);
output.color = colors[vertexIndex];
return output;
}
@fragment
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
return vec4f(input.color, 1.0);
}
Edit & Run »
Built-in Values
Values marked @builtin(...) come from, or go to, the GPU itself:
| Built-in | Shader | Description |
|---|---|---|
vertex_index | Vertex (input) | Which vertex is being processed: 0, 1, 2, … |
instance_index | Vertex (input) | Which copy is being drawn (see Instancing) |
position | Vertex (output) | The clip-space position of the vertex |
position | Fragment (input) | The pixel coordinate being coloured |
global_invocation_id | Compute (input) | Which thread is running (see Compute shaders) |
Built-in Functions
WGSL comes with many maths functions. These are the ones you will use most:
| Function | Description |
|---|---|
mix(a, b, t) | Blends from a to b as t goes from 0 to 1 |
clamp(x, low, high) | Keeps x between low and high |
smoothstep(edge0, edge1, x) | A smooth 0-to-1 step between two edges |
length(v) | The length of a vector |
normalize(v) | The same direction with length 1 |
dot(a, b) | How much two directions agree. Used for lighting. |
sin(x), cos(x) | Trigonometry, in radians |
max(a, b), min(a, b) | The larger or smaller value |
Example
Use mix() to make a sky gradient:
@fragment
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
let bottomColor = vec3f(0.05, 0.1, 0.3);
let topColor = vec3f(0.95, 0.55, 0.3);
let color = mix(bottomColor, topColor, input.uv.y);
return vec4f(color, 1.0);
}
Edit & Run »
uv coordinate from 0 to 1 across it. This "full-screen triangle" trick is used in many effects, including post-processing.
Functions
Write your own functions with fn. Parameters and the return value must have types:
fn circle(point: vec2f, center: vec2f, radius: f32) -> f32 {
let distanceToCenter = length(point - center);
return 1.0 - smoothstep(radius - 0.01, radius + 0.01, distanceToCenter);
}
Functions marked @vertex, @fragment or @compute are entry points: the places JavaScript can start the shader from.
Loops and Conditions
if, for and while work like in JavaScript, but conditions must be bool and braces are required.
Example
Draw five circles in a loop:
var color = vec3f(0.05, 0.06, 0.07);
for (var index = 0; index < 5; index++) {
let ordinal = f32(index);
let center = vec2f(aspect * 0.5 + (ordinal - 2.0) * 0.3, 0.5 + 0.2 * sin(ordinal * 1.3));
let shade = vec3f(0.3 + ordinal * 0.15, 0.9 - ordinal * 0.12, 0.6);
color = mix(color, shade, circle(point, center, 0.12));
}
Edit & Run »
Exercises
1. Which keyword declares a value that can change?
Show answer
var. Values declared with let cannot change.
2. What does vec4f(0.2, 0.4, 0.6, 1.0).zyx return?
Show answer
vec3f(0.6, 0.4, 0.2)
3. Fix this line: let half = count / 2.0; where count is an i32.
Show answer
let half = f32(count) / 2.0;4. In the gradient example, swap the two colours so the sky is darker at the top.
Show answer
let color = mix(topColor, bottomColor, input.uv.y);Summary
- WGSL is strictly typed:
f32,i32,u32,bool, vectors and matrices. letis constant,varcan change.- Structs with
@locationfields carry data from the vertex shader to the fragment shader. - Built-in functions like
mix,smoothstepandlengthdo most of the work.
Official documentation
Go deeper with the specifications and reference documentation:
- w3.org
WGSL specification: types
Scalars, vectors, matrices, arrays and structures, defined precisely.
- w3.org
WGSL specification: functions
Function declarations, parameters, return values and entry points.
- google.github.io
Tour of WGSL
An interactive tour of the language, one feature at a time.
- developer.mozilla.org
MDN: GPUDevice.createShaderModule()
Compiling WGSL source into a shader module and reading its errors.