Participating Media …
May 4th, 2010 by Bramz… are really good fun. And a huge performance killer too. Especially if you enable in-scattering on your final gather rays. Oh boy, that really hurts! Still playing around with the Sponza atrium, I wanted to do a render of sunlight being casted through the arches on the first floor.
Anyway, to cut things short, here’s the final render. It only took a ridiculous long time to compute (12 hours on a quadcore). Click on the image for full resolution. Tonemapping has been done in photoshop. Model & textures courtesy of Marko Dabrovic.
Adding single scattering was easy enough. Just ray march or sample some points along your camera rays and add light source contributions to each of them using a suitable phase function. Don’t forget to add some attenuation – Beer’s law comes to mind – to your camera rays, light rays and photon paths. Basically on anything that travels through your medium. As phase function, I’ve chosen to use the widely used Henyey-Greenstein model. It isn’t particularly the fastest one around, but as I don’t have a good profiler (yet ;), I don’t really know its impact on the render time anyway.
Multiple scattering is somewhat of a different beast, though it’s not too difficult either. In the photon mapping world, it means augmenting the renderer with a volumetric photon map that records all scatter events during the photon trace pass. For each photon that travels through a medium, you sample a possible scattering location. In case of a homogenous medium, this is as simple as feeding a uniform variate in the inverse cumulative of the exponential distribution, which can yield a travel distance from zero to infinity. If it is nearer than the first surface intersection, you store the photon and sample the phase function for a new direction. Some more details can be found in Lafortune and Willems (1996) and Raab, Seibert and Keller (2008).
In the rendering pass, I used the beam radiance estimate from Jarosz, Zwicker and Jensen (2008) to collect the in-scattered light from the volumetric map on the camera and final gather rays. This method represents the volumetric photons as spheres, and all photons intersected by a ray contribute to it. In case of camera rays, I ignore “single scattered” photons, as I account for single scattering seperately.
Because this operation weights very heavy on the final gather step, I stochastically skip the in-scattering estimate for a number of gather rays. For each ray, a uniform variate is compared to a quality factor. Only if it is lower, volumetric photons are collected. The result is divided by the quality factor to compensate. That way I can trade speed for accuracy.
May 9th, 2010 at 8:20 pm
Hey bramz,
That looks really good. I can’t see your photon map at all. How many photons did you use? Since you are using a final gather, I take it you are doing photon mapping on surfaces where those rays hit?
May 9th, 2010 at 8:21 pm
Also how long did photon emission take?
May 9th, 2010 at 9:16 pm
Hi Kevin,
Thanks for the kind words! I believe I’ve used around 400k surface photons in this one. Plus the ones in the volumetric photon map. The final gathering is the reason why you don’t see the “blobs” photon mapping usually suffers from. It is exactly as you said: instead of doing a direct photon lookup, you emit a number of gather rays (in my case, I’ve used 64 per camera ray, 9 camera rays per pixel), and you do a lookup where they hit. It takes much longer to compute though, and it’s a bit noisier, but the result is much nicer at the end. You’d also precompute irradiance values to speed it up. More details can be found in http://www.seanet.com/~myandper/jgt99.pdf and http://www.pbrt.org/plugins/exphotonmap.pdf
Photon emission took half an hour, because I was emitting them from a hemisphere, and a lot of photons were lost because they hit either nothing or the outside of the scene. So I did send several million photons, to have only 400k left. By now, I’ve implemented Light Portals so that I can define the open rectangle on top of the scene as origin of the photons. Now I only need to emit 200k photons to get the same 400k map, in only one minute! Much better =)