The following 927 words could not be found in the dictionary of 615 words (including 615 LocalSpellingWords) and are highlighted below:

4pi   able   above   absorb   accept   accounted   accumulate   accumulates   accurate   accurately   across   actual   actually   add   Added   added   addition   adjacent   air   algorithm   all   allocates   allow   alone   alter   altered   amount   an   and   angle   angles   appear   appearance   appears   Applied   applied   approaches   approximation   arctan2   area   areas   aren   arm1   arm2   around   artistic   assert   assumed   assuming   at   At   atan2   attached   attachment   away   axis   back   background   Baja   bajaengine   band   bands   Based   based   basis   bbox   be   because   Because   becomes   before   behave   behind   being   believe   Below   below   better   between   bias   bluring   book   bool   Both   bottom   bounces   bouncing   bow   Box   break   brightness   bug   built   bulk   bump   but   by   c0   c1   calculate   Calculate   calculated   calculating   calculation   calculations   camera   can   cannot   capture   case   cases   cause   cell   Cell   cells   Cells   Center   certain   chemical   chosen   class   classes   clear   close   cloud   cloudbehindmultiple   cloudbehindsingle   cloudlike   cloudsingle   colbert   Colbert   color   colored   come   coming   commercial   Compare   comparison   compiler   completely   complex   complexity   Computational   compute   computed   concentration   concept   consider   considerably   considered   considering   const   constant   constructor   consulted   continues   continuing   contribute   contributed   contributes   controlled   convects   converted   convex   correct   correctly   corresponds   cosine   counteract   counts   cout   cplusplus   created   creates   creating   cross   Cross   crosses   cs   Current   current   cut   dataset   date   dead   deal   decide   decided   default   definition   deflected   degrees   demonstrates   Density   density   dependence   dependent   depends   derivation   desirable   detected   Determination   determine   determined   determines   developed   didn   difference   different   differently   difficult   dimensional   direct   direction   directional   Directional   directly   distance   distribution   Distribution1   division   does   don   done   Dot   dot   double   downward   drives   driving   drop   drop1   drop2   drops   Due   due   Dynamics   E10   each   Each   early   easily   edge   edges   editor   edu   effect   effects   efficient   efficiently   either   else   emit   emmision   endl   Engine   enough   enter   entered   entering   enters   entire   equal   equation   Essentially   essentially   evaporates   example   exited   expedient   expense   explained   exported   exporter   extended   extent   extreme   extremely   eye   f0   f1   fabs   face   Face   Faces   faces   facets   factor   Failed   fall   false   familiar   far   farther   Fedkiw   ff   field   file   filename   Final   final   Finaly   find   fix   flexibility   float   floating   flow   flow1   flow2   Fluent   Fluid   fluid   focused   folder   following   follows   foot   For   for   force   format   forms   formulation   found   freeware   frequency   Fresnell   from   ft   function   functions   further   Further   generate   generated   geometry   given   glass   Glories   goes   goodies   Grass   Greenler   Greg   Group   had   half   Halos   having   heavily   Hegarty   hexahedral   high   hit   horizontal   how   hpp   huge   Huibers   Humphreys   id   Id   ideal   if   If   ignored   image   images   imaginary   Implementation   implementation   implemented   Importance   importance   imported   in   In   incident   include   included   incompressible   Indeed   independent   index   indexed   indicates   individual   infinite   infinitely   infinitesample   information   Inside   inside   instead   int   integrator   interact   interacting   intersect   Intersect   intersection   intersections   intersects   into   introduce   inverse   ip   isect   isn   isosurface   Issues   iter   iterations   itersection   its   James   jpg   June   just   large   larger   later   law   layer   leads   leave   length   Length   less   light   like   limit   Lit   lit   located   lock   looked   lookup   loops   lua   made   make   makes   manages   many   map   mapping   Mark   matches   matching   material   materials   Matt   maximize   maximum   maxiter   mayapbrt   measurements   medium   Members   memory   Mesh   mesh   Mesh1   Mesh2   meshes   method   methods   middle   might   millions   minus   mirrored   Misc   mix   model   modeled   Modeling   Models   models   modified   Modified   modify   modulate   modulated   momentum   Montage   more   most   move   Move   moved   much   Multiple   multiple   multiplied   must   nature   Navier   near   need   Nevada   never   next   No   no   noise   none   nore   normal   Normal   normalization   Normalize   normals   Note   noticeable   nt0   num   number   numbers   numerical   obeys   obj   object   objects   obvious   obviously   occur   occurring   octohedral   of   off   on   one   Only   only   onto   operations   Optics   optimization   or   order   orthogonal   Other   other   otherwise   Our   our   out   overrides   Overview   p0   p1   page   parameter   part   particle   particles   past   path   Path   Paul   pbrt   peak   peaks   perfectly   performed   Perlin   Pharr   photographs   photon   physical   physically   Physically   physics   Physics   pi   picture   pictures   pixel   plane   planer   plus   png   Pobj   Point   point   pointers   points   position   possible   practical   precision   prevent   previous   primary   private   probabilities   probability   probabily   probable   problem   problems   proceeds   produce   produced   producing   product   products   program   project   properly   Properties   properties   proportional   proved   provided   Pt   public   purpose   purposes   push   pyramid   Qiqi   quality   radius   Rainbow   rainbow   rainbowangle   rainbows   Rainbows   rand   random   randomized   randomly   rare   rate   Ray   ray   rays   Rays   re   reached   reacting   readable   reads   Realistic   reality   reason   reasons   record   reduces   References   reflected   reflecting   reflection   refracted   refracting   refraction   Region   region   regions   relatively   remained   removed   render   rendered   Rendering   rendering   required   requires   respect   rest   result   resulting   results   retain   return   returns   right   Robert   Robustness   robustness   Rock   Ron   roughly   routines   run   same   sampling   save   scalars   Scalars   Scattering   scattering   scene   script   second   Second   secondary   sect1   section   see   See   seen   send   sent   set   shape   should   show   showed   shown   shows   side   significant   similar   simple   simulate   simulated   sine   Single   single   situation   situations   size   sizes   Sky   slightly   slow   small   smoke   smooth   Snell   snell   so   Softimage   software   solve   solved   solver   source   sources   space   Special   species   specific   Specifically   specified   Spectrum   speed   sphere   spheres   spherical   step   Stokes   stores   storing   straight   straightforward   string   struct   structure   subclass   subdivided   successful   succession   such   sufficient   sufficiently   sun   surface   symmetric   t0   t1   table   taken   takes   tall   target   Team   technique   ten   term   terminated   test   textures   than   that   the   The   them   then   There   there   these   they   things   third   this   This   thousand   thousands   three   through   thus   time   times   to   To   top   total   Total   towards   traced   Tracing   tracing   trajectory   transmittance   transparent   transporting   travel   traveling   true   try   tt   turbulence   tweak   twenty   two   type   types   typical   ucf   unbiased   understood   Unfortunately   uniform   unit   unitd   unstructured   up   use   used   useful   user   Using   using   value   vapor   variance   variation   varies   vector   Vector   velocity   Verification   verify   verify1   verify2   verify3   version   vertex   vertices   very   view   viewer   virtual   visible   Visual   visual   visually   Vol   volume   Volume   volumemesh   Volumes   volumes   volumetric   vs   vt0   vt1   walk   Wang   want   wanted   was   wasn   water   Water   waterdrops   waterfall   Waterfalls   wavefront   wavelength   wavelengths   We   we   weight   weighted   weighting   well   went   were   weren   when   where   whether   which   while   While   whose   wide   will   with   within   work   Work   works   would   written   Yosemite   Yosemite1   Yosemite2   Yosemite3   zero  


