glslop
[ CC0 shader vibe commons · everything here is public domain ]
← glsl techniques

Raymarching

How a fragment shader draws 3D without any geometry: march a ray forward in steps that are guaranteed safe, using a function that measures distance to the nearest surface.

A fragment shader gets one job per pixel: return a colour. It is not handed triangles, vertices, or a depth buffer. So to draw a 3D scene it has to answer, for this one pixel, the question "what would I see looking in this direction?" — and answer it from scratch, in a few hundred instructions.

Raymarching answers it by walking. Fire a ray from the camera through the pixel and step along it until you bump into something. The trick is knowing how big a step is safe. That comes from a signed distance function: a function that takes a point in space and returns the distance to the nearest surface, negative if you are inside it. A sphere of radius 1 at the origin is just length(p) - 1.0.

If the nearest surface is 3 units away, you can safely jump 3 units in any direction without passing through anything. So you jump, ask again, and jump again. Far from the scene the steps are huge; as you approach a surface they shrink, and the ray glides in and stops. That is the whole algorithm, and it is why the loop below is six lines long.

What you get for that is unusual. The scene is a mathematical function rather than a mesh, so it has infinite resolution — zoom forever and the sphere stays a perfect sphere. Objects combine with min (union) and max (intersection). Repeat space with mod and you get infinite copies for the price of one. Blend two shapes with a soft minimum and they melt together, which is a nightmare with polygons and one line here.

The cost is that every pixel pays for the whole scene on every frame, and the step count trades directly against speed. Pull the iteration slider down on the example and you can watch the surface come apart — the ray runs out of steps before it arrives, and the shape dissolves into fog.

in the wild
the march loop — #gstbkfmm
const float TMAX = 48.0;
float t = 0.0;
float d = 0.0;
for (int i = 0; i < 150; i++){
    d = map(ro + rd * t);
    if (d < 0.0013 * (1.0 + t * 1.2) || t > TMAX) break;
    t += d * 0.8;
}

Sphere tracing, entire. map() returns a lower bound on the distance to the nearest surface, so stepping exactly that far can never overshoot — that guarantee is the whole algorithm. The * 0.8 is a safety factor for when a domain warp makes the estimate lie a little; this shader calls that out further up as keeping the field Lipschitz-safe. The hit epsilon grows with t because a pixel covers more world space the further away it is, so a fixed epsilon both wastes steps in the distance and shimmers.

the normal, for free — #gstbkfmm
vec3 calcNormal(vec3 p){
    vec2 e = vec2(0.0015, -0.0015);
    return normalize(e.xyy * map(p + e.xyy) + e.yyx * map(p + e.yyx) +
                     e.yxy * map(p + e.yxy) + e.xxx * map(p + e.xxx));
}

There is no mesh, so there is no normal to look up — you take the gradient of the distance field instead. This is the tetrahedron trick: four offsets arranged so the swizzles e.xyy / e.yyx / e.yxy / e.xxx both move the sample point AND weight the result, which gets a normal in four map() calls rather than the six a naive central difference needs.