Source code can be found here.
Our goal was to create a realistic rendering of Gummy Bears. Gummy products have a unique, translucent, semi-transparent appearance which is difficult to describe yet easy to identify. If incorrectly represented, this material could easily appear to be either too plastic or too liquid. Perfecting this representation requires careful attention to the reflecting of the light within the bears and will also require us to buy many bags of Gummy Bears! =-D
To achieve this effect, we would like to implement the techniques described in Henrik Wann Jansen's paper "Efficient Simulation of Light Transport in Scenes with Participating Media using Photon Maps." Gummy Bears are a kind of participating media that will require light transport techniques not provided by PBRT, and photon mapping is very good at handling participating media in a manner that increases efficiency and reduces noise. We are also looking at the techniques described in Henrik's book "Realistic Image Synthesis Using Photon Mapping" regarding caustics photon mapping.
At first we had hoped to model the gummy bear ourselves, so we spent a good deal of time working on this in Maya. Ultimately, we ended up purchasing a model online, as the modeling skills of graphics professionals proved to be better than our own. The model was a triangle mesh with approximately 1,700 vertices. We put the geometry in a separate pbrt file so it could be easily included
We added a new parameter to pbrt which allowed us to specify whether or not an object should be treated as a participating media, in which case a volumetric photon map will be created for it. This parameter included values for the medium's scattering coefficient and absorption coefficient, as well as a parameter which affects the granularity of the ray walking algorithm.
One of the first major hurdles we had to overcome was finding a way to determine when the photon rays intersected the gummy bears. Our first plan was to add a virtual method to the shape class. This method, called "IsVolumetric", would simply return false for every shape except the triangle mesh. However, if we added even a single line of code to the shape.h file, the pbrt default photon mapping would break down. After nearly two entire nights of working on this problem, we were unable to make any headway, and instead chose to use a less elegant, less efficient global array of pointers to all of the gummy bears. At its worst, our code iterates through this array multiple times per ray cast, but because there are relatively few bears in each scene, we felt that this extra cost was acceptable.
Another one of the challenges of this program was understanding how to write a pbrt scene file and set up appropriate values for the surface integrator, the sampler, the pixel filter, the lights and the material properties. After a great deal of experimentation, many renderings of black scenes, and reference to the Internet and previous assignments for examples, we ultimately created a scene file similar to the Cornell boxes that we used for most of our testing. This scene was composed of matte walls to act as our diffuse surfaces, and a gummy bear to act as our specular surface (and produce the caustic photons.) After some tests, we determined that using a sphere as an area light source seemed to produce the best results, and we also determined that the most gummy-like material provided in pbrt is glass with a 1.3 index of refraction and equal values for color reflectance and transmission. Once our volumetric photon map integrator was added, the images looked less like glass and more like gummy bears.
As a visual aid to help us better understand exactly where our photons were being stored, we wrote a program in OpenGL called the Photon Visualizer. As we were generating photons maps, we would output the coordinates of each stored photon to a file. Then we would read these coordinates in and display them as dots in a 3d viewer. The viewer had a number of control mechanisms, allowing us to rotate, zoom, and translate our viewpoint. We could view the photons in each of the maps separately (direct, caustic, and volumetric) or all together. Here is a screenshot of this viewer:
Once our pbrt scene was completed, we were able to move on to the implementation of the Jensen algorithm. We began by modifying the photon gathering portion of the default photonmap.cpp. For areas outside the gummy bears, the photon mapping proceeds as normal. For areas inside the gummy, our modified code enters into our new ray walking code.
The new ray walking code runs through a basic loop as follows:
The new volumetric map is populated at the same time as the caustic, indirect, and direct maps. When a sufficient number of photons are collected, the volumetric photons are stored in a kd-tree to allow for faster lookup in the future.Calculating results from the map
After we populated the volumetric photon map, the next step was to modify the integrator. We used a simplified method to approximate the contribution of the volumetric photons. When a ray passed into the Li function, we allow the default pbrt implementation to calculate the base L value. We then check to see if the ray intersects any of the gummy bears.
If the ray does intersect the gummy bears, we enter into a new algorithm which modifies the L value. We walk along the ray, using the same step size as was used to originally shoot the photons. We count the number of photons that lie within a specified distance from the ray. We then compare the number of photons to a standardized value (the "volumetric equalizer" specified in the scene file), and based on this number, decide if the L value should be modified. Our final algorithm skews these modifications towards the brighter side of the spectrum, as that provided a more visually satisfying result.
To create our final scene, we generated 24 non-intersecting gummy bears scattered on a texture mapped matte plane at semi-random intervals. The area light sphere is directly above the scene slightly to the left of the center. We used 100,000 caustic photons, 400,000 direct photons, and 2,000 volumetric photons per object. The scene took approximately 20 minutes to render with a 400 by 400 pixel resolution.