Asg 6: photon mapper

Objectives

To implement a photon mapper: store collected photons in a kd-tree, then run your ray tracer to accumulate and render caustics

Assignment

  1. Once photons have been shot into the scene and they are stored in a std::vector of pointers to photons, the next step is to scale each of the "stuck" photons' power by 1/n where n is the number of stuck photons—note that it may not be the same number as the number of photons shot out as some of these did not stick.
    (This can be done in main().)
  2. After photon power normalization, find the smallest and largest photons in terms of their positions, and then use these as the min and max bounds to insert the std::vector of pointers to photons into your kd-tree.
    (This can be done in main().)
  3. Once the kd-tree has been created, then the ray_t::trace() routine is augmented such that it accepts a reference to the kd-tree as an additional argument, and then as a final step in the routine, the k nearest photons are collected at each ray's hit point, and an estimate or the radiance at that point is calculated—this is very similar to calculating the specular reflection at the hit point. The key observation here is that the radiance estimate depends on the density of photons at the hit point—if there are a lot of tightly packed photons (e.g., as you would expect at the location of a caustic), then the radius r returned by the k-nearest neighbor kd-tree query will be smaller than at hit points where photon density is smaller. (This can be done in ray_t::trace().)
  4. For manageable render times, shoot 20,000 global and 5,000 caustic photons into the scene (this can be done in model_t::shoot()), and at each hit point, sample only 50 photons (this can be done in ray_t::trace()).
  5. Because the kd-tree knn query accepts as arguments a photon, the knearest std::vector<photon_t* > array of nearest photons, r, and k, "masquerade" the hit point as a photon by temporarily constructing a photon with the hit point as its position, e.g.,
        photon_t *query = new photon_t(hit,vec_t(0.0,0.0,0.0),vec_t(0.0,0.0,0.0));
    	
    then call the kd-tree knn query:
        kdtree.knn(query,knearest,radius,50);
    	
    which will return an array of k nearest photons and r, the distance the kth one. The idea is to calculate all the radiance flux from all gathered photons.
    (This can be done in ray_t::trace().)
  6. To compute flux, for each of the k nearest photons: (This can be done in ray_t::trace().)
  7. Once the flux has been computed, scale it by 1/(πr2) and add it to the resultant color, remembering to clamp the color to (0.0,1.0) as with all the other color contributions. (This can be done in ray_t::trace().)

Results

    Cornell box, one light
    20,000 photons per light, 50 photons sampled
    5 ray bounces, 10 photon bounces
    4 cores: 93.6 s, 8 cores: 56.3 s

    Cornell box, one light, cone filter
    20,000 photons per light, 50 photons sampled
    5 ray bounces, 10 photon bounces
    4 cores: 93.6 s

    Cornell box, one light, Gaussian filter
    20,000 photons per light, 50 photons sampled
    5 ray bounces, 10 photon bounces
    4 cores: 93.6 s

Revised Results

One of this semester's students noted that the genrand_hemisphere function emitted photons in all directions of the sphere. This was indeed true and incorrect, not to mention inefficient. Reconsidering of the code prompted the following revisions.
  1. To get the genrand_hemisphere function to emit photons in a hemispherical direction, all that was needed was the normal at the surface. Given the normal vector as argument to this function, a quick fix can be made to its return statment so that it returns
    	return (out.dot(normal) >= 0) ? out : -out;
    	
    Note that to use this with the light, just assume the light points downward.
  2. Re-reading Wann Jensen's book prompted another change, in that the caustic photon should only stick to a surface once it has passed through a transparent one. One can easily check the bounce counter to ensure that this is so:
    	return bounce ? true : false;
    	
  3. Finally, as the contribution of the global photons seemed to be too large in relation to that of the caustic photons, in comparison to the Cornell box images found on Wann Jensen's webpage, the code was re-written to allow setting of the power of photons, and caustic photons were instantiated with power of 100 while global photons were instantiated with a power of 1.

    Cornell box, one light
    2,000 global, 500 caustic photons
    10 photons sampled
    5 ray bounces, 10 photon bounces
    8 cores: 5.6 s

    Cornell box, one light, cone filter
    2,000 global, 500 caustic photons
    10 photons sampled
    5 ray bounces, 10 photon bounces
    8 cores: 5.6 s

    Cornell box, one light, Gaussian filter
    2,000 global, 500 caustic photons
    10 photons sampled
    5 ray bounces, 10 photon bounces
    8 cores: 5.6 s

    Cornell box, one light
    20,000 global, 5,000 caustic photons
    100 photons sampled
    5 ray bounces, 10 photon bounces
    8 cores: 112.8 s

    Cornell box, one light, cone filter
    20,000 global, 5,000 caustic photons
    100 photons sampled
    5 ray bounces, 10 photon bounces
    8 cores: 112.1 s

    Cornell box, one light, Gaussian filter
    20,000 global, 5,000 caustic photons
    100 photons sampled
    5 ray bounces, 10 photon bounces
    8 cores: 112.2 s

Suggestions

  1. Three photon_t class public member functions/operators are very important to get everything working properly:
  2. For the kd-tree to work properly, besides the above, your photon_t interface should also define a photon_c functor, or function object which has as its only public member function the overloaded bool operator() operator that returns true when two photon_t objects are compared with operator<, e.g.,
      bool operator()(const photon_t& p1, const photon_t& p2) const
        { return(p1[axis] < p2[axis]); }
    	
    where axis is photon_c's private integer data member that is optionally initialized to 0 upon construction:
      public:
      photon_c(int inaxis=0) : axis(inaxis) {};
    
      private:
      int axis;
    	
    so that the kd-tree can use this funcor to order photons during insertion and queries.
  3. For debugging purposes, you should edit the model file you're using to reduce the size of the image you are trying to generate: for "quick-and-dirty" debugging, use something very small like 64 x 48 to see if you're getting any kind of caustic effect at all...if it looks right, try 320 x 240 before going on to the our "full-size" 640 x 480 image.

Optional

  1. Once you have all of the above working and are able to produce unfiltered images as above, try either of the cone or Gaussian filters to smooth the somewhat cloudy appearance of the radiance estimate: and each of wpc and wpg are just used to scale the power of each of the photons that is used in the flux computation

Turn in

Turn in all of your code, in one tar.gz archive of your asg##/ directory, including:
  1. A README file containing
    1. Course id--section no
    2. Name
    3. Brief solution description (e.g., program design, description of algorithm, etc., however appropriate).
    4. Lessons learned, identified interesting features of your program
    5. Any special usage instructions
  2. Makefile
  3. source code (.h headers and .cpp source)
  4. object code (do a make clean before tar)

How to hand in

See handin notes