Asg 2: Ray Traced Transmission

Objectives

To implement transmission rays, rendering transparent objects

Assignment

  1. Using your parallelized ray tracer, rewrite your ray_t::trace() routine to spawn a transmission ray for any object intersected that has alpha > 0.
  2. Transmission rays, unlike reflection rays which bounce off the object surface, cross into the object, refracting as they do so, according to Snell's Law, which states that n1 sin(θ1) = n2 sin(θ2) where n1 is the index of refraction of medium 1 w.r.t. vacuum and n2 is the index of refraction of medium 2 w.r.t. vacuum. An index of refraction, or ior = 1.000293 models air, while an index of refraction of 1.337 models the vitreous humor—the liquid inside the human eye (see the list of refractive indices for others).
  3. The angle of refraction of the transmission ray, assuming incoming normalized direction u (the ray direction), is calcuated as:
    (n1/n2)(u - cosθ1N) - cosθ2N
    	
    where Note that you can set n1 = 1.000293 and never change it if we assume the scene is immersed in air.
  4. A transmission ray is spawned in much the same way as a reflection ray, except that it is only done if the intersected object's alpha value is > 0 (in other words only when the ray encounters a transparent object).
  5. Take care to refract the ray about a properly signed surface normal, i.e., if u·N < 0 (the incoming ray direction u points into the object) then refract the ray about N with n2 otherwise (the incoming ray direction u points out of the object) refract the ray about -N with 1/n2.
  6. Blend the resultant color via the traditional compositing (linear interpolation) equation:
    color  = ((1.0 - alpha) * color) + (alpha * transmitted_color);
    	
    where transmitted_color is the transparent color set by the transmission ray (don't forget to clamp the color to [0,1] range).
  7. With the model.txt given below, your ray tracer should now render the image below, of a transparent sphere with the same refractive index as the human eye's vitreous humor.

    no TIR, no t1

    t1 but no TIR (oops!)

    TIR but no t1

    no reflection?
    (doesn't seem right, I forget how I got this, sorry)

    final image

    with Schlick's approximation

Details

  1. Rewrite your material_t to handle processing material properties alpha (a single float value) and ior (also a single float value). Make sure that your material_t class provides queries that return these values, e.g., getalpha() and getior(), which will behave similarly to getspec() for example, but will return single float values instead of vec_t vectors. Before doing anything on this assignment, make sure that you can successfully process the input model.txt file and be able to write it out.
  2. The old model_t::find_closest() routine won't work anymore because its initial design assumed that a ray would hit any given object only once (and reflect). Clearly this won't work for transparent objects that have volume, through which the transparent ray needs to exit, once it has penetrated it. This means, particularly for transparent spheres, that the ray will hit the sphere twice, and so the find_closest() routine needs to be rewritten:
    1. initialize the local variable closest_dist to INFINITY instead of to -1
    2. remove the obj==last_obj || evaluation from the initial call to obj->hits()—the current object should only be skipped if the returned dist < 0
    3. only record the closest distance, object, hit point, and normal if (0.00001 < dist) && (dist < closest_dist) (the distance to the intersection point must be non-zero)
  3. Because the refracted ray is refracted either about N or -N, it is convenient to write a
    	vec_t::refract(const vec_t& n, double n_t) const
    	
    function that refracts the given (e.g., this) vector about n with n2 = nt. Writing a
    	vec_t::reflect(const vec_t& n) const
    	
    function for reflection is also a good idea, where the resultant reflection vector is u - 2.0(u·N)N.
  4. When calculating refraction, if the expression inside the radical is less than 0, i.e., if 1 - ((n1/n2)2(1 - (u·N)2)) < 0 then the refraction angle is critical and the ray does not refract, but reflects internally. This is known as total internal reflection, or TIR. When this happens, simply reflect the ray about N.
  5. When a ray enters a transparent sphere, the sphere's hits routine should now calculate both roots of the quadratic equation and return the smaller of the two, unless the smaller of the two is less than 0.00001 in which case the larger should be returned. Instead of just calculating t now calculate t0 = (-b - sqrt(d))/(2.0*a); t1 = (-b + sqrt(d))/(2.0*a); and return t0 if it is smaller than t1 unless it is behind the ray position (i.e., unless t0 is negative, but due to round-off error we test whether it smaller than 0.00001).
    Note that if t1 is returned, then we are exiting the sphere.
  6. The above calculation of t1 can in turn lead to infinite loops where rays can get stuck in a region close to the sphere surface. To prevent this, rewrite ray_t::trace() to include an additional integer argument, bounce which you test immediately as you enter the routine—if it is larger than say 10, return (prune this ray as it has already bounced too many times).

Optional

  1. If you implement both vec_t::reflect() and vec_t::refract() functions, then you can try implementing Pete Shirley's solution (see §10.7) which uses Schlick's approximation to the Fresnel equations for light transport (both reflection and refraction of light may occur at the surface interface where refractive indices differ). In this case two rays are spawned when intersecting a transparent surface: a reflection and a transmission ray. The result is linearly interpolated via the R parameter:
    	R = R0 + (1-R0)(1-cosθ)5
    	
    Blending is then done by using R as the linear interpolant between the reflected and transmitted colors, then this resultant color is used just as the transmitted color was blended with the surface color above.

Input

  1. The model.txt file used to generate the above image is:
    camera cam1
    {     
       pixeldim 640 480
       worlddim 8   6
       viewpoint 4 3 6
    }   
    
    light centerback
    {   
       location 3 4 -1
       emissivity 1 1 1     
    }                         
            
    light backleft          
    {       
       location 1 1 0
       emissivity 1 1 1
    }
    
    light topright
    {
       location 6 4 1
       emissivity 1 1 1     
    }       
            
    material green
    {     
       ambient 0 5 0
    }
          
    material red
    {     
       ambient 5 0 0
       diffuse  .7 .7 .7
      specular  .3 .3 .3
    }     
    
    material blue
    {     
       ambient 0 0 5
       diffuse  .2 .2 .2
      specular  .8 .8 .8
    }     
    
    material cyan
    {
       ambient 0 4 5
       diffuse  .2 .2 .2
      specular  .8 .8 .8
        alpha  .4
          ior  1.337
    }
    
    material yellow
    {
       ambient  5 4 0
       diffuse  .7 .7 .7
      specular  .3 .3 .3
    }
    
    plane leftwall
    {
       material green
       normal 3 0 1
       point  0 0 0
    }
    
    plane rightwall
    {
       material yellow
       normal -3 0 1
       point   8 0 0
    }
    
    material gray
    {
       ambient 2 2 2
    }
    
    plane floor
    {
       material gray
       normal 0 1 0
       point  0 -0.2 0
    }
    
    sphere upperright
    {
       material red
       center 6 4 -2
       radius 2
    }
    
    sphere central
    {
       material blue
       center 2 2 -2
       radius 1.3
    }
    
    sphere vitreous
    {
       material cyan
       center 4.3 2.3 -1
       radius 2.3
    }
    	

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. Lab description
    4. Brief solution description (e.g., program design, description of algorithm, etc., however appropriate).
    5. Lessons learned, identified interesting features of your program
    6. 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