Path Tracing
Realistic renders of complex scenes using Monte Carlo simulations
Introduction to Ray Tracing
Ray tracing is an algorithm for rendering a digital scene. It is a fairly simple algorithm: for each pixel in the final image, shoot a ray from the camera, and render the color of the first object that ray hits.
On the right, you can see a simple ray-traced scene. There are six walls, and a single point light in the middle of the “room”.
For every pixel, the ray tracer casts a ray through the pixel, which hits one of the walls. Then, it casts a ray from that hit point to the light, to check how far away the light is from the hit point. Finally, it combines the color of the light with the color of the material at the hit point, making the color we see in the pixel.
Try dragging the scene on the right around to interact with it! So far, it's not very interesting.
Features of Ray Tracing
Here is a more interesting scene. We have added a few things:
- Instead of one light, there are now three point lights in this scene
- There are two spheres in the scene - one diffuse, one reflective
Notice that the spheres have shadows. Remember how we said that for each ray we cast into the scene, we then cast a ray to the light? We can safely ignore any light if we encounter a different object in the scene first - that's how we render shadows.
We also have a reflective sphere! It turns out to be quite easy to render reflective materials with a ray tracer - instead of stopping the ray at the first place it hits, just reflect it and return that result instead!
We can render even cooler scenes using our newfound ability to handle mirrors...
Limitations of Ray Tracing
Now this is cool! We replaced our four diffuse walls with mirrors, and now we see the power of the ray tracer at work.
The reflection is not infinite - we need to cap the number of bounces, to avoid an infinite loop! On the right, we limit to 5 bounces.
But as it turns out, our ray tracer is actually limited. The problem is simple: until now, we have been working with a backwards ray tracer. We're working backwards from how it works in reality!
In the real world, light comes from a light source to our eye. But in our ray tracer, we trace a ray from our eye (the camera) to the light. But why is this a problem?
Introduction to Path Tracing
Let's go back to our simple scene, this time with an area light instead of a point light.
The problem is that we only consider one path from our eye to the light for each pixel. But there are actually tons of paths (infinite, in fact)! In real life, our eye just sums these. But on a computer, it's not so easy.
The solution? Monte Carlo path tracing. Instead of trying to sample every single path to the light, we will just sample one randomly. So for each ray we send from the camera, when it hits something in the scene we will then randomly shoot off another ray, to sample the incoming light at that point. Then we do it over and over again - the first frame will look like a mess, but when averaged out with dozens or hundreds of other frames, we'll start to get a much cleaner result.
Basically, instead of only ever considering the local illumination at any given point, we will also consider the global illumination
Let's see what this global illumination looks like...
Global Illumination
On the right, you can see the global illumination in the scene. We've also added the balls from the original scene back.
The first thing you may notice is how noisy the scene is. It will smooth out over time, but then if you drag the scene it goes right back to being noisy!
This is because of the randomness inherent to Monte Carlo path tracing. The entire idea is to get a lot of quick and dirty results, and average them together to get a slow and clean result.
Why does the scene get so noisy every time you drag it? Because we need to start computing the global illumination again from scratch - there's no way of using the global illumination from one scene in another one.
Notice how the global illumination takes light bouncing into account. If you reset the camera (by clicking the Reset button below the scene), the top left part of the ceiling has a red tint, and the top right has a blue tint. You can scroll back up to convince yourself that the ray tracer does not have this same property!
Putting it All Together
Now we can put it all together: the ray tracing for direct illumination, and the path tracing for indirect illumination. What you see on the right is a pretty realistic scene.
We've come a long way since our simple ray tracer! But our result still is not perfect. For example, check out the glass ball on the right. It should be focusing the light onto the ground below it, creating a bright spot (known as a caustic in the industry). But the odds of the ray we shoot out from the ground hitting the light at the top of the room are quite slim - and so we just see a spot the same color as the rest of the ground. So while we've got a lot more realistic already, perfect accuracy will take even more work.
A Note about Implementation
If you don't know or don't care what rasterization is, you can safely skip this page. But it will hopefully be of interest to those curious about how this site works.
This project was entirely inspired by Evan Wallace's similar project. Thanks to Evan for the excellent idea!
This project is implemented in WebGL, which is wrapper around OpenGL, which is an interface that allows you to write code (called “shaders”) that is run on a GPU. OpenGL is written for rasterization, which is fundamentally different from ray tracing. How, then, can we do ray tracing? With carefully constructed shaders!
There are two types of shaders: vertex shaders (called once per vertex of a 3D model) and fragment shaders (called once per pixel of the result image). The vertex shader for this program is very boring - it just passes the position along to the fragment shader. The fragment shader (which you can see the source code for here) is where all the action happens.
The rest of the site is built using React and Next.js, and hosted on Vercel. If any of this is interesting to you, I encourage you to check out the source code and see for yourself how it works!
Try it Yourself
Have fun playing around with the path tracing! You can try out different scenes, or turn global and local illumination on/off.