An overview of the ways an image can be produced from equations, rules, and simulations. Each method is described at a high level, with the governing mathematics, what it tends to look like, and where it is used in this collection.
Every equation is given twice: as typeset mathematics and as the plain code you would actually write. Notation: \(p = (x, y)\) is a point on the canvas, \(t\) a parameter or time, \(k\) an integer index, \(N\) a count. Images are produced either by plotting (evaluate a rule, place marks) or by evaluating a field (compute a value at every pixel).
1. Parametric and trigonometric shape families
Idea. Draw thousands of simple primitives whose positions, sizes, and colours are closed-form trigonometric functions of an index. No single primitive matters; the eye reads the envelope of the swarm.
Primitive swarms. For \(k = 1 \ldots N\), draw a circle with
for (let k = 1; k <= N; k++) {
const cx = Math.sin((a * Math.PI * k) / N);
const cy = Math.pow(Math.cos((b * Math.PI * k) / N), p);
const r = rho * Math.pow(Math.sin((d * Math.PI * k) / N), 2);
circle(cx, cy, r);
}
Integer frequencies \(a, b, d\) make the family close on itself, producing symmetric moiré. Odd powers sharpen curves toward the axes; even powers keep a quantity non-negative (useful for radii).
Lissajous and harmonograph.
x = A * Math.sin(a * t + delta) * Math.exp(-lambda * t);
y = B * Math.sin(b * t) * Math.exp(-lambda * t);
The ratio \(a : b\) sets the knot pattern. The decay gives the pendulum-drawn look of a harmonograph.
Rose curves and polar art.
r = Math.cos(k * theta);
x = r * Math.cos(theta);
y = r * Math.sin(theta);
Epicycles (Fourier drawing). Any closed curve \(z(t)\) is a sum of rotating vectors:
// c[n] = { re, im } from a DFT of the sampled outline
let x = 0, y = 0;
for (const [n, c] of coefficients) {
const phase = 2 * Math.PI * n * t;
x += c.re * Math.cos(phase) - c.im * Math.sin(phase);
y += c.re * Math.sin(phase) + c.im * Math.cos(phase);
}
Sample an outline, take the discrete Fourier transform, and animate the chain of circles.
Hypotrochoids (spirograph).
const q = (R - r) / r;
x = (R - r) * Math.cos(t) + d * Math.cos(q * t);
y = (R - r) * Math.sin(t) - d * Math.sin(q * t);
Phyllotaxis. The arrangement of seeds and petals. Point \(n\) sits at
const GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5)); // 137.508°
for (let n = 0; n < count; n++) {
const theta = n * GOLDEN_ANGLE;
const r = c * Math.sqrt(n);
petal(cx + r * Math.cos(theta), cy + r * Math.sin(theta));
}
The golden angle \(360^\circ (1 - 1/\varphi)\) is the irrational rotation that packs most evenly, and the \(\sqrt{n}\) radius keeps density constant. Fibonacci spirals appear on their own.
Looks like. Op-art, string art, flower heads, sunflower centres, animals traced by the envelope of many segments.
2. Integer sequences and number theory
Idea. Interpret a sequence of integers as drawing instructions and let its structure become visible.
Recamán sequence.
let a = 0;
const seen = new Set([0]);
for (let n = 1; n < N; n++) {
const back = a - n;
a = back > 0 && !seen.has(back) ? back : a + n;
seen.add(a);
semicircle(prev, a, n % 2 ? "above" : "below");
}
Draw each jump as a semicircle, alternating above and below the axis.
Ulam spiral. Write the integers along a square spiral and mark the primes. Diagonal streaks appear because many diagonals are quadratic polynomials \(4n^2 + bn + c\) that are unusually prime-rich.
Modular multiplication circles. Place \(N\) points on a circle and connect each to its multiple:
for (let i = 0; i < N; i++) {
const j = (m * i) % N;
line(pointOnCircle(i / N), pointOnCircle(j / N));
}
Sweeping \(m\) continuously traces a cardioid at \(m = 2\), a nephroid at \(m = 3\), and higher epicycloids beyond.
Collatz orbits.
while (n !== 1) {
turn(n % 2 === 0 ? -angle : +angle);
forward(step);
n = n % 2 === 0 ? n / 2 : 3 * n + 1;
}
Draw each orbit as a path turning one way on odd steps and the other on even ones; overlay thousands of starting values at low opacity.
Binary-word fractals. Take a self-similar sequence (Fibonacci word, Thue–Morse) and turn it into turtle moves: turn only on certain symbols. The self-similarity of the sequence becomes the self-similarity of the curve.
Looks like. Nested arcs, coral textures, cardioids, prime constellations.
3. Fractals and iterated systems
Idea. Repeat a simple rule; complexity comes from iteration rather than from the rule.
3.1 Escape-time fractals
Iterate a complex map and colour each pixel by how quickly its orbit escapes.
let zr = 0, zi = 0, n = 0; // Mandelbrot: z0 = 0, c = pixel
while (zr * zr + zi * zi < 4 && n < maxIter) {
const t = zr * zr - zi * zi + cr;
zi = 2 * zr * zi + ci;
zr = t;
n++;
}
const smooth = n + 1 - Math.log2(Math.log(Math.hypot(zr, zi)));
Mandelbrot: \(z_0 = 0\), vary \(c\) over the plane. Julia: fix \(c\), vary \(z_0\).
For fine boundary detail, the exterior distance estimate is better than the iteration count. Track the derivative alongside the orbit:
// alongside the loop above, starting with dz = 1 (Julia) or 0 (Mandelbrot)
const ndr = 2 * (zr * dr - zi * di);
const ndi = 2 * (zr * di + zi * dr);
dr = ndr; di = ndi;
// after escape:
const mag = Math.hypot(zr, zi);
const dist = (mag * Math.log(mag)) / Math.hypot(dr, di);
This gives filaments of uniform width at any zoom. Choosing \(c\) at a Misiurewicz point (where the orbit of 0 is pre-periodic) gives dendritic, frost-like Julia sets.
3.2 Newton fractals
for (let n = 0; n < maxIter; n++) {
z = sub(z, div(P(z), dP(z)));
const k = nearestRoot(z);
if (k >= 0) { colour = ROOT_COLOURS[k]; shade = n; break; }
}
Colour each pixel by the root it converges to, shaded by how many steps it took. Basin boundaries are fractal.
3.3 Iterated function systems
Choose one of \(m\) affine maps at random with probability \(p_i\) and apply it; plot every visited point (the chaos game).
let x = 0, y = 0;
for (let i = 0; i < points; i++) {
const m = pickMap(maps, rng.next()); // weighted by m.p
const nx = m.a * x + m.b * y + m.e;
const ny = m.c * x + m.d * y + m.f;
x = nx; y = ny;
density[toPixel(x, y)] += 1;
}
The attractor is the unique set \(A\) that is the union of its own images. The Barnsley fern:
| map | a | b | c | d | e | f | p |
|---|---|---|---|---|---|---|---|
| stem | 0 | 0 | 0 | 0.16 | 0 | 0 | 0.01 |
| main | 0.85 | 0.04 | −0.04 | 0.85 | 0 | 1.60 | 0.85 |
| left | 0.20 | −0.26 | 0.23 | 0.22 | 0 | 1.60 | 0.07 |
| right | −0.15 | 0.28 | 0.26 | 0.24 | 0 | 0.44 | 0.07 |
Small changes to the coefficients give other species (Culcita, Cyclosorus). Because points land unevenly, the visit count must be tone-mapped:
const d99 = percentile(density, 0.99);
tone = Math.log(1 + d) / Math.log(1 + d99); // or: alpha = 1 - Math.exp(-d / d0)
3.4 L-systems
String rewriting interpreted as turtle graphics.
axiom: F
rule: F → F[+F]F[−F]F
angle: 25°, 4–6 iterations
F draw forward + turn left [ push turtle state
− turn right ] pop turtle state
Brackets push and pop turtle state; stochastic rules choose among alternatives with fixed probabilities. Add taper (width × 0.7 per level) and tropism (a small constant added to every angle) for gravity and light.
3.5 Recursive branching
The same idea without the string: a branch of length \(\ell\) and width \(w\) spawns \(k \in \{2, 3\}\) children.
function branch(x, y, len, angle, width, depth) {
const x1 = x + Math.cos(angle) * len, y1 = y + Math.sin(angle) * len;
stroke(x, y, x1, y1, width);
if (depth === 0) return;
const k = rng.chance(0.35) ? 3 : 2;
const spread = rng.range(0.4, 0.75);
for (let i = 0; i < k; i++) {
const a = angle + (i - (k - 1) / 2) * spread + rng.gauss() * 0.12;
branch(x1, y1, len * rng.range(0.6, 0.76), a, width * 0.62, depth - 1);
}
}
Different constants give different species: long thin segments and many levels for a winter tree; short segments, wide spread and heavy width retention (\(0.84\, w\)) for a Joshua tree, with a rosette of 12–18 radiating spikes at each tip.
3.6 Strange attractors
Iterate a nonlinear map millions of times and accumulate a histogram of visited points.
// Clifford
const nx = Math.sin(a * y) + c * Math.cos(a * x);
const ny = Math.sin(b * x) + d * Math.cos(b * y);
// De Jong
const nx = Math.sin(a * y) - Math.cos(b * x);
const ny = Math.sin(c * x) - Math.cos(d * y);
Continuous systems are integrated instead:
// σ = 10, ρ = 28, β = 8/3, small dt (or RK4)
x += dt * sigma * (y - x);
y += dt * (x * (rho - z) - y);
z += dt * (x * y - beta * z);
Density plotted with a log tone map is what gives these their silk-like look; colour by local speed \(|p_{n+1} - p_n|\).
Looks like. Ferns, frost, coral, trees, nebulae, lace.
4. Noise and stochastic fields
Idea. A smooth pseudo-random function of position is the raw material for almost every organic texture.
Gradient noise. Assign a random gradient vector \(g\) to each lattice point; at \(p\), blend the dot products of the surrounding corners with a smooth fade.
const fade = (t) => t * t * t * (t * (t * 6 - 15) + 10);
const u = fade(fx), v = fade(fy);
const n = lerp(v,
lerp(u, dot(g00, [fx, fy]), dot(g10, [fx - 1, fy])),
lerp(u, dot(g01, [fx, fy - 1]), dot(g11, [fx - 1, fy - 1])));
The result is zero-mean, band-limited, and continuous.
Fractional Brownian motion (fBm). Sum octaves at doubling frequency and halving amplitude:
function fbm(x, y, octaves) {
let sum = 0, amp = 1, freq = 1;
for (let i = 0; i < octaves; i++) {
sum += amp * noise(x * freq, y * freq);
[x, y] = rotate(x, y, 0.5); // small rotation per octave
amp *= 0.5; freq *= 2;
}
return sum;
}
A small rotation \(R\) per octave avoids axis-aligned banding. Variants:
ridged += amp * Math.pow(1 - Math.abs(n), 2); // sharp crests
turbulence += amp * Math.abs(n); // fire, marble
stepped = Math.floor(L * f) / L; // terraces (add a short riser per step)
Domain warping. Distort the input before evaluating:
const q = [fbm(x, y), fbm(x + 5.2, y + 1.3)];
const r = [fbm(x + 4 * q[0], y + 4 * q[1]), fbm(x + 4 * q[0] + 1.7, y + 4 * q[1] + 9.2)];
const v = fbm(x + 4 * r[0], y + 4 * r[1]);
Produces marble, smoke, and drifting curtains. Anisotropy (high frequency in \(x\), low in \(y\), evaluated at warped coordinates) reads as vertical light rays.
1-D profiles. A landscape silhouette is a function of \(x\) alone, closed into a polygon:
for (let x = 0; x <= width; x += step) {
const h = 0.45 * fbm01(x * f) + 0.9 * Math.pow(ridged(x * f * 2.6), 1.4);
profile.push([x, base - amp * h]);
}
wash(closeToBottom(profile), mix(farColour, nearColour, depth), { opacity: 0.2 + 0.3 * depth });
Stacking several with paler colour and more haze as they recede is atmospheric perspective.
Flow fields. Sample noise to get an angle, drop particles, integrate, and draw the trails:
for (let i = 0; i < steps; i++) {
const theta = 2 * Math.PI * fbm(x * scale, y * scale);
x += h * Math.cos(theta);
y += h * Math.sin(theta);
path.push([x, y]);
}
Evenly spaced streamlines come from a seeding rule: start new lines a distance \(d_\text{sep}\) from existing ones and stop them when they come within \(d_\text{test}\) of another.
Cellular noise (Worley / Voronoi).
const d = featurePoints.map((q) => Math.hypot(p[0] - q[0], p[1] - q[1])).sort((a, b) => a - b);
const cells = d[0], borders = d[1] - d[0];
Scales, cracked earth, stained glass.
Looks like. Clouds, terrain, water, aurora, wood grain, marsh currents.
5. Distance functions and raymarching
Idea. Describe a scene as a function \(f(p)\) returning the signed distance to the nearest surface, then find surfaces by stepping along rays.
Signed distance functions.
const sdSphere = (p, r) => length(p) - r;
const sdBox = (p, b) => length(max(abs(p) - b, 0));
Combine with \(\min\) (union), \(\max\) (intersection), \(\max(a, -b)\) (subtraction), and the smooth union:
function smin(a, b, k) {
const h = clamp(0.5 + (0.5 * (b - a)) / k, 0, 1);
return mix(b, a, h) - k * h * (1 - h);
}
Normals are the gradient \(\nabla f\), estimated by central differences. Infinite repetition is free: evaluate at \(\text{mod}(p, c) - c/2\).
Sphere tracing. Step forward by the distance itself; it guarantees no surface is skipped.
let t = 0;
for (let i = 0; i < 128; i++) {
const d = scene(add(origin, scale(dir, t)));
if (d < 0.001) return t; // hit
t += d;
if (t > far) break;
}
Soft shadows and ambient occlusion fall out of the same distance queries.
Heightfield terrain. Not a true SDF. Step by a fraction of the height above the terrain and add fog with distance:
for (let t = tmin; t < tmax; ) {
const p = add(origin, scale(dir, t));
const dh = p.y - terrain(p.x, p.z); // fbm with few octaves
if (dh < 0.002 * t) return t; // tolerance grows with distance
t += 0.4 * dh;
}
colour = mix(colour, fogColour, 1 - Math.exp(-beta * t));
Looks like. Sculpted 3D forms, infinite abstract architecture, mountains with proper lighting.
6. Simulation and emergence
Idea. Set up local rules, run them for many steps, and render either the final state or (usually better) the accumulated history.
Cellular automata. A grid where each cell's next state depends on its neighbourhood.
Life: survive with 2 or 3 live neighbours; born with exactly 3
Rule 30 (1-D): new = left XOR (centre OR right)
Render 1-D rules as a time strip; continuous variants use a kernel and a smooth growth function.
Reaction–diffusion (Gray–Scott). Two concentrations \(u, v\):
// per cell, per step; lap = 5-point Laplacian
const uvv = u * v * v;
u1 = u + dt * (Du * lap(U, i) - uvv + F * (1 - u));
v1 = v + dt * (Dv * lap(V, i) + uvv - (F + k) * v);
Typical \(D_u = 1,\; D_v = 0.5\), \(F \in [0.01, 0.08]\), \(k \in [0.045, 0.07]\). Different \((F, k)\) give spots, stripes, mitosis, or worms; letting \(F\) and \(k\) vary across the canvas with noise gives several regimes in one image. Relight the final \(v\) as a height field for coral.
Physarum (slime mould). Agents sense a trail field ahead, turn toward the strongest, move, and deposit. The trail decays and blurs each frame.
const L = sample(trail, x + so * Math.cos(a + sa), y + so * Math.sin(a + sa));
const C = sample(trail, x + so * Math.cos(a), y + so * Math.sin(a));
const R = sample(trail, x + so * Math.cos(a - sa), y + so * Math.sin(a - sa));
if (C < L && C < R) a += rng.chance(0.5) ? ra : -ra;
else if (L > R) a += ra;
else if (R > L) a -= ra;
x += Math.cos(a) * speed; y += Math.sin(a) * speed;
trail[idx(x, y)] += deposit;
history[idx(x, y)] += deposit; // render this, not the agents
Render the accumulated trail history: the network only appears when time is integrated.
Differential growth. A closed polyline whose nodes attract their neighbours, repel all other nodes within a radius, are smoothed toward the midpoint of their neighbours, and split when an edge stretches past a threshold.
for (const node of nodes) {
let dx = alpha * (prev.x + next.x - 2 * node.x), dy = alpha * (prev.y + next.y - 2 * node.y);
for (const other of nearby(node, rho)) {
const d = dist(node, other), w = 1 - d / rho;
dx -= beta * w * (other.x - node.x) / d;
dy -= beta * w * (other.y - node.y) / d;
}
node.x += dx; node.y += dy;
}
splitEdgesLongerThan(maxEdge);
It buckles into coral and brain-like folds.
Diffusion-limited aggregation. Random walkers stick when they touch the cluster.
let x = randomOnCircle(), y = ...;
while (!touchesCluster(x, y)) { x += rng.sign(); y += rng.sign(); }
cluster.add(x, y);
Produces dendrites: lightning, frost, coral.
Branching random walks. A path that wanders and forks with shrinking length is a cheap model of runoff channels, deltas, and roots.
function runoff(x, y, angle, len, depth) {
const path = [[x, y]];
for (let i = 0; i < 8; i++) {
angle += rng.gauss() * 0.25;
x += Math.cos(angle) * (len / 8); y += Math.sin(angle) * (len / 8);
path.push([x, y]);
}
stroke(path);
if (depth === 0) return;
for (let i = 0, k = rng.int(1, 3); i < k; i++) {
const [px, py] = path[rng.int(3, 8)];
runoff(px, py, angle + rng.range(-0.9, 0.9), len * rng.range(0.45, 0.7), depth - 1);
}
}
Boids. Separation, alignment, cohesion; draw trails rather than agents.
Chladni figures. Nodal lines of a vibrating plate:
const f = Math.cos(n * Math.PI * x) * Math.cos(m * Math.PI * y)
- Math.cos(m * Math.PI * x) * Math.cos(n * Math.PI * y);
ink = Math.exp(-Math.abs(f) * sharpness); // dark where f ≈ 0
Plot where the function is near zero, or scatter particles that migrate downhill in \(|f|\).
Looks like. Coral, fungal networks, lichen, frost, flocks, deltas.
7. Tiling, packing, and constraint solving
Idea. Fill the plane with pieces under adjacency rules; the order comes from the constraints, the variety from the choices.
Truchet tiles. One tile with two quarter-arcs, placed on a grid in a random orientation. Because arcs meet edge midpoints, every placement is continuous, and the result is a labyrinth.
for (const [i, j] of cells) {
const flip = rng.chance(0.5);
arc(i, j, flip ? "NW" : "NE"); arc(i, j, flip ? "SE" : "SW");
}
Recursive subdivision gives multi-scale versions.
Wave function collapse. Each cell holds a set of possible tiles. Repeatedly pick the cell with the lowest entropy, choose one tile, and propagate the adjacency constraints until nothing changes.
while (uncollapsed.length) {
const cell = minBy(uncollapsed, entropy);
cell.options = [weightedPick(cell.options)];
propagate(cell); // remove neighbours' options that no longer fit
}
Coherent textures from a small example.
Aperiodic tilings. Substitution rules (Penrose kites and darts, rhombs) that never repeat yet show five-fold order.
Circle packing. Greedy rejection sampling for random packings; for the exact Apollonian gasket, Descartes' theorem on curvatures \(\kappa = 1/r\):
const k4 = k1 + k2 + k3 + 2 * Math.sqrt(k1 * k2 + k2 * k3 + k3 * k1); // the inner circle
Voronoi and recursive subdivision. Partition by nearest site, or split rectangles at varying ratios; colour with harmonic hue offsets.
Looks like. Mazes, mosaics, circuit boards, stained glass, quasicrystals.
8. Three-dimensional and node-based workflows
Idea. Every 2D method above can be lifted into 3D: a curve becomes a swept tube, a field becomes a displacement or a density, an IFS becomes a point cloud.
Scatter and instance. Distribute points on a surface, instance a shape at each, and drive scale, rotation, and selection from a noise or cellular field evaluated at the point. Per-instance index gives deterministic variation.
Displacement. Push a mesh along its normal by a field:
vertex.add(normal.multiplyScalar(fbm(vertex.x, vertex.y, vertex.z) * height));
Volumes. Evaluate a density \(\rho(p)\) (domain-warped fBm) and render it with light scattering for clouds and nebulae.
let T = 1, L = 0;
for (let s = 0; s < steps; s++) {
const rho = density(p);
const a = 1 - Math.exp(-rho * ds);
L += T * a * lightAt(p);
T *= 1 - a;
p = add(p, scale(dir, ds));
}
Per-pixel programs. A fragment shader evaluates a function at every pixel in parallel, which is the natural home for SDFs, raymarching, noise, and reaction–diffusion.
9. Physical media: watercolor simulation
Idea. Treat the image as wet pigment on paper rather than as a shaded colour field. The geometry still comes from a mathematical rule; the surface comes from simulating what pigment does.
Deformed-polygon washes. A wash is a polygon whose edges are recursively displaced: subdivide each edge at its midpoint and offset it along the normal.
function deform(poly, iterations, mu) {
for (let it = 0; it < iterations; it++) {
const out = [];
for (let i = 0; i < poly.length; i++) {
const a = poly[i], b = poly[(i + 1) % poly.length];
const len = Math.hypot(b[0] - a[0], b[1] - a[1]);
const d = rng.gauss() * mu * Math.min(len, maxEdge);
const nx = -(b[1] - a[1]) / len, ny = (b[0] - a[0]) / len;
out.push(a, [(a[0] + b[0]) / 2 + nx * d, (a[1] + b[1]) / 2 + ny * d]);
}
poly = out;
}
return poly;
}
for (let j = 0; j < layers; j++) coverage += rasterize(deform(base, 2, bleed)) / layers;
The base polygon is re-displaced independently for each of \(L\) glaze layers, and the layers are stacked at opacity \(\alpha / L\). The edge stays irregular but legible; the interior granulates where layers disagree. Blurring a mask is not the same thing: it produces fog, not paint.
Subtractive pigment. Each wash of colour \(c\) adds absorbance, and the sheet transmits what remains:
const A = colour.map((c) => -Math.log(Math.max(c / 255, 1e-3))); // per channel
for (const i of pixelsUnderWash) {
D[i] += opacity * coverage[i] * A[ch];
}
// at the end:
out = paper * Math.exp(-D);
One wash at full opacity reproduces its colour exactly; two glazes multiply, which is what real watercolor does and what alpha-blending gets wrong. Stacked glazes reach black quickly, so per-layer opacity must fall as layers accumulate.
Paper tooth and edge pooling. Coverage is multiplied by a high-frequency noise field (granulation), and extra pigment is added where coverage changes fastest (the dark rim of a drying wash).
const gx = C[i + 1] - C[i - 1], gy = C[i + w] - C[i - w];
Cp = C[i] * (1 - texture + texture * tooth[i]) + edge * Math.hypot(gx, gy);
Reserve and lift. Reserved pixels never take pigment and read as the white of the sheet: the cyanotype fern is an IFS density used as a mask. Lifting removes pigment already laid down, for mist, snow, waterfalls, god-rays, and highlights.
const mask = blur(rasterize(polygon), softness);
for (let i = 0; i < D.length; i++) D[i] *= 1 - amount * mask[i];
Rings. Concentric washes multiply to black at the centre. An annulus rasterizes the outer polygon with weight \(+1\) and the inner with \(-1\), so each band keeps its own colour.
coverage = rasterize(deform(outer), +1) + rasterize(deform(hole), -1); // clamp ≥ 0
Composition rules that follow from the physics.
- Close a landscape polygon where the next thing starts (the waterline), not at the bottom of the sheet; otherwise it lies under everything painted later and darkens it.
- Lift under anything that must read lighter than what is already there (shore, boulders).
- Let the mathematics draw (IFS, phyllotaxis, ridged profile, recursion) and let the medium paint. Hand-placed blobs with heavy bleed do not survive.
- Keep the mark vocabulary tiny:
wash(polygon, colour, {opacity, bleed, layers, texture, edge}),stroke,lift,reserve. That is what keeps the picture editable as a program.
Looks like. Cyanotypes, herbarium plates, dahlias, ink-wash mountains, national-park poster plates.
Where each method is used
All projects live in mathematical-art-projects/ and share _shared/ (noise, seeded RNG, float raster, watercolor sheet, scenery helpers, PNG export, browser viewer). Each folder has art.js with render(pixels, w, h, seed), a viewer index.html, and export.js for a 4K PNG.
| Project | Method | Section |
|---|---|---|
| Fern Grove | Barnsley IFS chaos game, perturbed maps, log tone map | §3.3 |
| Attractor Nebula | Clifford / De Jong histogram, speed → hue | §3.6 |
| Coral Reef | Gray–Scott with noise-varying \(F, k\), relit as height | §6 |
| Autumn Tree | Stochastic bracketed L-system with taper and tropism; warped clouds | §3.4, §4 |
| Tidal Marsh | Flow field with evenly spaced streamlines | §4 |
| Window Frost | Julia set at a Misiurewicz point, distance estimation | §3.1 |
| Mycelium | Physarum agents, trail history | §6 |
| Aurora | Anisotropic domain-warped fBm curtains | §4 |
| Cyanotype Meadow | Glazed washes over an IFS / bezier reserve, lifted toward the light | §9, §3.3 |
| Dahlia | Vogel phyllotaxis placing glazed petals | §9, §1 |
| Fern Plate | Three IFS species as pigment density, wet then dry | §9, §3.3 |
| Misty Ridges | Ridged fBm profiles as washes, lifting between layers, recursive trees | §9, §4, §3.5 |
| Glacier: Hidden Lake | Ridged profiles with a shaped horn; mirrored reflection; snow lifted | §9, §4 |
| Yosemite: Tunnel View | Power-curve cliff, circular arc cut by a chord; waterfall lifted | §9 |
| Grand Canyon: South Rim | Quantized fBm terraces with a Gaussian gorge | §9, §4 |
| Yellowstone: Grand Prismatic | Annular washes per temperature band; branching random-walk runoff | §9, §6 |
| Joshua Tree: Dusk | Three-level branching recursion with rosette tips | §9, §3.5 |
| Redwood: Fog in the Grove | Trunks in depth planes separated by lifted fog; IFS ferns | §9, §4, §3.3 |
Not yet built: a primitive swarm (§1), a Truchet or wave-function-collapse piece (§7), a true heightfield raymarch (§5), and a geometrically grown coral by differential growth or DLA (§6).