Raymarching and signed distance fields
Classic raytracing (14-raytracing.md) solves "where does this ray hit?" with algebra, so it needs an intersection equation per shape type. Raymarching declines to solve it: it walks along the ray until it is close enough to a surface. All it ever asks is "from here, how far is the nearest surface?"
The field
A signed distance field maps a point to the distance to the nearest surface — negative inside, zero exactly on it.
A sphere is length(p) - r. A box is length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0) with q = abs(p) - b. A ground plane at height h is p.y - h, essentially free. The sign is not decoration — it tells the march which side it is on, makes "inside?" a comparison rather than a query, and is what lets you subtract one shape from another at all.
Sphere tracing
The distance value is a safe step size — nothing can be nearer than that, so stepping exactly that far can never overshoot.
That sentence is the whole algorithm. Start at t = 0; evaluate d = map(o + t*dir); if d < epsilon you have hit; otherwise t += d and repeat until t passes a maximum distance (miss) or the iteration cap runs out (give up). Each step is the radius of a sphere known to be empty — hence sphere tracing. A ray driving head-on at a surface converges in a handful of steps; one that skims a surface keeps finding a small distance and crawls, which is why silhouettes are the expensive part of the picture.
Normals for free
The gradient of the field is the normal.
No stored vertex normals, no mesh: sample the field a hair either side on each axis and difference the results — six evaluations, or four with the tetrahedron trick. The same field gives soft shadows (march toward the light, track the minimum distance met; a near miss means a partial occluder) and ambient occlusion (step along the normal — if the field grows more slowly than you walked, something is crowding the point).
The operators are three characters
Union is min, intersection is max, subtraction is max(a, -b).
Real constructive solid geometry, one line each, no mesh booleans and no topology to repair. A smooth minimum blends two shapes into a fillet instead of a crease. And the input point is yours to abuse: p = mod(p, c) - 0.5*c tiles one object through infinite space, and rotating, twisting or displacing p before evaluating bends the geometry (domain warping). A world in thirty lines of fragment shader is why raymarching owns demoscene and shader art.
What it is bad at
Cost scales with step count, not triangle count.
Good at dense procedural scenes, bad at large sparse ones where every ray marches a long way through nothing. It does not share a rasteriser's depth buffer without extra work. And a field that overstates distance — non-Lipschitz, usually from scaling or deforming without correcting — lets a step jump clean through thin geometry; the patch is taking a fraction (0.5–0.9) of each step. So games use it for clouds, fog, volumetrics and effects rather than general geometry. See also ../math/trig-and-vector-heuristics.md.
Demo
The march drawn on a 2D slice — the field is visualised as distance bands, and each step draws a circle whose radius is the distance there, with the next step landing on its edge. Drag the ray until it grazes a surface and watch the circles crawl; switch scenes to see a boolean change the bands, not just the outline; push step × above 1 to punch through the thin walls.
Gotchas
| Symptom | Cause |
|---|---|
| Surfaces look eaten away or pocked with holes | Step multiplier at 1.0 on a non-exact field; distance is overstated, steps overshoot |
| A scaled object marches through itself | Scaling the input scales the answer: sd(p*k) must become sd(p*k)/k |
| Edges of shapes are noisy and crawl as the camera moves | Grazing rays hitting the iteration cap, not a real miss; raise max steps or fade cap-outs to background |
| Frame rate collapses when the camera backs off | Rays now cross a lot of empty space; add a bounding-volume skip or raise the step floor far from geometry |
| Surfaces look inflated or details are missing | Epsilon too large; scale it with distance (eps * t) instead of using one constant |
| Twisting or bending the domain produces artefacts | Domain warping breaks the distance guarantee; take fractional steps in the warped region |
| Normals come out speckled, or faceted and soft | Gradient offset badly sized: too small and float precision dominates, too large and it smooths real detail away |
| Raymarched geometry draws over rasterised meshes | No depth written from the march; output gl_FragDepth from the hit t |