The Mathematics of 3D Rendering: The Rendering Equation
Modern video games and CGI in movies look incredibly realistic due to our ability to mathematically simulate how light behaves in the real world. At the heart of this simulation lies The Rendering Equation, introduced by James Kajiya in 1986.
What does it look like?
The Rendering Equation
The fundamental equation that all physical light-transport simulators attempt to solve is:
Breaking it down
To a layman, this looks like inscrutable calculus. But mathematically, it’s just an energy balance equation expressing how light bounces off a specific point .
- : The total outgoing radiance from point in direction . This is essentially what the camera sees.
- : The emitted radiance. Is the object glowing? E.g., a lightbulb or the sun.
- : The integral over the hemisphere of incoming directions . This means we must check every possible angle light could be hitting the point from.
- : The Bidirectional Reflectance Distribution Function (BRDF). This defines the material properties. Is it a matte wall or a shiny mirror? It decides what proportion of incoming light from bounces toward .
- : The incoming radiance. How much light is actually arriving from direction ?
- : The attenuation factor (Lambert’s Cosine Law). Light hitting a surface at a steep angle transfers less energy than light hitting straight on. It is the dot product of the incoming light vector and the surface normal .
Why is it so hard to compute?
Look closely at the incoming light term: . Where does that incoming light come from? It comes from other surfaces in the scene.
To know for our current point, we must evaluate for the surface the light bounced from!
This means the rendering equation is recursive.
Evaluating an infinite-dimensional integral analytically is impossible for anything but a perfectly empty glowing sphere. Therefore, computer graphics engineers use Monte Carlo integration.
Monte Carlo Path Tracing
Instead of integrating perfectly, we shoot random rays to estimate the integral. The expected value function looks like this:
By randomly bouncing light rays (Paths) around the scene thousands of times per pixel (), the image converges to the correct solution.
// A highly simplified C++ pseudocode for Path Tracing
Vec3 trace_ray(Ray ray, int depth) {
if (depth > MAX_BOUNCES) return Vec3(0, 0, 0);
HitRecord rec;
if (world.hit(ray, 0.001, infinity, rec)) {
Ray scattered;
Vec3 attenuation;
Vec3 emitted = rec.mat->emitted(rec.u, rec.v, rec.p);
// Material decides how light scatters (the BRDF probability)
if (rec.mat->scatter(ray, rec, attenuation, scattered)) {
return emitted + attenuation * trace_ray(scattered, depth + 1);
}
return emitted;
}
return background_color(ray);
}
The next time you see a beautiful photorealistic game, remember that your GPU is evaluating this integral millions of times per second!