The Render Equation

  • The single most important equation in computer graphics.
  • Introduced by James T. Kajiya in 1986 — “The Rendering Equation”.
  • Parent: PathTracer Learning

The Full Equation

L_o(x, ω_o) = L_e(x, ω_o) + ∫_Ω f_r(x, ω_i, ω_o) L_i(x, ω_i) (N · ω_i) dω_i
  • This is a Fredholm integral equation of the second kind
  • The solution is the radiance value that your eye (or camera) receives — the brightness and color of each pixel

Breaking Down Every Term

L_o(x, ω_o) — Outgoing Radiance

  • The result we want to compute
  • x — the surface point being shaded
  • ω_o — the outgoing direction (toward the camera or previous bounce)
  • Units: W/m²/sr (watts per square meter per steradian)
  • This is the “color” of the point as seen from direction ω_o

L_e(x, ω_o) — Emitted Radiance

  • Light emitted directly by the surface (if it’s a light source)
  • For most surfaces: L_e = 0 (they don’t glow)
  • For an area light: L_e = power / (area * π) (Lambertian emitter)
  • ω_o dependence: some lights emit differently in different directions

∫_Ω ... dω_i — Hemisphere Integral

  • Integrate over all possible incoming directions ω_i in the upper hemisphere Ω
  • The upper hemisphere = all directions above the surface normal N
  • dω_i — differential solid angle (infinitesimal cone of directions)
  • ∫_Ω dω_i = 2π steradians for the full hemisphere
  • In practice: we can’t solve this analytically for most scenes → Monte Carlo

f_r(x, ω_i, ω_o) — The BRDF

  • Bidirectional Reflectance Distribution Function
  • Describes HOW the surface at x scatters incoming light from ω_i toward ω_o
  • Units: 1/sr
  • Lambertian (matte): f_r = albedo / π (same in all directions)
  • GGX (specular): f_r = D(h) G(ω_i, ω_o) F(ω_o, h) / (4 NdotL NdotV)
  • See PathTracer Learning BRDF and PathTracer Learning Physically Based Rendering

L_i(x, ω_i) — Incoming Radiance

  • The radiance arriving at point x from direction ω_i
  • THIS is why the equation is recursive / hard
  • L_i(x, ω_i) = L_o(hit(x, ω_i), -ω_i) — it’s the outgoing radiance at the next surface hit
  • hit(x, ω_i) — cast a ray from x in direction ω_i, find the first surface hit
  • L_i depends on L_o, which depends on L_i… infinite recursion

(N · ω_i) — The Cosine Term

  • Lambert’s cosine law
  • N — surface normal at point x
  • ω_i — incoming light direction
  • cos(θ) where θ is the angle between the normal and the light direction
  • When light hits at a grazing angle (θ ≈ 90°), cos(θ) ≈ 0 — almost no energy per unit area
  • When light hits head-on (θ = 0°), cos(θ) = 1 — maximum energy per unit area
  • This is a geometric fact, NOT a material property
  • Implementation: max(0.0, dot(N, omega_i)) — clamp to zero (no light from below surface)

The Recursive Nature

  • The rendering equation is self-referential:
L_o(x, ω_o) = L_e + ∫ f_r * L_o(hit(x,ω_i), -ω_i) * cos(θ) dω_i
  • To know the color of a point, you need the color of all other points it can see
  • To know those, you need the color of all points they can see
  • This is infinite recursion — direct solution is impossible for complex scenes
  • Solutions:
    • Path tracing — Monte Carlo: sample one random direction, recurse with finite depth + Russian Roulette
    • Radiosity — only diffuse surfaces, finite element method
    • Photon Mapping — trace photons from lights, then gather
    • VPL (Virtual Point Lights) — represent global illumination as many tiny point lights

How Path Tracing Solves It

  • Monte Carlo estimator for the rendering equation:
L_o(x, ω_o) ≈ L_e(x, ω_o) + (1/N) Σ [ f_r(x, ω_i^n, ω_o) * L_i(x, ω_i^n) * cos(θ^n) / p(ω_i^n) ]
  • For N=1 (one sample per bounce):
    • Sample one random direction ω_i from a probability distribution p(ω_i)
    • Trace a ray in that direction → hit a surface → recursively evaluate
    • Divide by p(ω_i) to correct for the sampling distribution (importance sampling)
    • Repeat many times per pixel and average → converges to the true integral
  • Convergence rate: O(1/√N) — slow but unbiased
  • See PathTracer Learning Monte Carlo Integration

