The three layers
Purpose
Every shader does three things in order — transform the coordinate, compute a signal from it, map the signal to color — and every effect you'll ever direct lives in one or more of those layers.
Key insight
Think of the input coordinate (usually a pixel's 2D position from (0,0) to (1,1)) as the raw address. The coordinate layer warps or zooms that address. The signal layer computes a number from the address — a distance, a wave, a noise value. The color layer turns that number into a hue.
Effects stack by modifying any of these three stages. "Zoom the pattern" is coordinate work. "Sharpen the edges" is signal work. "Shift the palette warmer" is color work.
Each slider above controls exactly one layer. Move them one at a time to see what each layer does.
Break it
1. Crank zoom to max (10). The pattern becomes so tight it reads as grey noise — the rings are fine but the screen can't resolve them. Teaches: coordinate scale directly controls the spatial frequency you perceive.
2. Pull softness to minimum. Hard posterized bands appear. Teaches: smooth transitions aren't a color choice — they're a signal choice. Step vs. smoothstep is where softness lives.
3. Pull warmth to either extreme. The same shapes remain — only the palette shifts. Teaches: upstream signal is unchanged by color choices; they're separable.
Direct Claude
uniform float u_zoom; // coordinate layer
uniform float u_softness; // signal layer
uniform float u_warmth; // color layer
void main() {
// Center uv at (0,0), aspect-corrected
vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution) / min(u_resolution.x, u_resolution.y);
// COORDINATE LAYER — scale the address before anything else
vec2 p = uv * u_zoom;
// SIGNAL LAYER — compute a number from the transformed coordinate
float d = length(p);
float rings = fract(d * 1.5 - u_time * 0.25);
float signal = smoothstep(0.5 - u_softness, 0.5 + u_softness, rings);
// COLOR LAYER — map the signal to a hue, tinted by warmth
vec3 cool_dark = vec3(0.12, 0.16, 0.26);
vec3 cool_light = vec3(0.45, 0.65, 0.90);
vec3 warm_dark = vec3(0.22, 0.10, 0.12);
vec3 warm_light = vec3(0.96, 0.56, 0.34);
vec3 dark = mix(cool_dark, warm_dark, u_warmth);
vec3 light = mix(cool_light, warm_light, u_warmth);
vec3 col = mix(dark, light, signal);
gl_FragColor = vec4(col, 1.0);
}