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

NameTypeNotes
definitionStdDefinition<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 wgsl body; 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 a vec4f color.
  • registerShader — Make a user-defined shader findable by its name, so preset JSON can reference it as type: '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 crosses value, not on every change.
  • isZero — Skip the filter entirely while prop is exactly 0.
  • listOf — Read a list prop inside a hand-written builder: its live item count and each item's fields.