Physically Realistic Volume Rendering

Group Members

James Hegarty

Qiqi Wang

Final Image

Added / Modified Code






In this project we rendered a scene based on a physically accurate light scattering model for volumetric materials made up of transparent particles. Special effects such as rainbows can be also rendered with this model. In order to accurately model the distribution of volume materials in complex geometry, a physically based fluid calculation must be performed, resulting in an unstructured mesh. A ray tracing algorithm for unstructured meshes was developed, and light reflection and scattering models from these volume materials was built.

The following pictures are from , I believe they are taken at the Nevada fall in Yosemite. The waterfall and rainbow is the type of things we want to render.

Modeling of volume material distribution (Qiqi Wang)

In order to model the distribution of volume materials accurately, we will use the commercial flow solver Fluent. In the waterfall scene, we use commercial Computational Fluid Dynamics software Fluent 6.2 to calculate the flow of air around the waterfall, as well as the density of water drops in the air. The size of the mesh is 1000 ft by 1000 ft by 6000 ft, the waterfall alone is 100 ft tall. The following pictures demonstrates the mesh generated for the Computational Fluid Dynamics calculation. The first picture shows the surface mesh, the second picture shows the volume mesh on the symmetric section plane.

This is a typical unstructured mesh used for Computational Fluid Dynamics calculation around complex geometry. First of all, tt has huge mesh size variation. The cells inside and near the waterfall is small, and the cells away from it is ten thousand times larger in volume. Also, it has three different types of cells. Inside the waterfall, octohedral cells are used; away from the waterfall, hexahedral cells are used; pyramid cells are used as a layer between octohedral cells and hexahedral cells.

