← All work

Per-Voxel-Face Irradiance Caching

Real-time diffuse GI cached on every exposed voxel face.

When
Jun – Jul 2026
Stack
Rust · Vulkan
Captures
9

Real-time global illumination for voxel worlds using world-space caching. Every voxel face has its own irradiance value, and a rays each frame refine it, so bounce light builds up over time instead of being recomputed from scratch.

How it works

Each voxel face has an RGB irradiance and sample count packed into 8 btyes of f16. Every frame rays are cast from the camera across the scene. Wherever a ray lands, it estimates the light arriving at that face via a shadow ray toward the sun and one bounce ray. It blends these calculations into the face's running average, capped at N samples. Bounce rays read the cache from other faces they hit, so multi-bounce lighting costs the same as a single bounce. Drawing the frame uses the DDA technique used in my other projects such that each pixel is a single ray color lookup

In the demo video I use a relatively low value of 70,000 samples a frame - which is about one per 50 pixels. Since rays's are cast from the camera, you get a natural fade of sampling where close faces get several samples, and distant ones may go many frames between samples

How fast does it recover?

To see how dynamic the cache is, I swung the sun 90° in a single frame and measured error against a fully converged cache for two different history caps.

Error vs. frames after the sun moves 90°, for N = 8 and N = 64
Relative L1 error after the sun swings 90° in one frame (one viewpoint, one event).

N = 8 reacts fast but converges to 6.6% error and flickers about six times as much as N = 64. N = 64 converges down to only 2.8%, but needs over 100 frames to close 95% of the gap. The recovery also isn't exponential: it closed the first half of the error in 9 frames, then took 50 more to go from 90% to 95%.

My hypothesis is that the slow convergance tail is due to faces that rarely get sampled or faces lit only by bounce light, which can't settle until the faces lighting them do.

This measurement led to my research proposal

Notes

I designed and wrote the cache layout and update logic myself; I used Claude Code for Rust/Vulkan plumbing and debugging.