Uniforms are the primary mechanism for passing dynamic values from your JavaScript/Svelte code into WGSL shaders. This page covers declaration, type inference, memory layout, and runtime updates.
Declaring uniforms
Uniforms are declared in the
uniformsdefineMaterialconst material = defineMaterial({
fragment: `
fn frag(uv: vec2f) -> vec4f {
let t = sin(motiongpuUniforms.uTime * 3.0);
let color = mix(motiongpuUniforms.uColorA, motiongpuUniforms.uColorB, t * 0.5 + 0.5);
return vec4f(color, 1.0);
}
`,
uniforms: {
uTime: 0,
uColorA: [1.0, 0.2, 0.3],
uColorB: { type: 'vec3f', value: [0.2, 0.5, 1.0] }
}
});const material = defineMaterial({
fragment: `
fn frag(uv: vec2f) -> vec4f {
let t = sin(motiongpuUniforms.uTime * 3.0);
let color = mix(motiongpuUniforms.uColorA, motiongpuUniforms.uColorB, t * 0.5 + 0.5);
return vec4f(color, 1.0);
}
`,
uniforms: {
uTime: 0,
uColorA: [1.0, 0.2, 0.3],
uColorB: { type: 'vec3f', value: [0.2, 0.5, 1.0] }
}
});Each uniform becomes a field on the
motiongpuUniformsInput forms
MotionGPU accepts two styles of uniform declaration:
Shorthand (value only)
The type is inferred from the value shape:
| Value | Inferred WGSL type |
|---|---|
number | f32 |
[x, y] | vec2f |
[x, y, z] | vec3f |
[x, y, z, w] | vec4f |
uniforms: {
uTime: 0, // f32
uMouse: [0.5, 0.5], // vec2f
uColor: [1.0, 0.5, 0.0], // vec3f
uTint: [1.0, 1.0, 1.0, 0.8] // vec4f
}uniforms: {
uTime: 0, // f32
uMouse: [0.5, 0.5], // vec2f
uColor: [1.0, 0.5, 0.0], // vec3f
uTint: [1.0, 1.0, 1.0, 0.8] // vec4f
}Explicit (type + value)
For clarity or for
mat4x4ftype | value | WGSL type |
|---|---|---|
'f32' | number | f32 |
'vec2f' | [x, y] | vec2f |
'vec3f' | [x, y, z] | vec3f |
'vec4f' | [x, y, z, w] | vec4f |
'mat4x4f' | 16-element number array | mat4x4f |
uniforms: {
uTime: { type: 'f32', value: 0 },
uModelMatrix: {
type: 'mat4x4f',
value: [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]
}
}uniforms: {
uTime: { type: 'f32', value: 0 },
uModelMatrix: {
type: 'mat4x4f',
value: [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]
}
}The
mat4x4fNaming rules
All uniform identifiers must be WGSL-safe:
[A-Za-z_][A-Za-z0-9_]*Invalid names throw at material definition time. Common conventions:
- ,
uTime,uMouse— prefix withuColorfor user uniformsu - — built-in frame uniforms (do not declare these in
motiongpuFrame.*)defineMaterial
WGSL alignment and packing
Uniforms are packed into a single
Float32Arraystd140| Type | Alignment (bytes) | Size (bytes) | Size (f32 slots) |
|---|---|---|---|
f32 | 4 | 4 | 1 |
vec2f | 8 | 8 | 2 |
vec3f | 16 | 12 | 3 (padded to 4-slot boundary) |
vec4f | 16 | 16 | 4 |
mat4x4f | 16 | 64 | 16 |
Packing behaviour
- Uniform entries are sorted alphabetically by name.
- Each entry is placed at the next offset that satisfies its alignment requirement.
- The total buffer size is rounded up to 16 bytes (minimum 16 bytes).
Example layout for
{ uA: f32, uB: vec3f, uC: vec2f }uAuBuCOffset 0: uA (f32) — 4 bytes
Offset 4: [padding] — 12 bytes (vec3f needs 16-byte alignment)
Offset 16: uB (vec3f) — 12 bytes
Offset 28: [padding] — 4 bytes (vec2f needs 8-byte alignment, next valid is 32)
Offset 32: uC (vec2f) — 8 bytes
Total: 40 bytes → rounded to 48 (16-byte multiple)Offset 0: uA (f32) — 4 bytes
Offset 4: [padding] — 12 bytes (vec3f needs 16-byte alignment)
Offset 16: uB (vec3f) — 12 bytes
Offset 28: [padding] — 4 bytes (vec2f needs 8-byte alignment, next valid is 32)
Offset 32: uC (vec2f) — 8 bytes
Total: 40 bytes → rounded to 48 (16-byte multiple)You generally don’t need to think about this — it’s handled automatically. The material signature is based on uniform names and types (order does not matter because layout is sorted).
Runtime updates with setUniform
setUniformInside a
useFramestate.setUniform(name, value)<script lang="ts">
import { useFrame } from '@motion-core/motion-gpu';
useFrame((state) => {
state.setUniform('uTime', state.time);
state.setUniform('uMouse', [mouseX, mouseY]);
});
</script><script lang="ts">
import { useFrame } from '@motion-core/motion-gpu';
useFrame((state) => {
state.setUniform('uTime', state.time);
state.setUniform('uMouse', [mouseX, mouseY]);
});
</script>Important rules
- Name must exist in the material’s map. Setting an unknown name throws.
uniforms - Value shape must match the declared type. Passing to an
[1, 2]uniform throws.f32 - Updates are merged — material defaults are used for any uniform not set during the frame.
- Dirty tracking — only changed ranges are written to the GPU buffer, minimizing upload overhead.
Built-in frame uniforms
The renderer provides a built-in
motiongpuFramedefineMaterial| Field | Type | Description |
|---|---|---|
motiongpuFrame.time | f32 | Elapsed time in seconds |
motiongpuFrame.delta | f32 | Frame delta in seconds (clamped by maxDelta |
motiongpuFrame.resolution | vec2f | Canvas size in physical pixels |
Validation errors
| Error condition | When it throws |
|---|---|
Invalid identifier (e.g., "2foo""my-var" | defineMaterial |
| Unknown uniform type | defineMaterial |
| Invalid value shape (wrong array length) | defineMaterial |
| Setting unknown uniform name at runtime | state.setUniform |
| Value type mismatch at runtime | state.setUniform |