The velocity field and the density of water drops are computed by Fluent 6.2, a most up-to-date commercial Computational Fluid Dynamics software. The small volume in the middle of the step is used to model the water fall, and the rest of the volume is used to model the air. The flow field is calculated using incompressible Navier-Stokes equation with k-e turbulence model. We model the driving force by the flow of water inside the waterfall as a constant downward source term in the momentum equation. This source term drives the flow field in the entire volume. We model the floating water drops in air as transporting and reacting chemical species, whose density is also solved by Fluent 6.2. Inside the waterfall volume, the water drop density is generated in a constant rate. In the rest of the volume, the water drops evaporates into vapor and convects away from the waterfall. The following two images shows the flow field on the symmetric cut plane and the isosurface of 20% of the maximum water drops concentration. Our rainbow image is rendered based on this calculated density field of the water drops.

Ray tracing through unstructured mesh (Qiqi Wang)

In PBRT, we added a file volumemesh.cpp in the volumes folder to deal with unstructured mesh ray tracing. The bulk of this code is four classes (VolumeMesh, Mesh, Cell, Face) and mesh file I/O routines. In our data structure of storing the mesh, each face is an object of the Face class, which stores position of all its vertices, its face normal and two pointers to its adjacent cells; each cell is an object of the Cell class, which stores pointers to all its faces and the water drops density in the cell. The definition of the classes are as follows:

   1 struct Face {
   2         int id;
   3         vector<Point> vertices;
   4         Cell *c0, *c1;
   5         Vector faceNormal;
   6         void CalculateNormal();
   7         bool Intersect( Point o, Vector d, float& t ) const;
   8         Point Center() const;
   9 };
  11 struct Cell {
  12         int id;
  13         vector<Face*> faces;
  14         vector<float> scalars;
  15         bool Intersect( Point o, Vector d, float& t0, float& t1,
  16                         Face*&f0, Face*&f1 ) const;
  17 };