The Importance of Sampling Direction

  • Naive sampling: pick ω_i uniformly from hemisphere
    • PDF: p(ω) = 1/(2π)
    • Most samples hit dark areas → high variance → noisy
  • Cosine-weighted sampling: weight by cos(θ)
    • PDF: p(ω) = cos(θ)/π
    • The cos(θ)/π cancels the (N·ω_i)/p term → estimate = f_r * L_i
    • Better for diffuse — still suboptimal for specular
  • BRDF importance sampling: sample proportional to f_r * cos(θ)

Extended Forms

With Emissive Medium (Volumetric)

  • The Light Transport Equation (LTE) extends the rendering equation to participating media
  • Adds a scattering integral through the volume along the ray
  • Used for fog, smoke, clouds, skin subsurface scattering
L(x→y) = L_e(x→y) + ∫ f_s(x, ω_i, ω_o) L(x'→x) G(x,x') V(x,x') dA
  • G(x,x') — geometry term: cos(θ_x) * cos(θ_y) / |x-y|²
  • V(x,x') — visibility: 1 if unoccluded, 0 if shadow

Spectral Form

  • Replace L with L_λ(λ) — spectral radiance per wavelength
  • BRDF also becomes wavelength-dependent: f_r(ω_i, ω_o, λ)
  • Required for accurate dispersion (prisms), fluorescence, interference effects
  • Path trace by sampling one wavelength per ray (Hero Wavelength Sampling)

Energy Balance Check

  • For any point x and any outgoing direction ω_o:
∫_Ω f_r(x, ω_i, ω_o) cos(θ_i) dω_i ≤ 1
  • = 1 — perfectly reflective surface (mirror), no absorption
  • < 1 — some light is absorbed (every real surface)
  • > 1 — physically impossible! Check your BRDF implementation
  • Common cause of energy gain bugs: wrong 1/π factor in Lambertian BRDF

The Rendering Equation in Code

vec3 traceRay(Ray ray, int depth) {
    if (depth <= 0) return vec3(0.0);
 
    HitInfo hit = findClosestIntersection(ray);
 
    // No hit → environment / sky
    if (!hit.valid) return sampleEnvMap(ray.direction);
 
    // L_e term — emission
    vec3 L_emitted = hit.material.emissive ? hit.material.emission : vec3(0.0);
 
    // ∫ f_r * L_i * cos(θ) dω_i  (Monte Carlo, 1 sample)
    vec3 omega_i = sampleHemisphere(hit.normal);       // sample ω_i
    float pdf    = hemispherePDF(hit.normal, omega_i); // p(ω_i)
 
    vec3 f_r     = evalBRDF(hit.material, ray.direction, omega_i, hit.normal);
    float cosTheta = max(0.0, dot(hit.normal, omega_i));
 
    Ray scattered = Ray(hit.point + hit.normal * 1e-4, omega_i);
    vec3 L_i = traceRay(scattered, depth - 1);    // recursive call
 
    vec3 L_indirect = f_r * L_i * cosTheta / pdf;  // Monte Carlo estimator
 
    return L_emitted + L_indirect;
}

Historical Context

  • 1980 — Whitted introduces recursive ray tracing (specular only)
  • 1984 — Cook et al. introduce distributed ray tracing (stochastic sampling)
  • 1986 — Kajiya publishes “The Rendering Equation” — the unified framework
  • 1997 — Jensen introduces photon mapping
  • 2002 — Jensen introduces path tracing to production
  • 2018 — Real-time path tracing via RTX / DXR / Vulkan RT

Checklist

  • TODO Can write the rendering equation from memory
  • TODO Understand what each term represents physically
  • TODO Can explain why L_i makes it recursive
  • TODO Know why the cos(θ) term appears and what it means geometrically
  • TODO Understand why uniform sampling is suboptimal
  • TODO Can implement the rendering equation as a path tracer in pseudocode