For me, the most interesting part of this project was the volumetric path tracing. My implementation was based on the simple volume integrator from
PBRT, which allows volumes to scatter different wavelengths of light with different probabilities. While. in the image on the left, the volume itself has no "color," the scattering properties of the volume create an interesting effect. Red wavelengths of light are more likely to scatter than their blue/green counterparts; as a result, red wavelengths can't travel far, making points away from the light appear blue.
While a typical simple GPU-accelerated path tracer will perform one sample per kernel invocation, this strategy becomes impractical when rendering scattering media. In a typical scene with a handful of objects and lights, a path of light might scatter 3-5 times before reaching a light source; however, even in simple scenes like that on the left, a path of light might scatter 30-60 times before reaching a light source, which is an impractical number of rays to trace per kernel invocation. To circumvent this while keeping the overall code simple, my renderer is a mix between this simple strategy and a full-blown wavefront path tracer. Instead of each kernel invocation computing one sample per pixel, an invocation extends each active path by one step. This allows a path to be computed over multiple invocations, allowing it to scatter more and improving GPU occupancy.