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
-
support for triangle primitives
-
triangle texturing modeled after the "GL_BLEND" texture blending mode in
OpenGL
-
soft shadows
-
glossy reflections
-
anti-aliasing
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]