Both Face and Cell class have an Intersect method, which determines whether a given ray intersect with the Face / Cell, and calculate the intersection point(s). To calculate the intersection point of a ray with a face, we just need to compute the intersection of the ray with the face plane, which can be done in 2 dot products and 1 division. Determination of whether the ray intersects with the face is much nore difficult. Our algorithm is to calculate the total angle of the face edges with respect to the ray. If the total angle is plus or minus 2*pi, then the ray intersects with the face; if the total angle is 0, then the ray does not intersect with the face. The total angle cannot be any other value. For robustness reason that will be explained later, we project the face onto a plane that is normal to the ray before calculating the total angle. It takes roughly 4 dot products, 1 cross product, 4 normalization and 1 arctan2 operations for each edge of the face to determine intersection. To calculate the intersection points of a ray with a cell, we test intersection of the ray with all its faces. Only 0 or 2 faces should have an intersection with the ray assuming the cell is convex. The code for the two Intersect methods are:

   1 bool Face::Intersect( Point o, Vector d, float& t ) const {
   2     const float EPS = 0.0001;
   3     // calculate intersection point and t
   4     float total = Dot( d, faceNormal );
   5     float sect1 = Dot( vertices[0] - o, faceNormal );
   6     if (total == 0) return false;
   7     t = sect1 / total;
   8     // calculate total angle around itersection point
   9     float angle = 0;
  10     for (int i=0; (size_t)i<vertices.size(); i++) {
  11          int ip = (i+1) % vertices.size(); // next vertex
  12          Vector arm1 = vertices[i ] - o;
  13          Vector arm2 = vertices[ip] - o;
  14          // project to the foot of the ray
  15          Vector unitd = Normalize(d);
  16          arm1 -= unitd * Dot(arm1,unitd);
  17          arm2 -= unitd * Dot(arm2,unitd);
  18          // right on top
  19          if (arm1.Length() == 0 || arm2.Length() == 0) return true;
  20          // compute the angle of this side
  21          arm1 = Normalize(arm1);
  22          arm2 = Normalize(arm2);
  23          float sine = Dot( Cross(arm1, arm2), unitd );
  24          float cosine = Dot(arm1, arm2);
  25          float side_angle = atan2( sine, cosine );
  26          // add to the total angle
  27          angle += side_angle;
  28     }
  29     // intersect within the face if the total angle is +- 2*PI
  30     if (2*M_PI-EPS < fabs(angle) && fabs(angle) < 2*M_PI+EPS)
  31         return true;
  32     // otherwise the total angle must be 0
  33     else if ( fabs(angle) > EPS ) {
  34         Error("\nTotal angle equal to %f in intersection\n", angle);
  35         cout << "o = " << o << " d = " << d << "\n";
  36         for (int i=0; (size_t)i<vertices.size(); i++) cout << vertices[i] << endl;
  37         return false;
  38     }
  39     return false;
  40 }
  42 bool Cell::Intersect( Point o, Vector d, float& t0, float& t1, Face*&f0, Face*&f1 ) const {
  43     t0 = 1E10; t1 = -1E10;
  44     bool iIntersect = false;
  45     // find intersection with all faces.
  46     for (int i=0; (size_t)i<faces.size(); i++) {
  47         float t;
  48         if (faces[i]->Intersect( o, d, t )) {
  49             iIntersect = true;
  50             if (t <= t0) { t0 = t; f0 = faces[i]; }
  51             if (t >  t1) { t1 = t; f1 = faces[i]; }
  52         }
  53     }
  54     if (iIntersect && t0 == t1) {
  55         if (f0 != f1) {
  56             // decide which face is more on the side of the ray direction
  57             if (rand() > rand()) {
  58                 Face* ff = f0;
  59                 f0 = f1;
  60                 f1 = ff;
  61             }
  62         }
  63         else {
  64             Error("\nt0 == t1 and f0 == f1 in Cell::Intersect, t0=%f, t1=%f\n", t0, t1);
  65             cout << "CellId = " << id << " o " << o << " d " << d << endl;
  66             cout << "f0 = " << f0->id << " f1 = " << f1->id << endl;
  67             return false;
  68         }
  69     }
  70     return iIntersect;
  71 }

