Recursive Raytracing

Here is where it all comes together. We can intersect a ray with a sphere, we can reflect it off a mirror, bend it through glass, and shade the point it hits with the lighting equation. A raytracer is the astonishingly short recursive routine that stitches those pieces into a whole image — and falls out of one idea: to find the colour a ray sees, trace it, then trace the rays it spawns.

That single self-referential rule produces hard shadows, mirror reflections, and clear glass almost for free — phenomena that older rasterizers had to fake with separate hacks. Whitted's 1980 algorithm is barely a page of pseudocode; let's derive it line by line.

The algorithm, line by line

Step 1 — shoot a primary ray per pixel. Place the camera at a point and the image as a grid in front of it. For each pixel, fire one primary ray from the camera through that pixel out into the scene. An image W \times H pixels wide starts W\cdot H primary rays — one per pixel.

Step 2 — find the nearest hit. Test the ray against every object and keep the closest intersection (smallest positive distance t along the ray). Nearer surfaces occlude farther ones, so the nearest hit is what this pixel actually sees. If nothing is hit, the ray escapes to the background or sky.

Step 3 — compute local lighting at the hit. At the hit point, evaluate the ambient + diffuse + specular colour from the previous page, using the surface normal there. This is the base colour of the surface before any reflections or shadows are folded in.

Step 4 — cast a shadow ray to each light. Is this point actually lit, or is something in the way? Fire a shadow ray from the hit point straight toward each light. If any object blocks it before the light, that light is occluded — drop its diffuse and specular contribution, and the point falls into shadow. Shadows are just "can the light see this point?", asked with one more ray.

Step 5 — recurse for mirror reflections. If the surface is reflective, spawn a reflection ray in the bounced direction

\vec{R} = \vec{D} - 2(\vec{D}\cdot\vec{N})\,\vec{N},

and call the same routine on it. Whatever colour that ray returns is what the mirror shows. This is the recursion: a mirror's pixel is coloured by re-running the whole tracer down the reflected ray.

Step 6 — recurse for transparency. If the surface is transparent, spawn a refraction ray bent by Snell's law into the material, and recurse on that too. Its returned colour is what you see through the glass. (Total internal reflection is handled here automatically — when refraction is impossible, the ray reflects instead.)

Step 7 — blend the returned colours by material. Combine the local shading with the reflected and refracted results, weighted by how mirror-like and how transparent the surface is:

\text{colour} = (1 - r - \tau)\,\text{local} + r\,\text{reflected} + \tau\,\text{refracted},

where r is the reflectivity and \tau the transparency of the material.

Step 8 — stop at a maximum depth. Reflections inside reflections could bounce forever (two facing mirrors), so cap the recursion at a maximum depth d. When a ray has bounced d times, stop and return black (or the ambient colour). This guarantees the routine terminates — and depth d is the single knob trading quality against cost.

That's the entire algorithm: trace, find the nearest hit, shade it, then trace the rays it spawns until you run out of depth. Shadows, mirrors, and glass all emerge from the same eight steps.

To find the colour seen along a ray, at recursion depth up to d:

Raytracing has always been the beautiful, slow way to render. Its cost is brutal: W \cdot H primary rays, each spawning shadow rays per light and fanning out a reflection/refraction tree up to depth d — and to smooth the jaggies and soft shadows you fire many rays per pixel and average them. A single frame can cost billions of ray–object tests. For decades that meant raytracing lived in film render farms, where a frame could take hours, while games used cheaper rasterization.

Two things changed it. Hardware gained dedicated RT cores that do ray–triangle intersection in silicon, and — crucially — denoising let renderers fire just a few noisy rays per pixel and reconstruct a clean image with a neural filter, instead of averaging thousands. Together they dragged the billion-ray frame into the millisecond budget of a game, and real-time raytraced reflections and shadows arrived in living rooms. The maths didn't change — these are still the same eight steps — we just learned to afford them.