This chapter links to the course’s interactive demos and uses MathJax for its formulas. Read it through the course website (or download the file and open it in a browser); a Canvas inline preview will reach neither the demos nor the math. All chapters.

Ray Tracing and Global Illumination

Arthur Appel cast rays from the eye to shade and shadow solids in 1968, at IBM, twelve years before Whitted's recursive ray tracer. Appel, Some Techniques for Shading Machine Renderings of Solids, AFIPS 1968

The rasterizer of the earlier chapters asks, for each triangle, which pixels it covers. Ray tracing asks the opposite question, for each pixel, which surface it sees, by following a ray from the eye through the pixel into the scene. The reversal costs a search per pixel and buys everything the rasterizer cannot do directly: exact shadows, mirrors, glass, and, once the rays are allowed to bounce at random, the whole of global illumination. This chapter builds the machine from its parts. It casts a camera ray through the illumination demo’s sphere and solves the quadratic by hand, intersects a triangle two ways and a box, sends a ray into an instanced ellipsoid, traces Whitted’s reflection and refraction tree with Snell’s law and the Fresnel factor worked, renders a small scene with that recursion, and then softens it: an area light sampled for a penumbra and a thin lens for depth of field, both rendered. A bounding-volume hierarchy turns a scene of seventy thousand triangles from impossible to routine. The second half replaces the fixed tree by random sampling: a four-sample Monte Carlo estimate, the measured \(1/\sqrt{N}\) convergence, the two ways to sample a lighting integral and the combination that survives both failures, and a Cornell box path-traced at 8 and 512 samples per pixel next to the same scene lit directly, the difference being the indirect light a local model omits. Fog and the shader stages of a hardware ray tracer close the chapter.

Contents
  1. Rays, and the ray through a pixel
  2. Intersections: sphere, triangle, box
  3. Instancing: rays into object space
  4. Whitted ray tracing: shadows, mirrors, glass
  5. Distributed rays: soft shadows, depth of field, motion blur
  6. Acceleration: the bounding-volume hierarchy
  7. Monte Carlo integration and importance sampling
  8. Path tracing and the Cornell box
  9. Light in a medium
  10. Radiosity, photon maps, bidirectional paths, and hardware rays
  11. Exercises

1 · Rays, and the ray through a pixel

Definition (ray) A ray is a point and a unit direction, \(\mathbf{r}(t) = \mathbf{o} + t\,\mathbf{d}\), \(t \ge 0\). Intersecting it with a surface means finding the smallest positive \(t\) at which \(\mathbf{r}(t)\) lies on the surface; \(t\) is then the distance to the hit.

The camera of the viewing chapter gives the ray through a pixel. With the eye at \(\mathbf{e}\), the camera basis \(\mathbf{u}, \mathbf{v}, \mathbf{w}\) (right, up, and backward), a vertical field of view \(\phi\) and a square image of \(S\) pixels, the pixel \((p_x, p_y)\) has normalized coordinates \(s_x = 2(p_x + \tfrac12)/S - 1\), \(s_y = 1 - 2(p_y + \tfrac12)/S\) in \([-1, 1]\), and its ray is \[ \mathbf{d} = \mathrm{normalize}\bigl(s_x \tan\tfrac{\phi}{2}\,\mathbf{u} + s_y \tan\tfrac{\phi}{2}\,\mathbf{v} - \mathbf{w}\bigr), \qquad \mathbf{o} = \mathbf{e}. \] This is the projection matrix run backward: instead of mapping a point to a pixel, it maps a pixel to the line of points that project there. No matrix is needed, and no perspective divide: the perspective is in the fan of directions. The interaction chapter builds the same ray from the inverse of the projection and view matrices, and the two agree to four digits on the pixel below.

Worked example (the ray through the marked point’s pixel) The illumination demo’s camera at \(\mathbf{e} = (3, 3, 6)\) looking at the origin, with \(\phi = 28^\circ\) and \(S = 150\), has basis \(\mathbf{u} = (0.894, 0, -0.447)\), \(\mathbf{v} = (-0.183, 0.913, -0.365)\), \(\mathbf{w} = (0.408, 0.408, 0.816)\) and \(\tan 14^\circ = 0.2493\). The marked point \((0.693, 0.693, 0.693)\) projects to pixel \((89, 62)\) (its pixel in the illumination chapter’s figure). That pixel’s center has \((s_x, s_y) = (0.1933, 0.1667)\), so \(\mathbf{d} = \mathrm{normalize}(0.0482\,\mathbf{u} + 0.0416\,\mathbf{v} - \mathbf{w}) = (-0.372, -0.370, -0.852)\), pointing from the camera toward the origin and slightly to the camera’s left and up, where the marked point sits.

2 · Intersections: sphere, triangle, box