The Mesh class manages all the cells and faces. It also manages a 'Current Point' and 'Current Cell' that can be efficiently moved by tracing a ray through the mesh volume. The ray tracing algorithm through the volume mesh is implemented with the MoveTo method of this class.

   1 class Mesh {
   2 public:
   3         Mesh( string filename, int numScalars );
   4         Point CurrentPoint() { return currentPt; }
   5         const Cell* CurrentCell() { return &cells[currentCell]; }
   6         bool MoveTo( Point target, vector<const Cell*>& trajectory, vector<float> t0, vector<float> t1 );
   7         BBox bbox() const { return BBox(p0, p1); }
   8 private:
   9         Point currentPt;
  10         int currentCell;
  11         int numFaces, numCells;
  12         Face* faces;
  13         Cell* cells;
  14         Point p0, p1;
  15         void CalculateBBox();
  16 };
  18 bool Mesh::MoveTo( Point target, vector<const Cell*>& trajectory, vector<float> vt0, vector<float> vt1 )
  19 {
  20     const Cell *c = CurrentCell();
  21     Point  o = currentPt;
  22     Vector d = target - currentPt;
  23     float t0 = 0, t1 = 0;
  24     int iter = 0, maxiter = 10000;
  25     while (c != NULL) {
  26         if (iter++ > maxiter) break;
  27         Face *f0 = NULL, *f1 = NULL;
  28         bool isect = c->Intersect(o,d,t0,t1,f0,f1);
  29         if (!isect) {
  30             Error("Failed to find intersection for cell %d\n", c-cells);
  31             cout << "o = " << o << ", d = " << d << "\n";
  32             return false;
  33         }
  34         assert( f0!=NULL && f1!=NULL );
  35         // save trajectory
  36         trajectory.push_back(c);
  37         vt0.push_back(t0);
  38         vt1.push_back(t1);
  39         if (t1 >= 1) break;
  40         // move to next cell
  41         if (f1->c0 == c) c = f1->c1;
  42         else if (f1->c1 == c) c = f1->c0;
  43         else
  44             Error("Mesh structure error detected in Mesh::MoveTo, Face %d with Cell %d\n", f1-faces, c-cells);
  45     }
  46     if (iter > maxiter) {
  47         Error("Mesh::MoveTo, maximum iterations %d reached.\n", maxiter);
  48         return false;
  49     }
  50     // modify current point
  51     if (c != NULL) {
  52         currentCell = c - cells;
  53         currentPt = target;
  54         return true;
  55     }
  56     // return value indicates whether we are able to move to the point
  57     else return false;
  58 }

The VolumeMesh class is a subclass of DensityRegion. It manages a Mesh class. It overrides the Density function and returns the density of any given point by use Mesh::MoveTo to move the 'Current Point' to the target point and return the density of the 'Current Cell'.

   1 float VolumeMesh::Density(const Point &Pobj) const {
   2         if (!extent.Inside(Pobj)) return 0;
   3         vector<const Cell*> trajectory;
   4         vector<float> t0, t1;
   5         if (mesh->MoveTo( Pobj, trajectory, t0, t1 ))
   6                 return trajectory.back()->scalars[0];
   7                 // return 1;
   8         else return 0;
   9 }

Finaly, the mesh file I/O routines are used in the constructor of Mesh class. It counts the number of cells and faces in the mesh, allocates memory space and reads the file content into our data structure.

Robustness Issues

In rendering each image, millions of rays sent into the volume mesh, and each ray intersect with thousands of faces and cells. Because of the huge number of intersections, extremely rare situations can occur. Rays that goes right through an edge creates two numerical problems: (1) Rays that goes right through an edge of a cell might be found to intersect with 1 or 3 faces of that cell. (2) The two intersect points of a cell can be two close to determine which is farther. Using double precision numbers instead of single precision numbers reduces the number of such numerical problems, but cannot solve them completely. In order to deal with that, we need to tweak the algorithm. First of all, when calculate the total angle around the ray, we project the face into a plane that is orthogonal to the ray. This makes the calculated angle of each edge with respect to a given ray independent of the face. Also, when the two intersection points are close, we use a randomized algorithm to determine which face is farther away in order to prevent dead lock.

Verification of ray tracing algorithm

The following three images are rendered to verify that we have a correct unstructured mesh ray tracing algorithm. In the first image, we set the light emmision factor to be constant within the entire volume. The rendered result matches the shape of the mesh in the previous section. In the second and third image, we set the light emmision factor to be proportional to the water drops density calculated by Fluent 6.2. We see the shape matches the isosurface of waterdrops density shown in the previous section.

Visual Properties of Waterfalls

There were multiple visual effects that we wanted to capture in our waterfall rendering. First, we wanted to create a physically correct Rainbow model. Second, we wanted to simulate the effect of light bouncing around in the volume of water particles to create the cloudlike appearance of the water. Volumes of water particles behave differently from other type of particles because in an ideal situation they are clear, and thus do not either emit or absorb light. We wanted to create a model that correctly accounted for this.

