Shaders & Textures

Uniforms

Passing dynamic values from JavaScript to WGSL shaders declaration, types, and updates.


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

uniforms
field of
defineMaterial
:

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] }
  }
});
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

motiongpuUniforms
struct. The library generates the binding struct and uniform buffer automatically.

Input 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

mat4x4f
, use the explicit form:

type
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

mat4x4f
type must use explicit form — it cannot be inferred from an array length of 16.

Naming 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
    ,
    uColor
    — prefix with
    u
    for user uniforms
  • motiongpuFrame.*
    — built-in frame uniforms (do not declare these in
    defineMaterial
    )

WGSL alignment and packing

Uniforms are packed into a single

Float32Array
buffer following WGSL
std140
-like alignment rules:

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

  1. Uniform entries are sorted alphabetically by name.
  2. Each entry is placed at the next offset that satisfies its alignment requirement.
  3. The total buffer size is rounded up to 16 bytes (minimum 16 bytes).

Example layout for

{ uA: f32, uB: vec3f, uC: vec2f }
(sorted:
uA
,
uB
,
uC
):

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)
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

Inside a

useFrame
callback, use
state.setUniform(name, value)
to update a uniform’s value for the current frame:

<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
    uniforms
    map. Setting an unknown name throws.
  • Value shape must match the declared type. Passing
    [1, 2]
    to an
    f32
    uniform throws.
  • 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

motiongpuFrame
uniform buffer. You do not declare it in
defineMaterial
:

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
call
Unknown uniform type
defineMaterial
call
Invalid value shape (wrong array length)
defineMaterial
call
Setting unknown uniform name at runtime
state.setUniform
call
Value type mismatch at runtime
state.setUniform
call