- Published on
Light Probes
- Authors

- Name
- Jacob Bekele Jansson

For one of my projects at The Game Assembly, I implemented global illumination using pre-computed irradiance volumes, or grids of light probes. I enjoy graphics programming, and I wanted to gain a better understanding of how more advanced lighting techniques are implemented, which was my main motivation for this project. My implementation, though, is probably not how you would do it in the real world.
How it Works
As briefly mentioned, the GI is implemented by using regular grids of light probes. A probe in this case consists of two sets of 3rd order spherical harmonic coefficients. One for light information and one for depth information.
When generating the grid of probes, I capture the scene as a cube-map at each vertex in the grid. Then I project them into the SH of the current probe being processed. This works by rendering the scene six times (once per face) for each probe, copying the results into a cube-map, and passing them off to a compute shader which projects the results into the SH. I do this for the world position texture of the g-buffer as well to get the linear depth from the probe to the surrounding surfaces.
Lighting the Scene
During the main lighting pass, I get the three volumes affecting the shading surface the most and sample each one. During this step I also reduce the contribution of volumes if they are occluded, and I also weigh the volumes by their distance to the shading surface. Occlusion of a volume is determined by the dot product of the surface normal and the direction from the closet point on the volume to the shading surface.
Sampling the Probes
When sampling the volumes, I retrieve the eight probes closest to the shading surface and perform trilinear interpolation between them. During this process I also attempt to weigh out the probes that aren’t in view of the shading surface. This is done by computing the dot product between the surface normal and the direction to the current probe being sampled and using the depth values baked into the probe. With the depth values I can use Chebyshev visibility approximation to test whether a probe is likely to be occluded.
What I would like to improve
I still have some issues I want to solve and improvements I want to make. The main ones being minimizing light leaks, improving performance, and improving probe placement.
Light Leak
Even with tangent plane tests and the depth testing I still didn’t manage to eliminate all light leaks. They are still present in the scenes and most noticeable in corners and on the borders between volumes. I believe one of the main causes of this is the loss of precision I incur by storing my depth values as spherical harmonics coefficients, and on top of that I blur the SH values even further to avoid ringing. One technique I read about which could potentially solve this problem is octahedral mapping, which involves mapping 3d points on a unit sphere to 2d points on a unit square. This would allow for more precise depth values, and I wouldn’t have to deal with ringing artifacts.
Performance
Right now, finding the right volumes to sample involves iterating over all the volumes in the scene and calculating weights for each one, and this happens for every pixel. This is quite slow, and the more volumes there are, the worse it gets. We used these probes for one of our game projects at TGA and when profiling the shaders, it took around 2 ms for the full-screen lighting shader to complete, a little over 1 ms with optimized shaders. To alleviate this, I would like to introduce an acceleration structure to allow me to quickly look up which volumes are closest to the current pixel.
Probe Placement
One issue with the grid approach that was sometimes hard to avoid was probes ending up inside objects in the scene, which causes some artifacts and light leaks. And since the volumes are axis-aligned, areas with sloped surfaces or staircases became especially tricky. The second issue could be somewhat resolved by implementing the volumes as OBBs instead of AABBs, but I would also like to implement some simple intersection tests. This would allow me to move probes out of objects when generating the grids.
Pictures



