defineShader
Define a shader component from a plain object: its props, and one field that says what it draws.
defineShader<T extends ComponentProps>(definition: StdDefinition<T>): GpuShaderDefinition<T>
Import defineShader from shaders/std.
The field carrying the per-pixel work decides what kind of component you get. paint: makes a generator: it paints from coordinates and needs nothing inside it. effect: makes a filter over the layer inside it (the child). A wgsl body that only reads child edits one pixel at a time; one that samples childTexture reads neighbours through its own render pass. map: makes a distortion from one coordinate function, shape: a 2D shape with fill and stroke, gpu: a raw builder. Give exactly one. role and species are optional and may only restate what the field already says.
props are the component's attributes. Each has a default, an optional transform (transformColor, transformPosition, …), optional ui metadata for editors, and compileTime: true when a change should rebuild the shader (or recompile: crosses(0) to rebuild only at a threshold). A prop name must be a plain identifier and must not be one of the layer props every component already has — blendMode, opacity, visible, id, maskSource, maskType, renderOrder, transform, boundingBox, flow, absolute, children, ref, key, src — nor a name the renderer manages itself (_animTime, _opacity, or anything starting _pad, _bbox_, _map_). Those throw when the definition is built. animatedTime: {speed: 'speed'} gives the layer its own clock, scaled by that prop (0 pauses it); time in a wgsl body and animatedTime(params) in a builder read it.
The returned definition is what you hand to <CustomShader src={Halo}> in React, Vue, Svelte or Solid, to registerShader, or to createShader(canvas, preset, {components: [Halo]}) so preset JSON can name it by type.
Parameters
| Name | Type | Notes |
|---|---|---|
definition | StdDefinition<T> |
Examples
import {defineShader, wgsl, transformColor, transformPosition} from 'shaders/std'
export const Halo = defineShader({
name: 'Halo',
animatedTime: {speed: 'speed'},
props: {
inner: {default: '#ffd166', transform: transformColor},
outer: {default: '#0b132b', transform: transformColor},
center: {default: {x: 0.5, y: 0.5}, transform: transformPosition},
radius: {default: 0.6},
bands: {default: 4},
speed: {default: 1},
},
paint: wgsl`
let d = length((uv - center) * vec2f(aspect, 1.0)) / radius;
let wave = 0.5 + 0.5 * cos(d * bands * 6.2831853 - time * 2.0);
return vec4f(mix(outer.rgb, inner.rgb, wave * (1.0 - smoothstep(0.7, 1.0, d))), 1.0);
`,
})
// A filter composed from std words, skipped entirely while `intensity` is 0.
export const Vignette = defineShader({
name: 'Vignette',
props: {
color: {default: '#000000', transform: transformColor},
center: {default: {x: 0.5, y: 0.5}, transform: transformPosition},
radius: {default: 0.5},
falloff: {default: 0.5},
intensity: {default: 1, recompile: crosses(0)},
},
effect: tintToward(p('color'), {amount: radialMask({center: p('center'), radius: p('radius'), falloff: p('falloff')}).times(p('intensity'))}),
identityWhen: isZero('intensity'),
})
Tip. A prop the engine never sends to the GPU (a URL string, a shape object, a list) cannot be read in a
wgslbody; reference it and WGSL reports the name as undefined.
See also
wgsl— Write the per-pixel math by hand, as one WGSL function body that returns avec4fcolor.registerShader— Make a user-defined shader findable by itsname, so preset JSON can reference it astype: 'Halo'.p— Reference one of the definition's props by name, wherever a word asks for a value.crosses— Rebuild the shader only when the prop crossesvalue, not on every change.isZero— Skip the filter entirely whilepropis exactly 0.listOf— Read a list prop inside a hand-written builder: its live item count and each item's fields.