defineShader & wgsl

A shader is a plain object: a name, the props a user can set, and one field that says what it draws. Hand it to defineShader and it becomes a component you can mount, nest, blend, mask and animate like any shader in the library.

import {defineShader, transformColor, transformPosition, wgsl} from 'shaders/std'

export const Halo = defineShader({
  name: 'Halo',                      // what the editor and preset JSON call it
  props: {                           // the controls a user can set
    color: {default: '#ffd166', transform: transformColor},
    center: {default: {x: 0.5, y: 0.5}, transform: transformPosition},
    radius: {default: 0.6},
  },
  paint: wgsl`                       // what it draws: a color for every pixel
    let d = length((uv - center) * vec2f(aspect, 1.0)) / radius;
    return vec4f(color.rgb, 1.0 - smoothstep(0.8, 1.0, d));
  `,
})

One field says what kind of shader it is. Give exactly one.

FieldYou getWhat is inside it
paint:a generator that paints from coordinates (gradients, noise, light)nothing: it stands on its own
effect:a filter over the layer nested inside it (tints, blurs, ripples)the child layer, read as child or sampled as childTexture
map:a distortion that moves the pixels of the layer inside itthe child layer
shape:a 2D shape with fill and strokenothing

Two ways to write what it draws. Compose it from std words (the rest of this reference) and the engine compiles the composition to WGSL. A radial gradient between two color props is one line:

paint: rampOver(dist.radial({center: p('center'), radius: p('radius'), aspect: 1, skew: 0}), pair(p('inner'), p('outer'), p('colorSpace')))

Or write the math yourself in a wgsl body: one function that returns a vec4f color. Your props, uv, time, aspect, viewport, pointer and the child are bound by name, so there is no setup to write. A filter that inverts whatever is inside it:

effect: wgsl`
  return vec4f(1.0 - child.rgb, child.a);
`

Props are the controls. Each has a default, usually a transform that says what kind of value it is (transformColor, transformPosition, …), and optional ui metadata that tells the editor how to show it. Blend modes, opacity, masks, transforms, dynamic props and code export come with every shader; you never write them.

Use it like any other shader. Mount it with <CustomShader src={Halo}> in React, Vue, Svelte or Solid. Call registerShader(Halo) when preset JSON should be able to name it as type: 'Halo'.

import {Shader, CustomShader, Blur} from 'shaders/react' // or shaders/vue, shaders/svelte, shaders/solid
import {Halo} from './halo'

<Shader>
  <Blur intensity={8}>
    <CustomShader src={Halo} radius={0.8} />
  </Blur>
</Shader>
import {defineShader, wgsl, registerShader, …} from 'shaders/std'

Reach for it when

When you needUse
a shader of your own, from a plain objectdefineShader
per-pixel math you want to write by handwgsl in paint: or effect:
a generator that paints from coordinatesdefineShader with paint:
a filter over the layer nested insidedefineShader with effect: holding a color word, tintToward, or a wgsl body that reads child
a filter that reads neighbouring pixels (blur, ripple, mosaic)a wgsl body that samples childTexture, or gather
a distortion from one coordinate functiondefineShader with map:
naming your shader in preset JSON for createShaderregisterShader, or the components option
listing the custom shaders an app has registeredgetRegisteredShaders, onShaderRegistered

Example

import {defineShader, wgsl, transformColor, transformPosition} from 'shaders/std'

// A generator: concentric color bands radiating from a draggable point, on its own clock.
export const Halo = defineShader({
  name: 'Halo',
  description: 'Concentric color bands radiating from a point.',
  animatedTime: {speed: 'speed'},
  props: {
    inner: {default: '#ffd166', transform: transformColor, ui: {type: 'color', label: 'Inner'}},
    outer: {default: '#0b132b', transform: transformColor, ui: {type: 'color', label: 'Outer'}},
    center: {default: {x: 0.5, y: 0.5}, transform: transformPosition, ui: {type: 'position', label: 'Center'}},
    radius: {default: 0.6, ui: {type: 'range', min: 0.1, max: 1.5, step: 0.01, label: 'Radius'}},
    bands: {default: 4, ui: {type: 'range', min: 1, max: 16, step: 1, label: 'Bands'}},
    speed: {default: 1, ui: {type: 'range', min: 0, max: 4, step: 0.1, label: 'Speed'}},
  },
  paint: wgsl`
    let q = (uv - center) * vec2f(aspect, 1.0);
    let d = length(q) / radius;
    let wave = 0.5 + 0.5 * cos(d * bands * 6.2831853 - time * 2.0);
    let fade = 1.0 - smoothstep(0.7, 1.0, d);
    return vec4f(mix(outer.rgb, inner.rgb, wave * fade), 1.0);
  `,
})

Types

TypeWhat it is
DisplaceByEffectRefA filter that moves the layer inside by a simulation's vector field.
GatherEffectA filter that reads the layer inside anywhere, not just at this pixel: blurs, halftones, displacements.
IdentityRuleWhen a filter does nothing and can be skipped: built with isZero, isValue, allOf or identityWhenever.
PointwiseEffectA filter that edits the layer inside one pixel at a time: what the color words (saturate, hueRotate, …) and pointwise({build}) return.
RecompileRuleWhen a prop change rebuilds the shader: crosses(value) at a threshold, or recompileWhen(fn) by your own test.
StdCustomDefinitionA raw builder: gpu: holds a fragment function (and optionally a uvRemap) for effects the words cannot say yet.
StdDefinitionWhat defineShader accepts: one of the definition shapes, told apart by which field carries the per-pixel work.
StdDefinitionBaseThe fields every definition shares: name, props, optional description and category, the animatedTime clock, and engine flags.
StdGatherFilterDefinitionA filter that reads the layer inside anywhere: effect: holds gather({…}), displaceBy(…) or a wgsl body that samples childTexture.
StdGeneratorDefinitionA generator: paint: produces a color from coordinates and needs nothing nested inside.
StdPointwiseFilterDefinitionA filter that edits the layer inside one pixel at a time: effect: holds a color word, tintToward, pointwise({…}) or a wgsl body that reads child.
StdPropConfigOne prop of a definition: default, optional transform, ui, description, compileTime, and a recompile: rule.
StdPropsThe props object of a definition: one StdPropConfig per prop name.
StdShapeDefinitionA 2D shape: shape: gives the distance function and the shape's own props; fill, stroke, softness and bounds come for free.
StdWarpDefinitionA distortion: map: is one function from the pixel's coordinate to where in the layer inside to read.
StdWgslFilterDefinitionA filter whose effect: is a wgsl body with no species declared: gather when the body samples childTexture, pointwise otherwise.
WgslHostWhere a body runs: a generator's paint:, or a pointwise or gather filter's effect:.
WgslInputOne entry of inputs: the value to bind under a name — a prop p('x'), a context token ctx.time, a Scalar, or a number — optionally with its WGSL type spelled out as {value, type}.
WgslSpecThe long form of a body: wgsl({body, inputs, alpha, name}).
WgslTypeThe WGSL types a bound name can have inside a body.

Words