Parameters
The uniform ABI every shader in the catalogue shares.
The struct
struct Params {
resolution : vec2f, // physical pixels
pointer : vec2f, // normalised, top-left origin
time : f32, // seconds, per renderer
speed : f32,
scale : f32,
intensity : f32,
vibrancy : f32,
grain : f32,
vignette : f32,
warp : f32,
a : f32, // shader-specific
b : f32,
c : f32,
d : f32,
quality : f32, // 1 on the High tier, 0 on Low
e : f32,
f : f32,
g : f32,
colorA : vec4f,
colorB : vec4f,
colorC : vec4f,
h : f32,
i : f32,
j : f32,
k : f32,
l : f32,
m : f32,
n : f32,
}160 bytes, one binding. Every shader uses this same layout, so the runtime can swap the shader behind a canvas without rebuilding a bind group.
Shared controls
| Control | Range | What it does |
|---|---|---|
speed | 0 – 4 | Time multiplier. 0 freezes the current frame |
scale | 0.1 – 6 | Zoom of the underlying field |
intensity | 0 – 2.5 | Brightness before the post chain |
vibrancy | 0 – 2 | Saturation. 0 greyscale, 1 as authored |
grain | 0 – 0.35 | Per-pixel noise; a little hides banding |
vignette | 0 – 1 | Edge darkening |
warp | 0 – 2.5 | Domain warp, or whatever the shader's strongest control is. Not every shader has one |
Those are the widest bounds in the catalogue. Ranges and defaults are set per
shader. Plasma's scale stops at 2 where Julia's goes to 6, because the two
mean different things by "zoom". Read the real numbers off the registry rather
than assuming the envelope applies to the shader in front of you.
Named controls
The fourteen generic slots are named by each shader. Aurora uses exposure,
bloom and stars; Voronoi calls them jitter, edge, depth and drift.
You always write the name, never the slot.
import { shaderRegistry } from "shadertown";
shaderRegistry.aurora.controls.exposure;
// { slot: "a", label: "Exposure", min: 0.2, max: 3, step: 0.01, default: 1.2, hint: … }The registry is plain data, so building your own control panel is a map over
Object.entries(definition.controls), which is exactly what the detail pages
on this site do.
Colours
Three colours, given as hex strings and passed through to WGSL in sRGB with no linearisation. What you pick in the colour field is what the shader receives.
st_palette(t) interpolates colorA → colorB → colorC for t in [0, 1];
st_paletteCyclic(t) wraps, for shaders that cycle.
Quality
quality is the tier the runtime is drawing
at: 1 on High, 0 on Low. A shader does not read it directly. The prelude
turns it into an iteration count:
let steps = st_steps(96); // 96 on High, 48 on Low, never under 3
for (var i = 0; i < steps; i = i + 1) { … }Give st_steps the bound of every loop whose length is price rather than
picture — march steps, noise octaves, blur taps, rays. st_fbm2, st_fbm3,
st_ridge2 and st_srcBlur already do. A loop of three curtains or five
layers is the picture and keeps its literal. Where a shorter march would
stop short of what it has to reach, st_low() says which tier this is, so the
step can grow to cover the ground: Tunnel relaxes its sphere trace and Hills
doubles its stride on Low.
The post chain
Every shader returns through st_post, which applies vibrancy, grain and
vignette in that order. That is why those three behave identically across all
seventy-seven shaders, and why you can tune them once and reuse them.