Rainbow Physics

The rainbow implementation proved to be relatively straightforward. We first consulted Greenler's book, Rainbows, Halos, and Glories to determine the physical basis for rainbows. Essentially, as a light ray enters a water drop, it bounces around in the drop multiple times, each time reflecting part of the light back into the drop and refracting part of it out of the drop. The refracted light obeys snell's law, and the reflected light obeys the reflection law. The mix between reflected and refracted light depends on Fresnell's reflection law.

The image below shows an example of a single ray interacting with the drop:

If all the light that enters the drop from one direction is considered, then due to the geometry of a perfectly spherical drop, a visually significant amount of light is focused at 42 degrees, creating the primary rainbow, and a secondary band appears at 51 degrees, creating a secondary rainbow.

The image below shows a record of all of the rays the leave the drop, if all of the rays that enter the bottom half of the sphere are considered. The peak areas where rays accumulate can be seen. Note: in reality, the light distribution would be like this picture, but mirrored across the horizontal axis. We're only considering half the rays in this picture to show the individual peaks.

Snell's law depends on the refraction index of the medium that the light is refracted through. The refraction index is slightly different for different wavelengths of light. Due to this effect, a colored rainbow is seen: the areas of focused light seen above is located at slightly different angles for different wavelengths, thus creating the familiar bands of color.

Because of the nature of how light rays interact with individual water drops, the rainbow is seen by the viewer. At an angle of 42 degrees between the light (assumed to be directional, like the sun) and the viewer, light rays are colored based on the properties of water drops explained above.

Here is an early test image of the rainbow effect:

Rainbow Implementation

As the physics behind rainbows was well understood, all that remained was to alter the the volume integrator to include this effect. A simple sphere ray tracing program was written (attached below) that sent many rays through a sphere. It is straightforward, and is based on the physics above. Huibers created a sufficiently accurate approximation of the wavelength dependence of the refraction index based on physical measurements that we used in our implementation. The script accumulates the light from a large number of rays into a table indexed by the cosine of the angle between the light that entered and the light that exited. It then exported this table in a format readable by a C++ compiler (data.hpp, attached at top of page).

In order to deal with the wavelength dependent effects, the table was exported with the rays traced at 20 different wavelengths. In all rainbow code, calculations were made at these twenty wavelengths, and then converted to XYZ color using the CIE matching functions in the Spectrum class. This is not the most efficient method by far, but it was done to maximize flexibility.

The single scattering integrator was modified (rainbow.cpp, below) to use the rainbow table lookup to modulate the light that went from the light to the eye. To allow flexibility, the integrator was also modified to accept a virtual light position and rainbow brightness in the pbrt scene file. This is not physically correct, but for the purpose of producing our image it was expedient. A version based on the actual angle between the light and camera could easily be created. Directional light source would be required, as other light sources would cause multiple angles between the light and the eye to contribute to the same pixel in the image, bluring the rainbow to the point that it wasn't noticeable.

Code: drop.lua