Ray against a sphere A sphere of center \(\mathbf{c}\) and radius \(r\): substitute the ray into \(|\mathbf{p} - \mathbf{c}|^2 = r^2\). With \(\mathbf{m} = \mathbf{o} - \mathbf{c}\) and unit \(\mathbf{d}\), \[ \begin{gathered} t^2 + 2(\mathbf{m}\cdot\mathbf{d})\,t + (\mathbf{m}\cdot\mathbf{m} - r^2) = 0, \\ t = -b \pm \sqrt{b^2 - c}, \qquad b = \mathbf{m}\cdot\mathbf{d},\; c = \mathbf{m}\cdot\mathbf{m} - r^2. \end{gathered} \] A negative discriminant is a miss; otherwise the smaller positive root is the entry point and the larger the exit. The normal at a hit \(\mathbf{p}\) is \((\mathbf{p} - \mathbf{c})/r\).
Worked example (the demo’s sphere, hit and miss) For the ray of Section 1 and the sphere \(r = 1.2\) at the origin, \(\mathbf{m} = \mathbf{e}\), so \(b = \mathbf{e}\cdot\mathbf{d} = 3(-0.372) + 3(-0.370) + 6(-0.852) = -7.334\) and \(c = 54 - 1.44 = 52.56\). The discriminant \(b^2 - c = 53.78 - 52.56 = 1.222\) is positive: \(t = 7.334 \mp 1.106\), entry at \(t = 6.228\), exit at \(8.439\). The hit is \(\mathbf{e} + 6.228\,\mathbf{d} = (0.683, 0.698, 0.697)\), within 0.012 of the marked point (the pixel center is not exactly the point’s projection), and the normal there is the hit divided by 1.2. Pixel \((20, 20)\) near the image corner gives \(b = -7.119\) and \(b^2 - c = -1.887\): no real root, the ray misses the sphere and sees the background.
Hands-on The sphere test is four lines with the course library’s vector functions. From the course folder:
node -e "
import('./lib/core/xform.js').then(x=>{
const e=[3,3,6], d=x.normalize([-0.372,-0.370,-0.852]);
const b=x.dot(e,d), c=x.dot(e,e)-1.44, disc=b*b-c; const t=-b-Math.sqrt(disc);
console.log({b:+b.toFixed(4), c:+c.toFixed(2), disc:+disc.toFixed(4), t:+t.toFixed(4), hit:e.map((v,i)=>+(v+t*d[i]).toFixed(3))});})"
Output:
{ b: -7.3336, c: 52.56, disc: 1.2224, t: 6.228, hit: [ 0.685, 0.697, 0.697 ] }
(the three-decimal direction differs from the exact one in the fourth digit, hence the hit’s last digit).
  1. Replace the direction by that of pixel \((20, 20)\), \(\mathrm{normalize}(s_x\tan14^\circ\,\mathbf{u} + s_y\tan14^\circ\,\mathbf{v} - \mathbf{w})\) with \(s_x = s_y = -0.7267\), and confirm a negative discriminant.
  2. Move the sphere to \(\mathbf{c} = (0, 0.5, 0)\) by replacing \(\mathbf{e}\) with \(\mathbf{e} - \mathbf{c}\) in \(b\) and \(c\); the ray now passes the sphere on which side?
  3. Add the far root \(-b + \sqrt{\text{disc}}\) and compute the chord length inside the sphere, \(2\sqrt{\text{disc}}\).
Ray against a triangle, two ways A triangle \(\mathbf{A}\mathbf{B}\mathbf{C}\) with normal \(\mathbf{n} = (\mathbf{B}-\mathbf{A})\times(\mathbf{C}-\mathbf{A})\). Plane first: \(t = (\mathbf{A} - \mathbf{o})\cdot\mathbf{n} / (\mathbf{d}\cdot\mathbf{n})\), then the inside test on the hit point with the three edge functions of the rasterization chapter, which also yield the barycentric weights for interpolating the vertex attributes. Möller–Trumbore (1997) solves for \(t\) and the barycentric coordinates \((u, v)\) of the hit at once, without forming the plane: with \(\mathbf{e}_1 = \mathbf{B} - \mathbf{A}\), \(\mathbf{e}_2 = \mathbf{C} - \mathbf{A}\), \(\mathbf{s} = \mathbf{o} - \mathbf{A}\), \(\mathbf{p} = \mathbf{d}\times\mathbf{e}_2\), \(\mathbf{q} = \mathbf{s}\times\mathbf{e}_1\), \[ \det = \mathbf{e}_1\cdot\mathbf{p}, \qquad u = \frac{\mathbf{s}\cdot\mathbf{p}}{\det}, \qquad v = \frac{\mathbf{d}\cdot\mathbf{q}}{\det}, \qquad t = \frac{\mathbf{e}_2\cdot\mathbf{q}}{\det}, \] a hit exactly when \(u \ge 0\), \(v \ge 0\), \(u + v \le 1\) and \(t > 0\); \(\det = 0\) means the ray is parallel to the triangle. It is Cramer’s rule on the three unknowns of \(\mathbf{o} + t\mathbf{d} = \mathbf{A} + u\,\mathbf{e}_1 + v\,\mathbf{e}_2\), and it is the routine in nearly every production tracer because it reads only the three vertices and does two cross products and four dot products.
Worked example (Möller–Trumbore on one triangle) \(\mathbf{A} = (0,0,0)\), \(\mathbf{B} = (2,0,0)\), \(\mathbf{C} = (0,2,0)\); ray from \((0.5, 0.5, 1)\) straight down, \(\mathbf{d} = (0, 0, -1)\). \(\mathbf{e}_1 = (2, 0, 0)\), \(\mathbf{e}_2 = (0, 2, 0)\), \(\mathbf{p} = \mathbf{d}\times\mathbf{e}_2 = (2, 0, 0)\), \(\det = 4\). \(\mathbf{s} = (0.5, 0.5, 1)\), \(u = \mathbf{s}\cdot\mathbf{p}/4 = 0.25\); \(\mathbf{q} = \mathbf{s}\times\mathbf{e}_1 = (0, 2, -1)\), \(v = \mathbf{d}\cdot\mathbf{q}/4 = 0.25\); \(t = \mathbf{e}_2\cdot\mathbf{q}/4 = 1\). Hit at \(t = 1\) with barycentric coordinates \((1 - u - v, u, v) = (0.5, 0.25, 0.25)\): the plane-first method gives the same \(t = 1\), hit point \((0.5, 0.5, 0)\), and the same weights. The ray from \((1.5, 1.5, 1)\) gives \(u = v = 0.75\), \(u + v = 1.5 > 1\): outside, beyond the hypotenuse, which plane-first reports as a negative edge function \(-2\) on edge \(\mathbf{B}\mathbf{C}\). A ray with \(\mathbf{d} = (1, 0, 0)\) gives \(\det = 0\): parallel, no test.
Ray against a box An axis-aligned box \([x_0, x_1]\times[y_0, y_1]\times[z_0, z_1]\) is three slabs: for each axis the ray is inside the slab for \(t \in [(x_0 - o_x)/d_x,\; (x_1 - o_x)/d_x]\) (sorted); the ray hits the box exactly when the three intervals overlap, entering at the largest lower bound and leaving at the smallest upper bound. A zero component of \(\mathbf{d}\) gives an infinite interval, or an empty one if the origin is outside that slab.
Worked example (a box) The unit box \([0,1]^3\) and the ray from \((-1, 0.5, 0.5)\) with \(\mathbf{d} = (0.981, 0.196, 0)\): the \(x\) slab gives \(t \in [1.02, 2.04]\), the \(y\) slab \(t \in [-2.55, 2.55]\), and \(d_z = 0\) with \(o_z\) inside the \(z\) slab imposes nothing; the overlap is \([1.02, 2.04]\), so the ray enters at \(t = 1.02\), at \((0, 0.7, 0.5)\) on the box’s left face.

