This page covers texture declarations in defineMaterial, the runtime texture value forms, update modes, upload behavior, mipmap generation, and sampler configuration.
For loading textures from URLs, see Loading Textures.
Declaring textures
Textures are declared in the textures field of defineMaterial:
Each texture with fragmentVisible: true creates two WGSL bindings:
uFoo: texture_2d<f32>— the textureuFooSampler: sampler— the associated sampler
When fragmentVisible: false, the texture slot is excluded from fragment WGSL declarations and group(0) fragment bind-group bindings.
fragmentVisible defaults to true for non-storage textures and float-sampled storage formats (e.g. rgba8unorm, rgba16float). For integer storage formats (*uint, *sint) it defaults to false because the fragment shader binding is typed texture_2d<f32> and cannot sample integer textures. Setting fragmentVisible: true explicitly on an integer storage format throws at material-definition time.
Float32 formats (r32float, rg32float, rgba32float) are sampled as unfilterable textures unless the device supports float32-filterable. When filtering is unavailable, Motion GPU uses a non-filtering sampler and coerces filter: 'linear' to nearest sampling to avoid WebGPU validation errors.
TextureDefinition fields
Runtime texture value forms
You can set texture sources at definition time or at runtime via state.setTexture():
Example: setting a texture from a video
Because the source is an HTMLVideoElement, the update mode is automatically set to perFrame.
Update modes
The update mode controls when the texture is re-uploaded to the GPU:
Resolution precedence
- Runtime override —
TextureData.update(fromstate.setTexture({ source, update: '...' })) - Definition default —
TextureDefinition.update(fromdefineMaterial({ textures: { ... } })) - Automatic fallback —
HTMLVideoElement→perFrame, everything else →once
Storage textures (compute)
When storage: true is enabled, the texture participates in compute bindings (group(2)):
formatmust be a storage-compatible format.- Storage texture targets must have explicit
widthandheight. - Storage textures are compute-managed and are not source-uploaded through normal texture update flow.
- For compute-only storage textures,
fragmentVisibledefaults based on the format (integer formats default tofalse, float formats totrue). SetfragmentVisible: falseexplicitly on float formats when the slot should not consume fragment sampler/texture bindings. Float32 storage formats visible to the fragment stage use non-filtering sampling unlessfloat32-filterableis available.
Fragment feedback textures (PingPongShaderPass)
PingPongShaderPass targets are declared as normal sampled texture slots in defineMaterial({ textures }). The pass owns its internal A/B render textures, then attaches the latest output view to the target before the base scene renders.
Do not set storage: true for a PingPongShaderPass target. Use PingPongComputePass when the texture needs compute storage access.
During the feedback pass, the target texture is excluded from the regular material texture bind group to avoid sampling the texture being written in the same render pass. Read the previous state through the generated motiongpuPrevious / motiongpuPreviousSampler bindings instead.
Upload behavior
The renderer decides how to handle each texture per frame:
The renderer maintains a fallback 1×1 opaque white texture for each binding. If the user source is null, the fallback is used so the shader always has a valid binding. For explicit float formats such as rgba16float, the fallback still uses a safe sampled 8-bit format until a real source or renderer-managed feedback view is attached; this avoids invalid tiny upload layouts while preserving the declared runtime texture contract.
Mipmap generation
When generateMipmaps: true is set:
- The base level (mip 0) is uploaded normally with
copyExternalImageToTexture. - Each subsequent mip level is generated on the GPU by rendering the previous mip level into the next one.
- The generated mip chain stays in the same GPU texture allocation.
Mipmap generation adds GPU work proportional to ~33% of the base texture size. Use it when texture minification is visible (e.g., a texture displayed at varying scales).
Sampler configuration
The sampler created for each texture is configured from the TextureDefinition fields:
If generateMipmaps is enabled, mipmapFilter is also set to the same value as filter.
Naming rules
Texture identifiers follow the same rules as uniforms: [A-Za-z_][A-Za-z0-9_]*. Invalid names throw at material definition time.