← all demos · chapter 16

The depth atlas — 104 illusions of depth on a flat plane, A to Z four times (+104 rhymes)

Every gallery before this one asked how does it move? This one asks how does it look round, far, lit, or solid — on a surface that has no third axis? The answer is always a gradient of something: of colour (a sky), of contrast (a mountain range dissolving into the air), of brightness across a form (a ball, a cylinder), of three flat shades meeting at an edge (a cube from squares), of size-brightness-speed together (smoke and sparkles near and far), of slope (a wave, a twisting ribbon), of blur (focus), of darkness on the ground (a shadow). None of it needs a camera, a mesh, or a light — which is the point: a 2D game or a web page gets the depth without paying for the dimension. Eight families, one mechanism each. Skim until you recognise the picture you were imagining, then open its code and turn its numbers. Press ▶ Run all, or wake one card at a time. Every card can be touched — a small badge says click or drag until you have — and on the drag cards the canvas is an invisible slider: pull the sun across the sky, scrub a sunrise, pan a mountain range, swing a lamp round a ball. The tempo buttons run every clock at ×2 or ×4 for skimming — a time-lapse, so the slow ones (a full day, a moon's month) show their whole idea in a few seconds. One card, X·Xenon (a strobe), contains flashing light: it never starts with Run all and shows a notice instead — it runs only when you click it.

Every picture also hides a rhyme. A rhyme is the same picture with two or three dials turned — a palette, a count, a speed — and nothing else. Click ⇄ its rhyme on any card to swap them in place (104 more pictures folded into the same cards, and where the genres live: sci-fi fog, candy sunrises, synthwave grids, glitch auroras), then open code to read the pair side by side: the rhyme's opening comment names exactly which dials moved. Once a recipe is understood, its neighbours are nearly free.

tempo ↓ jump to the code editor

The editor — pick a picture above

Every picture is one function, make(u), returning { frame(dt, t), press(x, y) }. Its dials sit at the top in a D = { … } object — palettes as hex strings, counts, speeds — so the first thing to edit is always in the first three lines. The kit u is three gradient calls and a handful of forms: lin(x0, y0, x1, y1, stops) and rad(x, y, r, stops, ox, oy) build linear and radial gradients (the ox, oy offset moves the radial's centre — that offset is what turns a disc into a lit ball), sky(stops) fills the canvas top to bottom, fog(colour, depth, air) is atmospheric perspective in one call, and sphere, cyl, cube, iso, shadow, soft are gradients already folded into shapes. Colour helpers mix, shade, rgba, hsl keep palettes editable as plain hex. Change a number, press ▶ Run — the picture repaints from your code. Click the preview to move its light, camera, or weather.

The preview is bigger than the cards; every picture is drawn relative to its canvas, so the same code fills any size. Click or drag the preview to move the light, the camera, or the weather. A card declares drag: true when its press sets a value rather than firing an event — the runtime then repeats the press while the pointer is held.

Try tweaking: in Orb, set the light offset to 0 and the ball flattens into a disc — the offset is the roundness; push it past the edge and the ball reads as backlit. In Alps, change u.fog's depth multiplier to 0 and every mountain turns the same colour: the range collapses into a paper cut-out (that flatness is what atmospheric perspective was hiding). In Block, swap the top and right shades and the cube becomes an inside corner — the same three squares, now a room. In Plume, make the near puffs paler instead of darker and the smoke turns inside-out. In Contact, freeze the shadow while the ball rises and the ball stops rising — it just gets bigger. The universal anatomy: depth is a gradient of some property between "here" and "there". Choose the property, choose the two ends, and the eye supplies the space between.

In Godot: all 104 (+104 rhymes) are ported — depth.gd pages through the eight families in scenes/depth/; drag the drag cards, right-click any card for its rhyme, double-click to open it large in the middle of the window. Godot's CanvasItem has no gradient-fill call, so the port's kit.gd shows the two honest spellings: a linear gradient is draw_polygon with one colour per vertex (the GPU interpolates for free), and a radial gradient is a fan of draw_primitive triangles with a bright centre colour and a dark rim colour — the same offset-centre trick as the web. For nodes rather than _draw, GradientTexture2D (fill: linear / radial, with fill_from/fill_to) on a Sprite2D or TextureRect is the zero-code route (grab the Godot project and press its menu's J). Godot also gets a 3D sibling this gallery can't draw on canvas: depth_wire_3d.gd (menu key K) — a turning wireframe whose lines fade with distance from the camera, via per-vertex colours and a five-line spatial shader; it's the recipe for putting gradients on any line-mesh gallery. In Unity a gradient is a Gradient asset evaluated into a Texture2D (or a Shader Graph Gradient/Lerp over UV), a sphere is a sprite with a radial-gradient texture, a cube is three sprites in three tints — DepthAtlas2D.cs paints six ambassadors (sky, fogged ridges, lit ball, cube, depth-sorted smoke, contact shadow) from code. In Unreal it's the material editor's LinearGradient and RadialGradientExponential nodes in 3D and the same nodes on a UMG Image in 2D — the recipe covers both spellings. Chapter 16 is the full encyclopaedia entry: the eight cues, why each one works, what each costs, and how the same picture is spelled in all four engines.