A ray tracer’s inner loop is these three routines. Everything else in a scene is either triangles (meshes), or is tested through a box around it (Section 6), or is reached by transforming the ray (next).

3 · Instancing: rays into object space

A scene of a thousand identical chairs stores one chair and a thousand matrices, as the scene-graph chapter describes. A ray tracer never transforms the chair; it transforms the ray. With the object’s world matrix \(M\), the ray is pulled into object space by \(M^{-1}\), intersected with the stored geometry there, and the hit distance and normal are carried back.

The transformed ray \(\mathbf{o}' = M^{-1}\mathbf{o}\) (as a point), \(\mathbf{d}' = M^{-1}\mathbf{d}\) (as a vector, translation ignored), and \(\mathbf{d}'\) is not renormalized: then the parameter \(t\) of the object-space hit is the world-space parameter too, and the world hit is \(\mathbf{o} + t\mathbf{d}\). The normal comes back through the inverse transpose, \((M^{-1})^{\top}\mathbf{n}'\), for the reason given in the illumination chapter. Since \(\mathbf{d}'\) is not unit, the sphere quadratic keeps its leading coefficient \(a = \mathbf{d}'\cdot\mathbf{d}'\).
Worked example (an ellipsoid as a scaled sphere) The unit sphere scaled by \(S = \mathrm{diag}(2, 1, 1)\) is an ellipsoid two units long in \(x\). Ray from \((4, 0.5, 0)\) in direction \((-1, 0, 0)\). Object space: \(\mathbf{o}' = (2, 0.5, 0)\), \(\mathbf{d}' = (-0.5, 0, 0)\), of length \(0.5\). The quadratic with \(a = 0.25\), \(b = \mathbf{o}'\cdot\mathbf{d}' = -1\), \(c = |\mathbf{o}'|^2 - 1 = 3.25\): discriminant \(b^2 - ac = 0.1875\), \(t = (1 - 0.433)/0.25 = 2.268\). The object-space hit is \((0.866, 0.5, 0)\), on the unit sphere; the world hit is \(\mathbf{o} + 2.268\,\mathbf{d} = (1.732, 0.5, 0)\), and \(1.732^2/4 + 0.5^2 = 1\) confirms it is on the ellipsoid, \(2.268\) units from the origin of the ray as \(t\) says. The object normal is the hit itself, \((0.866, 0.5, 0)\); through \((S^{-1})^{\top} = \mathrm{diag}(\tfrac12, 1, 1)\) and normalizing it is \((0.655, 0.756, 0)\). Pushing the normal through \(S\) instead would give \((0.961, 0.277, 0)\), \(33^\circ\) off, and the ellipsoid would shade as if it were much flatter than it is.

Instancing also makes the bounding-volume hierarchy of Section 6 two-level: a top-level tree over the instances’ world-space boxes, and one bottom-level tree per distinct object in its own space, built once and shared. Hardware ray tracing exposes exactly this structure.

4 · Whitted ray tracing: shadows, mirrors, glass

Appel cast rays in 1968 to find visible surfaces and shadows. Whitted’s 1980 algorithm made the hit point itself a source of rays: at each hit, shade with the local model of the illumination chapter, but first send a shadow ray toward each light and use the light only if nothing blocks it; then, for a reflective surface, send a reflection ray in the mirror direction and add what it sees; for a transparent one, send a refraction ray bent by Snell’s law and add that too. Each secondary ray is traced by the same routine, so the image is a recursion, a tree of rays per pixel, cut off at a maximum depth.

Reflection, refraction, Fresnel For incoming direction \(\mathbf{d}\) and unit normal \(\mathbf{n}\) facing the ray, the reflection is \(\mathbf{d} - 2(\mathbf{d}\cdot\mathbf{n})\,\mathbf{n}\) (the illumination chapter’s \(\mathbf{R}\) with the sign of \(\mathbf{L}\) reversed). Refraction from index \(n_1\) into \(n_2\) obeys Snell’s law \(n_1 \sin\theta_1 = n_2\sin\theta_2\); with \(\eta = n_1/n_2\) and \(\cos\theta_1 = -\mathbf{d}\cdot\mathbf{n}\), \[ \mathbf{t} = \eta\,\mathbf{d} + \bigl(\eta\cos\theta_1 - \sqrt{1 - \eta^2(1 - \cos^2\theta_1)}\bigr)\,\mathbf{n}, \] and when the square root’s argument is negative there is no refracted ray: total internal reflection. The split between reflected and refracted energy is the Fresnel factor, approximated by Schlick as \(F = F_0 + (1 - F_0)(1 - \cos\theta_1)^5\) with \(F_0 = \bigl((n_1 - n_2)/(n_1 + n_2)\bigr)^2\).
Worked example (glass) Air to glass, \(n_2 = 1.5\): a ray at \(30^\circ\) from the normal refracts to \(\arcsin(\sin 30^\circ / 1.5) = \arcsin 0.333 = 19.47^\circ\); at \(60^\circ\), to \(35.26^\circ\). Glass to air the angles reverse, and the exit angle reaches \(90^\circ\) at the critical incidence \(\arcsin(1/1.5) = 41.81^\circ\); beyond it the ray stays inside. \(F_0 = (0.5/2.5)^2 = 0.04\): glass reflects 4 % head-on, 4.2 % at \(45^\circ\), 7 % at \(60^\circ\), 41 % at \(80^\circ\), and 92 % at \(89^\circ\), which is why a window is a mirror at a grazing angle and nearly invisible face-on. A Whitted tracer weights the reflection ray by \(F\) and the refraction ray by \(1 - F\).
A two-dimensional scene: a gray mirror circle and a blue glass circle above a floor line, a light above, and the eye below; two black primary rays from the eye, red reflection rays off the mirror and inside the glass, blue refraction rays bending through the glass, dashed orange shadow rays reaching the light and dashed gray ones blocked.
Two primary rays traced to depth 2 in a flat scene, by this chapter’s code: 13 ray segments in all, 6 of them shadow rays. The ray into the glass sphere refracts on entry, splits again inside, and refracts on exit.
A rendered image: a mirror sphere reflecting a checkered floor and the sky, a glass sphere refracting and reflecting them, and a small red diffuse sphere between, all casting sharp shadows on the checkered floor under a blue sky gradient.
The chapter’s Whitted renderer: 64,000 primary rays at depth 4 over three spheres and a plane, a point light with shadow rays, Phong shading, mirror reflection and Fresnel-weighted glass. The upside-down floor in the mirror sphere and the inverted view through the glass are the recursion at work.

What the picture has that the rasterizer lacks: shadows that are exact by construction (a shadow ray either hits something or does not), reflections of things off-screen, and refraction that shows the scene through the sphere. What it lacks is everything soft: the shadows have hard edges because the light is a point, the mirror is perfect because there is one reflection ray, the floor under the spheres is not tinted by them because a diffuse hit sends no further rays. Those are the limits of following one ray per event, and the rest of the chapter removes them by sending many.

Pitfall (the shadow acne) A shadow ray that starts exactly at the hit point re-intersects the same surface at \(t \approx 10^{-8}\) because of rounding, and the surface shadows itself in a speckled pattern. Every tracer offsets the origin along the normal by a small \(\varepsilon\) (the chapter’s code uses \(10^{-4}\)) or discards hits with \(t < \varepsilon\). Too small an \(\varepsilon\) leaves acne; too large lets a shadow ray start on the far side of a thin object, and the object stops casting a shadow. The right value scales with the scene, and a robust tracer computes it from the floating-point error bound of the hit point (Pharr, Jakob and Humphreys give the derivation).

5 · Distributed rays: soft shadows, depth of field, motion blur

Cook, Porter and Carpenter (1984) noticed that every hard edge in a Whitted image comes from replacing an integral by a single sample: a point light instead of an area, a pinhole instead of a lens, an instant instead of a shutter interval, a mirror direction instead of a glossy lobe. Their distributed ray tracing sends several rays per event, each at a random position in the integral’s domain, and averages. It is the Monte Carlo of Section 7 before the name, and it produces the effects that make an image look photographed.

Definition (an area light’s contribution) For a light of area \(A_L\), radiance \(L_e\), and normal \(\mathbf{n}_L\), the direct illumination at a point \(\mathbf{p}\) with normal \(\mathbf{n}\) is the integral over the light’s surface of \(L_e\,V\,\cos\theta_p\cos\theta_L / r^2\), where \(V\) is 1 if the light point is visible from \(\mathbf{p}\) and 0 otherwise, \(r\) the distance, and the cosines those at the two ends. Estimated with \(N\) points on the light, each term weighted by \(A_L/N\). The fraction of visible samples is the visibility, and where it is strictly between 0 and 1 the point is in the penumbra.
Two renderings of a red sphere on a gray floor: on the left, one shadow ray to the light's center gives a hard-edged shadow; on the right, sixteen jittered shadow rays give a shadow with a soft, graded edge.
The same sphere and square light, one shadow ray (left) against sixteen (right). The right image is noisy at the shadow’s edge because sixteen samples give seventeen visibility levels, the coverage quantization of the antialiasing chapter.
Worked example (a point in the penumbra) A sphere of radius \(0.5\) at height \(0.5\) over the floor, and a square light of side \(1.2\) at height \(3\) centered at \((0.8, 3, 0.8)\). The shadow’s center on the floor is at \((-0.16, 0, -0.16)\), on the line from the light’s center through the sphere’s. Walking outward along \(x\), the umbra (no light visible) ends at radius \(0.506\) and the fully lit floor begins at \(0.721\): a penumbra \(0.215\) wide, close to the geometric estimate \(1.2 \times 0.5 / 2.5 = 0.24\) (light size times the sphere’s height over its distance from the light). At the point \((0.44, 0, -0.16)\) in its middle, four stratified samples at the centers of the light’s quadrants give two blocked and two visible: visibility \(0.5\), against the exact \(0.496\) from 4,096 samples. Sixteen samples give \(8/16\). One sample at the light’s center is blocked, and the point is wrongly black.

A thin lens gives depth of field. Instead of one ray from the eye, choose a point on a disk of radius \(a\) (the aperture) around it, and aim at where the pinhole ray crosses the focal plane at distance \(f\): points on that plane are seen the same by every lens sample and stay sharp; points at another distance \(z\) spread over a circle of confusion of diameter \(2a\,|z - f|/z\) measured on the focal plane.

Two renderings of three colored spheres on a checkered floor: the pinhole image is sharp everywhere, with a moire pattern on the far floor; the thin-lens image has the middle sphere sharp and the near blue and far green spheres blurred, and the distant floor blurred smooth.
Pinhole (left) and thin lens with 32 samples per pixel (right), focused on the middle sphere. The blur also removes the far floor’s aliasing, which is the same integral taken over the aperture instead of the pixel.
Worked example (how blurred) Aperture radius \(0.12\), focus at \(3.482\) from the lens. The near sphere at depth \(2.31\) has a circle of confusion of \(2\cdot0.12\cdot1.17/2.31 = 0.122\) units on the focal plane, and the far one at \(5.04\), \(0.074\); at this camera the focal plane shows \(55.1\) pixels per unit, so the near sphere’s edges smear over about \(6.7\) pixels and the far sphere’s over \(4.1\), while the middle sphere at \(3.48\) has a circle of \(0\). Stopping the aperture down to \(0.03\) would quarter both.

Motion blur is the same trick on the time axis: give each ray a random time within the shutter interval and place the moving objects where they are at that time. Glossy reflection samples directions around the mirror direction weighted by the BRDF lobe of the light-transport chapter. All four effects cost the same: \(N\) times the rays for a noise that falls as \(1/\sqrt N\), and the rays of different effects can share the same sample (one ray with a lens position, a light position and a time), so the cost does not multiply.

6 · Acceleration: the bounding-volume hierarchy

Every ray of every kind must find its nearest hit, and the naive way tests every object. For the bunny of the meshes chapter at \(1920\times1080\) with three rays per pixel (primary, shadow, reflection), that is \(2{,}073{,}600 \times 3 \times 69{,}451 \approx 4.3\times10^{11}\) triangle tests for one frame. The fix is to test boxes before triangles, hierarchically.

Definition (bounding-volume hierarchy) A binary tree whose every node holds the axis-aligned box of all the primitives below it; the leaves hold a few primitives each. It is built by splitting a node’s primitives into two groups (by the median of their centroids along the box’s longest axis, or by minimizing the expected cost of a ray visiting the children) and recursing. A ray traverses it by testing a node’s box and descending only on a hit, testing primitives only at the leaves it reaches, and pruning any subtree whose box lies beyond the nearest hit found so far.
Fourteen gray triangles scattered in a rectangle, enclosed by nested boxes at four levels in black, blue, green and orange; the boxes a red ray from the lower left passes through are drawn solid and tinted, the others dashed; the ray ends at its first triangle hit.
A three-level hierarchy over 14 triangles, built by median split, and one ray’s traversal: 15 box tests and 7 triangle tests, against 14 triangle tests by brute force, with the same nearest hit at \(t = 7.09\).
Worked example (what the tree buys) On the small scene of the figure the saving is modest: 7 triangle tests instead of 14, at the price of 15 box tests. The saving is in the scaling. A balanced tree over \(T\) primitives has depth \(\log_2 T\), and a ray that hits few things visits a few nodes per level, so its cost is \(O(\log T)\) instead of \(O(T)\). For the bunny, \(\log_2 69{,}451 = 16.1\): on the order of \(2\times17\) box tests and a handful of triangle tests per ray, roughly \(2\times10^8\) tests for the frame above instead of \(4\times10^{11}\), two thousand times fewer. Building the tree costs \(O(T\log T)\) once, or once per frame for deforming geometry, which is what the dedicated hardware in a modern GPU does.

The median split is the simplest builder. Production builders minimize the surface area heuristic: the expected cost of a node is the probability of a ray entering each child, which is proportional to the child’s surface area, times the child’s primitive count, and the split plane is chosen to minimize that sum over a few candidate positions. It produces trees that a ray traverses two to three times faster than median trees on real scenes. Alternatives partition space rather than objects: a uniform grid (cheap to build, poor when objects cluster) or a k-d tree (splits space by axis-aligned planes; excellent traversal, harder to update). Production tracers use BVHs, and the acceleration structure is why ray tracing scales to scenes of billions of triangles while the rasterizer must touch each triangle every frame.

7 · Monte Carlo integration and importance sampling

Soft shadows, glossy reflections, and light bouncing between diffuse surfaces are all integrals: over the area of a light, over a lobe of directions, over the whole hemisphere above a point. The rendering equation of the light-transport chapter is that integral in its general form. It has no closed form for any scene of interest, and the way to evaluate it is to sample it.

Definition (Monte Carlo estimator) To estimate \(I = \int f(\omega)\,d\omega\), draw \(N\) random directions \(\omega_i\) from a density \(p(\omega)\) and average \[ \hat I = \frac1N \sum_{i=1}^{N} \frac{f(\omega_i)}{p(\omega_i)}. \] The estimate is unbiased (its expectation is \(I\)) for any \(p\) that is positive wherever \(f\) is, and its standard deviation falls as \(1/\sqrt N\). Choosing \(p\) close to the shape of \(f\) (importance sampling) reduces the constant; choosing \(p \propto f\) makes every sample exact.
Worked example (four samples of the cosine integral) The irradiance from a uniform sky of radiance 1 is \(\int_{H} \cos\theta\,d\omega = \pi\) over the hemisphere. Sampling directions uniformly, \(p = 1/2\pi\), four draws with cosines \(0.720, 0.456, 0.763, 0.487\) sum to \(2.426\), and the estimate is \(2\pi \times 2.426 / 4 = 3.811\), 21 % above \(\pi\). With cosine-weighted sampling, \(p = \cos\theta/\pi\), each term is \(\cos\theta_i / (\cos\theta_i/\pi) = \pi\) exactly and any number of samples gives \(\pi\): the integrand has been folded into the sampling. Real integrands are not so kind, but cosine-weighted directions are the standard choice for diffuse bounces because the cosine is always a factor.
Log-log plot of relative RMS error against sample count from 1 to 4096 for the uniform-sampling estimate: blue points on a straight line of slope minus one half, from 0.58 at one sample to 0.009 at 4096.
Measured error of the uniform estimate, 200 trials at each \(N\). Four times the samples halves the error: 0.30 at 4, 0.136 at 16, 0.073 at 64, 0.0094 at 4096.

The \(1/\sqrt N\) law is the economics of every renderer in this chapter. Halving the noise costs four times the samples; going from a preview to a clean frame is not a few more samples but a few hundred times more. It is also why the noise is honest: an unbiased estimator’s error is random, with no systematic darkening or brightening, and it averages away. The one lever that changes the constant is \(p\), and a lighting integral offers two natural choices.

Definition (two strategies, and the balance heuristic) The direct light at a point is \(\int f_r(\omega)\,L(\omega)\cos\theta\,d\omega\), a product of the BRDF and the incoming light. Light sampling picks directions toward the light (\(p\) proportional to the light’s solid angle): every sample hits the light, and the estimator fails when the BRDF is a narrow lobe that the light’s directions rarely fall into. BRDF sampling picks directions from the lobe: every sample is where the material reflects, and it fails when the light is small and rarely hit. Multiple importance sampling (Veach and Guibas, 1995) draws from both and weights each sample by the balance heuristic, \[ w_a(\omega) = \frac{p_a(\omega)}{p_a(\omega) + p_b(\omega)}, \] so that a sample counts most where its own strategy was the good one; the combined estimator is unbiased and its variance is never much worse than the better strategy’s.
Bar chart of relative RMS error at 16 samples for three integrands: with a small light and a wide lobe, light sampling 0.02, BRDF sampling 0.81, balance heuristic 0.22; with a large light and a narrow lobe, 0.77, 0.01, 0.22; with both moderate, 0.20, 0.20, 0.16.
Two sampling strategies and their balance-heuristic combination, measured over 2,000 trials of 16 samples on three product integrands (a narrow and a wide Gaussian standing in for the light and the lobe). Each single strategy fails by a factor of forty on one integrand; the combination is within a factor of ten of the best on all three, and better than both when neither dominates.
Worked example (one sample, two weights) In the small-light case, a direction with \(p_{\text{light}} = 2.70\) and \(p_{\text{BRDF}} = 1.448\): the balance weights are \(2.70/4.148 = 0.651\) for the light strategy and \(0.349\) for the BRDF strategy. The integrand there is \(3.91\). Light sampling alone would score it \(f/p_{\text{light}} = 1.448\), BRDF sampling alone \(2.70\), and the combined estimator, which draws from each half the time, \(f / (\tfrac12 p_{\text{light}} + \tfrac12 p_{\text{BRDF}}) = 1.885\) whichever strategy produced it. The combined weight is the harmonic blend of the two densities, and that is what keeps a rare BRDF sample that happens to hit the small light from scoring enormously, which is where the BRDF-only variance of \(0.81\) comes from.

Next-event estimation is the name for adding a light sample at every bounce of a path, and MIS is what reconciles it with the path’s own bounce direction hitting the light by chance. The chapter’s Cornell box uses both.

8 · Path tracing and the Cornell box

Path tracing (Kajiya, 1986) applies the estimator to the rendering equation recursively. At a hit point, estimate the direct light by sampling a point on each light and sending a shadow ray; then estimate the indirect light by choosing one random bounce direction, tracing it, and weighting what it returns by the BRDF and cosine over the density. Each pixel’s value is the average over many such paths. Because the bounce is a single random direction, the path does not branch; it is a random walk from the eye that ends when it escapes the scene or, to keep it finite without bias, when Russian roulette terminates it.

Russian roulette is unbiased Continue a path with probability \(q\) and divide its further contribution \(X\) by \(q\); stop with probability \(1 - q\) and contribute 0. The expectation is \(q\cdot E[X]/q + (1 - q)\cdot 0 = E[X]\), unchanged; only the variance grows, by a factor of about \(1/q\) on the terminated part. With \(q = 0.7\) after two bounces, \(0.7^4 = 24\,\%\) of paths reach the sixth bounce and the expected number of extra bounces is \(q/(1 - q) = 2.3\). Choosing \(q\) equal to the path’s accumulated throughput (dark paths die young) keeps the variance cost small.
Three renderings of a Cornell box with a red left wall, green right wall, two white spheres and a ceiling light: the first shows direct light only, with black shadows and black ceiling; the second is path traced at 8 samples per pixel and is speckled with noise; the third, at 512 samples, is smooth, with the shadows filled in, the ceiling lit, and the walls' colors bleeding onto the spheres and floor.
The chapter’s Cornell box (\(96\times72\) pixels, enlarged): direct light only at 256 samples per pixel, then path traced at 8 and at 512 samples, maximum depth 6, Russian roulette with continuation probability 0.7 after two bounces. About a minute of computation for the three.
Worked example (what one path contributes) A path leaves the eye, hits the floor (albedo \(k_1\)), bounces to the red wall (albedo \(k_2\)), and there its shadow ray sees the light with radiance \(L_e\), geometric factor \(G\) and light-sampling density \(p_A\). With cosine-weighted bounces (density \(\cos\theta/\pi\)) and a diffuse BRDF \(k/\pi\), the bounce weight is \((k/\pi)\cos\theta / (\cos\theta/\pi) = k\): each bounce simply multiplies by the albedo. The path’s indirect contribution to the pixel is \(k_1 \cdot \bigl[\text{direct at the wall}\bigr] = k_1\, k_2\, L_e\, G / p_A\), and if roulette with \(q = 0.7\) let the bounce happen, the value is divided by 0.7 to compensate for the 30 % of paths that were stopped. Averaged over hundreds of such paths, the floor near the red wall comes out pink: color bleeding, which the direct-only panel cannot show because its paths stop at the floor.

Read the three panels against the local model. Direct light only is the illumination chapter with area-light shadows: the ceiling is black (the light faces down), the shadows are black, the walls are their own color and nothing else. At 8 samples the path tracer already has every effect, buried in noise; at 512 the noise has fallen by \(\sqrt{64} = 8\) and the effects are legible: the ceiling is lit from below, the shadows are soft and filled, the spheres pick up red on the left and green on the right. Nothing in the algorithm names any of these effects. They are what the integral contains.

Production path tracers add what the chapter’s does not: importance sampling of glossy BRDFs, next-event estimation with many lights chosen by their expected contribution, the bidirectional and Metropolis variants of Section 10 for hard paths (caustics through glass), and denoisers that trade the last factor of ten in samples for a learned filter. Every one of them is the same estimator with a better \(p\).

9 · Light in a medium

Everything so far assumed that light travels unchanged between surfaces. Fog, smoke, clouds, milk and skin do not allow that: light is absorbed and scattered along the way, and a ray’s radiance changes with every unit of distance it travels through the participating medium.

Definition (extinction, transmittance, single scattering) A medium with absorption coefficient \(\sigma_a\) and scattering coefficient \(\sigma_s\) has extinction \(\sigma_t = \sigma_a + \sigma_s\) (per unit length) and albedo \(\sigma_s/\sigma_t\). Radiance traveling a distance \(d\) is reduced by the transmittance \[ T(d) = e^{-\sigma_t d} \] (the Beer–Lambert law; \(\sigma_t d\) is the optical depth). Along a ray, light is also added by scattering toward the eye: with single scattering, the in-scattered radiance is \(\int_0^d T(x)\,\sigma_s\,L_{\text{light}}(x)\,\rho(\theta)\,dx\), where \(\rho\) is the phase function (the medium’s BRDF, a function of the scattering angle; isotropic \(\rho = 1/4\pi\) for fog, forward-peaked for clouds), and \(L_{\text{light}}(x)\) is the light reaching the point \(x\) on the ray, itself attenuated.
Transmittance against distance from 0 to 8 for three extinction coefficients, exponential decays: 0.2 still at 0.2 after 8 units, 0.5 reaching one half at 1.386, 1.5 near zero by 3.
Beer–Lambert for three media. The distance at which half the light is gone is \(\ln 2/\sigma_t\).
Worked example (fog by the numbers) \(\sigma_t = 0.5\) per meter: \(T = 0.607\) after \(1\) m, \(0.368\) after \(2\), \(0.135\) after \(4\), \(0.018\) after \(8\); half the light is gone at \(1.386\) m. A light morning fog with \(\sigma_t = 0.05\) per meter lets \(2\,\%\) of light through at \(\ln 50/0.05 = 78\) m, which is about the visibility a driver would report. Single scattering of a uniform light of radiance 1 into a 2-meter ray through the \(\sigma_t = 0.5\) medium with \(\sigma_s = 0.3\), integrated in four steps of \(0.5\) m with the transmittance at each step’s middle, adds \(\sum T(x_k)\,\sigma_s\,\Delta x = 0.378\) (phase function and the light’s own attenuation set to 1): the fog glows with a good third of the light that falls on it.

Ray marching evaluates that integral by stepping along the ray, as the worked example did, and a volumetric path tracer instead samples a scattering distance from \(T\) (\(d = -\ln\xi/\sigma_t\) for a uniform \(\xi\)) and continues the random walk from there, which is unbiased and handles multiple scattering. Subsurface scattering in skin and marble is the same physics with a surface entry and exit, and is the reason those materials look soft. Real-time engines approximate a homogeneous fog by the transmittance alone, applied per pixel from the depth buffer, plus a few marched samples for light shafts.

10 · Radiosity, photon maps, bidirectional paths, and hardware rays

Radiosity (Goral, Torrance, Greenberg and Battaile, 1984) solved the diffuse case before path tracing was practical: divide the scene into patches, compute a form factor for every pair (the fraction of light leaving one that arrives at the other, a geometric integral), and solve the resulting linear system for each patch’s brightness. It is view-independent, so the solution can be baked into vertex colors or lightmaps and walked through in real time, which is what every game’s precomputed lighting still is. Its cost grows as the square of the patch count and it handles only diffuse surfaces.

Photon mapping (Jensen, 1996) traces paths from the lights instead of the eye, stores the hits in a spatial map, and estimates radiance at a point from the density of nearby photons. It handles caustics, which eye-first path tracing finds only by luck, at the price of a blurred, biased estimate. Bidirectional path tracing (Veach and Guibas, 1994; Lafortune and Willems, 1993) traces one path from the eye and one from the light and connects every vertex of one to every vertex of the other with shadow rays, weighting all the resulting paths by MIS; it finds light through a keyhole or under a lampshade that the eye path alone would almost never reach. Metropolis light transport (Veach and Guibas, 1997) goes further and, having found one such path, explores its neighbors by small mutations, so that the hard paths are sampled in proportion to how much light they carry.

Denoising is the practical companion of all of them: a filter, today a trained network, that takes an image at a few samples per pixel together with its depth, normal and albedo buffers and produces one that looks converged, trading the last order of magnitude of samples for a small bias in the fine detail. Every production renderer ships one.

Real-time ray tracing arrived in hardware in 2018: GPUs with dedicated units for box and triangle tests traverse two-level BVHs at billions of rays per second, enough for one or a few samples per pixel per frame. The programming model is five shader stages: a ray generation shader (one per pixel, the camera of Section 1), an intersection shader for primitives that are not triangles (the sphere of Section 2), an any-hit shader called at every candidate hit (alpha-tested leaves, shadow rays that can stop at the first hit), a closest-hit shader (the shading of Section 4, which may recurse), and a miss shader (the sky). The acceleration structure is built by the driver from the triangles and the instance matrices of Section 3. Games use the stages for shadows, reflections and one bounce of indirect light, run a denoiser over the noisy result, and rasterize everything else. The hybrid is the current state of the field: the rasterizer for visibility, rays for the effects it cannot do, and the estimator of Section 7 underneath both.

Papers A. Appel, “Some Techniques for Shading Machine Renderings of Solids,” AFIPS Spring Joint Computer Conference, 1968. T. Whitted, “An Improved Illumination Model for Shaded Display,” Communications of the ACM 23(6), 1980. R. Cook, T. Porter and L. Carpenter, “Distributed Ray Tracing,” SIGGRAPH 1984. C. Goral, K. Torrance, D. Greenberg and B. Battaile, “Modeling the Interaction of Light Between Diffuse Surfaces,” SIGGRAPH 1984. J. Kajiya, “The Rendering Equation,” SIGGRAPH 1986. E. Veach and L. Guibas, “Optimally Combining Sampling Techniques for Monte Carlo Rendering,” SIGGRAPH 1995. H. W. Jensen, “Global Illumination Using Photon Maps,” Eurographics Workshop on Rendering, 1996. T. Möller and B. Trumbore, “Fast, Minimum Storage Ray-Triangle Intersection,” Journal of Graphics Tools 2(1), 1997. E. Veach, Robust Monte Carlo Methods for Light Transport Simulation, PhD thesis, Stanford, 1997. M. Pharr, W. Jakob and G. Humphreys, Physically Based Rendering, 4th ed., MIT Press, 2023.

11 · Exercises

Exercise 1 (camera rays) For the camera of Section 1, which pixel does the center of the image correspond to, and what is its ray direction?
Solution Pixel \((74.5, 74.5)\), that is, the corner shared by pixels 74 and 75: \(s_x = s_y = 0\), so \(\mathbf{d} = -\mathbf{w} = (-0.408, -0.408, -0.816)\), straight at the origin. It hits the sphere at \(t = |\mathbf{e}| - 1.2 = 7.348 - 1.2 = 6.148\), the nearest point of the sphere to the camera.
Exercise 2 (a sphere from inside) The ray from the origin in direction \((0, 1, 0)\) against the demo’s sphere. Which root is the hit?
Solution \(\mathbf{m} = 0\), \(b = 0\), \(c = -1.44\): \(t = \pm1.2\). The negative root is behind the origin; the hit is \(t = 1.2\) at \((0, 1.2, 0)\), from inside, and the outward normal \((0, 1, 0)\) faces away from the ray. A tracer must flip the normal when \(\mathbf{d}\cdot\mathbf{n} > 0\), as the glass code does on exit.
Exercise 3 (Möller–Trumbore, by code) Implement the Section 2 formulas with the course library’s cross and dot and run them on the triangle with the ray from \((0.2, 1.5, 1)\), direction \((0, 0, -1)\). Inside or outside?
Solution
node -e "import('./lib/core/xform.js').then(x=>{
const A=[0,0,0],B=[2,0,0],C=[0,2,0],o=[0.2,1.5,1],d=[0,0,-1];
const e1=x.sub(B,A),e2=x.sub(C,A),p=x.cross(d,e2),det=x.dot(e1,p),s=x.sub(o,A);
const u=x.dot(s,p)/det,q=x.cross(s,e1),v=x.dot(d,q)/det,t=x.dot(e2,q)/det;
console.log({det,u,v,t,inside:u>=0&&v>=0&&u+v<=1});})"
gives \(\det = 4\), \(u = 0.1\), \(v = 0.75\), \(t = 1\), inside (\(u + v = 0.85\)); the hit is \((0.2, 1.5, 0)\) with barycentric weights \((0.15, 0.1, 0.75)\), mostly on \(\mathbf{C}\).
Exercise 4 (instancing) The same ellipsoid as Section 3, but the ray comes from \((0, 3, 0)\) straight down. Find the world hit and the world normal, and check the normal against the obvious answer.
Solution Object space: \(\mathbf{o}' = (0, 3, 0)\), \(\mathbf{d}' = (0, -1, 0)\), \(a = 1\), \(b = -3\), \(c = 8\), disc \(= 1\), \(t = 2\): hit \((0, 1, 0)\) in object space and in world space (the scale is in \(x\) only). Object normal \((0, 1, 0)\); through \(\mathrm{diag}(\tfrac12, 1, 1)\) it is still \((0, 1, 0)\), the top of the ellipsoid facing straight up, as it must.
Exercise 5 (Snell) A ray inside glass hits the surface at \(50^\circ\). What happens? At \(30^\circ\)?
Solution \(50^\circ > 41.81^\circ\): total internal reflection, no refracted ray. At \(30^\circ\): \(\sin\theta_2 = 1.5\sin30^\circ = 0.75\), \(\theta_2 = 48.6^\circ\), bending away from the normal on the way out.
Exercise 6 (a penumbra) Using the geometric estimate of Section 5, how wide is the penumbra of the same sphere if the light is raised to height 6, and if instead the light’s side is halved?
Solution Width \(\approx\) light size \(\times\) sphere height \(/\) (light height \(-\) sphere height): at height 6, \(1.2 \times 0.5 / 5.5 = 0.109\), half as wide; with side 0.6 at height 3, \(0.6 \times 0.5 / 2.5 = 0.12\), also half. A farther or smaller light makes sharper shadows, and the sun, tiny in angle, makes the sharpest of all.
Exercise 7 (the tree) A scene of one million triangles, leaves of 4. How deep is a balanced BVH, and roughly how many box tests does a ray that hits one leaf make?
Solution \(2^{18} = 262{,}144\) leaves need depth 18. Visiting one child per level after the root: about \(2\times18 = 36\) box tests (both children are tested at each level, one is descended) and 4 triangle tests, against a million.
Exercise 8 (Monte Carlo) The estimate at 64 samples has relative error 0.073. How many samples for 0.01? For 0.001?
Solution Error scales as \(1/\sqrt N\): \(N = 64 \times (0.073/0.01)^2 = 3{,}411\), about 3,400; for 0.001, \(3.4\times10^5\). The chapter’s run at 4096 samples measured 0.0094, in agreement.
Exercise 9 (the balance heuristic) A sample has \(p_{\text{light}} = 0.1\) and \(p_{\text{BRDF}} = 5\), with integrand value 2. What does each single strategy score it, and what does the combined estimator score it? Which strategy would you guess produced it?
Solution Light-only: \(2/0.1 = 20\); BRDF-only: \(2/5 = 0.4\); combined: \(2/(0.05 + 2.5) = 0.784\). The BRDF strategy almost certainly produced it (its density is fifty times higher there), and the combined score stays near the BRDF-only value; had the light strategy produced it by luck, light-only would have scored a 20 that inflates the variance, and MIS is what prevents that.
Exercise 10 (roulette and fog) With continuation probability \(q = 0.7\), what fraction of paths reach depth 6, and what is the expected number of bounces after depth 2? And through how many meters of the \(\sigma_t = 0.5\) medium does a path travel, on average, before it scatters?
Solution Reaching depth 6 needs four survivals: \(0.7^4 = 0.24\). Expected extra bounces \(q/(1-q) = 2.33\). The scattering distance \(d = -\ln\xi/\sigma_t\) has mean \(1/\sigma_t = 2\) m (the mean free path), and its median is the half-distance \(1.386\) m.