Note: this script requires the freeware Baja Engine ( to run.

Multiple Scattering with Path Tracing

While our single scattering based integrator was successful at producing rainbows, we wanted to accurately simulate the effect of light entering our volume of particles and interacting the the transparent particles before it reached the eye. To do this, we implemented path tracing based multiple scattering integrator.

Each ray that enters a volume region is simulated to walk through the region at a step distance provided by the user. At each step, the ray randomly either continues straight, or bounces off in a random direction, with a probability determined by the transmittance of the region through which it will travel in the next step. This corresponds to the physical concept of a light ray having traveling through space, and either continuing straight or being deflected based on whether it actually hit a water particle or not. If the particle is deflected, the color that the ray contributes is modulated using the drop rainbow table calculated using the method in the section above, based on the angle between the incident and refracted ray. Because a large number of the possible angles don't contribute a significant amount of light, importance sampling was used here to send more rays in the regions that contributed more light. Importance sampling here was based off the Distribution1D class implemented in infinitesample.cpp. It can be considered to be one dimensional, because the value only varies in one axis; the other axis is chosen randomly with a uniform distribution.

In order for the path tracing to be unbiased, each ray is weighted based on its probability of it actually occurring. Each step is calculated to have a certain probability, and this probability is multiplied by the weight of all past probabilities, because as a ray proceeds through the volume it becomes less probable that this specific ray would actually contribute to the scene. Specifically, each step is weighted by a factor of 1/(4pi)l, where l is the length of the step size. This is based on the derivation for surface path tracing found on page 746 of the PBRT book. For path tracing, a ray is weighted by the inverse of all possible rays could occur. In our case, all rays that could occur at a certain point in the volume come from an area of 1/(surface area of a unit sphere) = 1/4pi. Because we may want to consider spheres of different sizes, we change our weight to be 1/(4pi)l, based on geometry. For example, a sphere of radius 3 would have the probabily of 1/(4pi)^3, because it is essentially the probability of three unit sphere in succession. To further verify this formulation, it is also correct in the limit: as the sphere considered becomes infinitely large, the probability of a ray coming from that distance approaches zero, and as the sphere becomes infinitely small, the probability approaches 1.

The light source is an imaginary light source given as a parameter to the integrator; the implementation doesn't use the built in PBRT light sources as an optimization. The imaginary light is specified using a source point and direction. This forms an infinite planer light, and any path that crosses this plane is considered terminated. This is not physically correct, but it was useful for practical purposes and the implementation could be extended to use PBRT's light sources, at the expense of considerably more variance. The implementation has a maximum path size given by the user in order to prevent infinite loops in extreme cases. Rays that never make it to the light are ignored, and the final weighting of rays is altered so that they don't introduce bias, because rays that don't come from the light obviously aren't significant and should be ignored.

In order to retain the rainbow effect, we added a parameter in the integrator to bias the particles towards bouncing a ray directly at the light. This is not physically correct, but in order for the rainbow effect to the better controlled for artistic reasons, this is desirable.

Below is an image of a smoke dataset produced by Ron Fedkiw rendered with the light to the side using our integrator to show that it works properly. Note: the angle of view isn't wide enough to produce rainbows so they do not appear.

Compare this with the single scattering version:

In the end we decided not to use this technique in our final image. In our image, the light is behind the camera, because this is when rainbow occur. Lit from behind the camera, the multiple scattering volume looked very similar to the image produced by the single scattering integrator with the high frequency information removed. As our volume data didn't include much high frequency information, we didn't find this difference to be significant. In addition, due to the complexity of our volumetric data set, we weren't able to produce an image of sufficient quality to actually see much of a result from the multiple scattering integrator in our final scene, due to the slow speed of path tracing. Indeed, we were never able to produce an image that showed much more than random noise, so none of these images are included. See the comparison of the single scattering vs. multiple scattering lit from behind the camera below:

Single Scattering:

Multiple Scattering:

Further obvious work on this would be to create a photon mapping based version of this integrator and try to use it in the actual image.

Other, Misc Work

The background was modeled in Softimage XSI. As there is no direct path from XSI to PBRT, we exported from XSI to obj format and used the wavefront editor written by Mark Colbert ( Unfortunately, the exporter has a bug that results in the normals not being imported correctly. We weren't able to fix this problem, so we just had PBRT generate default normals for the objects. This leads to visible facets in the object because the objects aren't smooth. To counteract this, the objects were subdivided heavily.

The Sky, Rock, and Grass textures were from photographs. The Water was a glass material with a Perlin noise bump map applied.

Team Work

Qiqi produced the volumetric data set and the algorithm for tracing through an unstructured mesh.

James produced the rainbow and multiple scattering code, in addition to the background and textures.


Greenler, Robert. Rainbows, Halos, and Glories.

Huibers, Paul. Models for the wavelength dependence of the index of refraction of water. Applied Optics, Vol. 36 No. 16, June 1997.

Humphreys, Greg and Pharr, Matt. Physically Based Rendering.