COMP 238 Assignment 2

Stochastic Raytracer

[previous version | introduction | triangles and texturing | soft shadows | glossy reflections | anti-aliasing | source code]

Introduction

This raytracer builds off of my previous raytracer, done as a first assignment for this course. The features that I added in this implementation are

Triangles and Texturing

To add triangle primitives, I simply added a new subclass of my Primitive class for triangles and had it implement a fast ray triangle intersection. The intersection technique that I used was taken from the paper:
"Fast, Minimum Storage Ray-Triangle Intersection", Tomas Möller, Ben Trumbore,  journal of graphics tools, vol. 2, no. 1, pp. 21-28, 1997.
The main advantage of this intersection method, other than its speed, is that it directly computes the barycentric coordinates of the intersection point of the ray with the triangle as it performs the intersection test. This means that using barycentric coordinates to interpolate texture coordinates and normal vectors comes with almost no extra cost.

To implement texturing I had to extend my ray/object intersection routine to allow it to return the texture coordinates at the point of intersection. I then used a bilinear interpolation routine to interpolate the color in the texture at those coordinates. This texture color is then used to scale the surface's ambient color, as well as the contributions of the reflection and lighting rays generated from that intersection. The reason that I use texture color as a scale factor for these rays is that I wanted to mimic the OpenGL texture blending operation, in which the texture scales the fragment color. The result is that the texture detail is visible under diffuse, specular and reflected light. The scaling of ambient color by the texture adds the effect of having a small amount of texture detail visible even in completely shadowed areas, where only ambient light is incident on the surface.  These effects can be seen in the image below.
 
 
 

Image 1: The cols238 scene. This shows triangle models and texture mapping along with reflections, refractions and shadows. Note how the texture detail can be visible in the dark shadows of the pillars on the floor, as well as under the reflections on the floor. There was no anti-aliasing, soft shadowing or blurry reflections enabled for this image.

 

Soft Shadows

Soft shadows were created by extending the basic point light in my previous implementation to an extended light source. My implementation for extended lights creates a spherical light source with a specified radius. The plane perpendicular to the vector from which the light is viewed is sampled a prescribed number of times using a grid perturbation scheme. For example, if I want 9 samples on the light, I  generate a 3 x 3 grid of the required radius and randomly sample once in every grid square. This gives random locations on the light which can be used as targets for lighting rays. Because the samples are taken from a perturbation of a grid we are guaranteed to have one sample in every grid cell, ensuring a more uniform random distribution of samples. The lighting rays are weighted evenly by 1/numberOfSamples, so in areas where not all of the lighting rays reach the light, the result is a partial shadow.
 
 
 
Image 2: The reflective sphere scene with one extended light source. I use a jittered grid scheme with 9 samples averaged together to produce the soft shadow on the left. 

 

Glossy Reflections

Once I had implemented the grid based jittering scheme described above, it was quite trivial to achieve glossy reflections. I simply created a procedure _PerturbVector which takes in a vector, grid resolution, and grid radius, and returns a list of vectors generated by randomly perturbing the input vector in cells in a grid perpendicular to the input vector. To achieve glossy reflections I simply used the _PerturbVector routine with the reflected vector. The grid radius determined how much variation there would be in the perturbed rays. I used a radius of 0.1, or 10% of the length of the reflection ray to generate the image below.  Just like for soft shadows each sample ray is weighted by 1/numSamples so that the result is an average of the colors seen by the reflection samples.
 
 
Image 3: The reflective balls scene with glossy reflections. I used 9 perturbed reflection vectors, generated by sampling on a grid perpendicular to the true reflection vector. The size of the grid gives a 10% variance in the reflection samples.

Anti-Aliasing

To achieve anti-aliasing I used the same method that I used to produce glossy reflections. The _PerturbVector routine was called for each or the original eye rays, to generate a grid based random sampling of the pixel area. The resulting samples were then weighted evenly and released as eye rays. The image below on the right was produced with 9 extra samples per pixel. The image below on the right was produced without anti-aliasing and is provided for comparison
 
 
Image 4a: The reflective ball scene without anti-aliasing. Image 4b: The reflective ball scene with anti-aliasing. Each pixel is sampled 9 times using a jittered grid. 
You can see the results of anti-aliasing most notably on the edges of objects, shadows and reflections, where the image on the left shows jagged edges, while the image on the right does not.

Source Code

I've included all of the code that I have written for this project in a .zip file. This is intended to illustrate my work, not provide complete source code for others to compile. The code relies on several external libraries that I have not included here, including QT, QGLVU, and a Bison/Flex parser provided by the instructor.

[source code]