Animated procedural patterns with sine, time, and smoothstep
Learning objectives
- learner can drive looping motion with sine and time using bias/gain to map into color range
- learner can turn time oscillators into spatial patterns via UV coordinates
- learner can use smoothstep to control thresholds and anti-alias procedural edges
- learner can layer octaves of sine to add fine detail to a pattern
Capstone — one whole task that evidences the objectives
Produce a looping animated GLSL pattern that combines UV-driven spatial variation, sine-and-time motion, layered octaves, and smoothstep-thresholded edges into a seamless tile.
Prerequisite modules
This module builds the bread-and-butter move of live-coded visuals: a fragment shader that breathes. In a VJ or algorave rig, an animated procedural tile is what fills the screen between bolder moves — it must loop seamlessly (no pops when the projector wraps it), pulse in time, and hold up at any resolution. Everything here runs on a stock Shadertoy-style setup: one fragment shader, iTime, UV coordinates, nothing else.
The arc starts fully supported. First, get a single color channel pulsing: feed time into sine, then apply the bias-and-gain mapping so the −1..+1 oscillation lands cleanly in 0–1 instead of clamping black for half the cycle. Next, swap the constant frequency for a UV coordinate — the moment a time oscillator becomes a spatial pattern is the conceptual pivot of the whole module. From there, two refinement passes: use smoothstep with adjustable limits to carve the smooth sine field into deliberate light/dark regions with anti-aliased edges instead of jagged step cuts, and add a second sine octave at double frequency and half amplitude to break the pattern’s machine-like regularity. The capstone then removes the scaffolding: you design your own tile that must exhibit all four techniques at once and loop without a visible seam.
The required atoms are exactly the capstone’s load-bearing skills — drop any one and the tile fails visibly (black flicker, flat field, jagged edges, or sterile regularity). The supporting atoms widen the palette rather than gate it: aliased high-frequency sine for pseudo-random seeding, the step→smoothstep replacement at SDF boundaries, and 1/x neon falloff are ready-made detours once the core tile works. Note the deliberate split between the two smoothstep atoms: the required smoothstep-anti-aliasing is the general concept the capstone gates — smoothstep as a two-threshold transition that anti-aliases procedural edges — while the supporting smoothstep-antialiasing is a narrower procedure (swapping step() for smoothstep() at an SDF boundary to get fringe and glow effects), useful only if your tile detours through SDF shapes. The two are near-duplicates at the corpus level and are candidates for consolidation into one canonical atom. Bias/gain mapping and smoothstep threshold tuning recur in nearly every edit, so drill them inside the whole task until they are reflexive.
Walkthrough
The bread-and-butter of live visuals: a shader that breathes — an animated procedural tile that loops seamlessly and pulses in time. Paste each into The Book of Shaders editor (u_time is the running clock — Shadertoy calls the same thing iTime). Each is a complete shader.
1 — a channel pulsing in time. Feed u_time into sin, but a raw sine is −1..1 — and negative values clamp to black for half the cycle. The bias-and-gain map *0.5 + 0.5 lands it cleanly in 0..1 ([[sine-animation-time]], [[glsl-sine-bias-gain]]).
precision mediump float;
uniform float u_time;
void main() {
float v = sin(u_time) * 0.5 + 0.5; // 0..1, no black half-cycle
gl_FragColor = vec4(vec3(v), 1.0);
}
2 — time becomes space (the pivot). Swap the constant frequency for a UV coordinate: sin(uv.x * 20.0 + u_time) makes moving stripes — a spatial pattern that also animates. This is the conceptual turn of the whole module ([[glsl-uv-coordinates-spatial-variation]]).
precision mediump float;
uniform vec2 u_resolution;
uniform float u_time;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
float v = sin(uv.x * 20.0 + u_time) * 0.5 + 0.5;
gl_FragColor = vec4(vec3(v), 1.0);
}
3 — carve clean edges with smoothstep. A raw sine field is a mushy gradient; smoothstep(a, b, v) turns it into deliberate light/dark bands with anti-aliased edges (no jaggies) — two thresholds define the fringe width ([[smoothstep-anti-aliasing]], [[smoothstep-threshold]]).
precision mediump float;
uniform vec2 u_resolution;
uniform float u_time;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
float wave = sin(uv.x * 20.0 + u_time) * 0.5 + 0.5;
float bands = smoothstep(0.45, 0.55, wave); // crisp but anti-aliased
gl_FragColor = vec4(vec3(bands), 1.0);
}
4 — a 2D field. Combine both axes — sin(x) + sin(y) — for an interference weave instead of flat stripes, each axis drifting at its own rate.
precision mediump float;
uniform vec2 u_resolution;
uniform float u_time;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
float v = sin(uv.x * 18.0 + u_time) + sin(uv.y * 18.0 - u_time * 0.7);
v = v * 0.25 + 0.5; // bias/gain the sum into 0..1
gl_FragColor = vec4(vec3(v), 1.0);
}
5 — octaves break the regularity. A single sine is machine-perfect and sterile. Add a second octave at double frequency and half amplitude and the pattern gains organic detail — the same trick as fBM ([[layered-sine-octaves]] if present).
precision mediump float;
uniform vec2 u_resolution;
uniform float u_time;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
float v = sin(uv.x * 10.0 + u_time);
v += 0.5 * sin(uv.x * 20.0 + u_time * 1.5); // 2× freq, 0.5× amp
v = v * 0.33 + 0.5;
gl_FragColor = vec4(vec3(v), 1.0);
}
6 — a seamless animated tile (the capstone). All four techniques: UV spatial variation, sine-and-time motion, layered octaves, and smoothstep edges. Multiplying the phase by 6.28318 (2π) keeps it seamless — the pattern wraps without a pop when the projector tiles it:
precision mediump float;
uniform vec2 u_resolution;
uniform float u_time;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
float TAU = 6.28318;
float v = sin(uv.x * TAU + u_time); // whole-cycle → seamless wrap
v += 0.5 * sin(uv.y * TAU * 2.0 - u_time * 1.3);
v = v * 0.33 + 0.5;
float edge = smoothstep(0.4, 0.6, v); // carved, anti-aliased
vec3 col = mix(vec3(0.05, 0.1, 0.2), vec3(0.4, 0.9, 1.0), edge);
gl_FragColor = vec4(col, 1.0);
}
What good looks like. A tile that loops with no visible pop (that’s the 2π/whole-cycle phase), pulses in musical time (sine of u_time), reads as organic rather than a barcode (the octaves), and has clean edges without jaggies (smoothstep). If it flickers to black, you skipped the bias/gain and half the sine is clamping; if edges are jagged, you used step instead of smoothstep; if it’s sterile, add another octave. This is the between-the-bolder-moves texture that fills a set. (Skill map: live-visualist Domain A3/C — procedural, time-driven shader patterns.)
Now make it yours. Change the octave ratio (try 3.0/0.33). Drive a colour channel with a different phase for chromatic drift. Warp the UV with uv += 0.1 * sin(uv.yx * TAU + u_time) before the pattern. Speed the loop by scaling u_time.
Runnable examples
Generated from the context/ instrument corpus by concept (redistributable idioms only). Do not edit — regenerate with gen-module-examples.mjs.
oscillation
let y = height/2 + sin(frameCount * 0.05) * 100
p5live-0004 · CC0-1.0
float rings = abs(sin(length(uv)*20.0 - u_time*2.0));
glsl-0039 · public-domain
Atoms in this module
Required — these gate the capstone
Supporting — enrichment, not gating
Part of curricula
- Generative & AI AV Artist — real-time machine-driven performance — Orient the machine collaborator & ship a first ML AV artefact optional
- Live Visualist — zero to performing live-coded & generative visuals — Reactive & procedural — make it listen, and go to the GPU required
- Shader Artist — real-time GPU craft to a demoscene-grade visual — The fragment shader as a per-pixel instrument required
Unlocks — modules that require this one