COMP 238 Assignment 1
Basic Raytracer
[introduction | images
| architecture | lighting
and shadows | user interface | source
code]
Introduction
This is my first time writing a raytracer, but I had several ideas that
I wanted to try out in this implementation.
Some features that I included are:
-
support for sphere and plane primitives
-
complete OpenGL style Phong lighting
-
a ray queue architecture which unrolls the usual raytracing recursive formulation
and provides flexible processing and better memory coherence
-
the standard handling of reflections, refractions, and shadows, including
partial shadows from transparent objects
-
a convenient user interface which allows interactive camera navigation,
changing of resolution, and enabling/disabling of features
Images
|
|
| model1: 10 reflective spheres, and 3 lights, over diffuse
plane. |
model2: A transparent glass sphere in front of a slightly
reflective red sphere. Illuminated by 3 lights. Notice the partial shadow
from the transparent sphere on the left side of the image. |
|
|
| The same scene as above but with the camera moved closer
to the central sphere. |
The same scene as above but with the camera moved
closer. Notice that the result of the refraction depends on the camera
position, as the eye rays are bent as they pass through the glass ball. |
Architecture
When deciding on an architecture to implement I wanted a method that would
allow me to obtain partial results from the raytracing computation. The
problem with the standard recursive model is that the pixel color is known
only when the entire recursive stack of rays generated by the eye ray through
that pixel is resolved. I instead wanted to be able to see the results
of the eye rays hits, then the initial lighting, followed by improvements
in the image as more reflection and refraction rays are resolved. I would
also like to control the number of rays processed at a time, so that in
an interactive application, I can limit the processing time per frame to
an interactive rate, even if only partial results are shown at each frame.
The architecture I chose is a ray queue. This is a large contiguous array
of rays in memory, which I treat as a circular queue. I add rays to be
processed at one end of the queue, and process rays in order from the other.
When I reach the end of the memory space I loop around to the beginning.
Enough memory must be allocated to support the maximum number of rays active
at one time. I have two separate queues; one for object rays, which are
eye rays, reflection rays, and refraction rays, and one for lighting rays.
Each ray stores the index of the pixel in the frame buffer to which it
contributes, and a weighting factor that represents the weight of the rays
contribution to the pixel. The algorithm for processing ray is as follows:
algorithm processRay
find
nearest hit object
if
object exists
{
let w = the weight factor of the ray
apply any immediately available color contribution, weighted by w,
to the destination pixel
add reflection ray to queue with weighting factor w * material.getReflectivity()
add refraction ray to queue with weighting factor w * material.getTransparency()
for each light
{
add lighting ray to lighting queue with weighting factor w
}
}
So we see that every ray contributes a weighted amount directly to the
frame buffer, without having to pass its results back through the recursion.
By breaking up the, ray evaluation process, I can view meaningful partial
results, since the eye rays are processed first, followed later by rays
that have resulted from a larger number of object bounces. It is also very
nice for memory coherency, since all rays are allocated in a contiguous
block of memory and are processed in order.
|
| Figure 1: A diagram of the two circular queues used to maintain the
rays in my raytracer. |
Lighting and Shadows
If a lighting ray reaches the light without encountering an opaque object
it evaluates the lighting at the surface that produced the ray and add
that color to the destination pixel. This means that lighting rays need
to store their point of origin, material properties, and normal vector,
to evaluate lighting. To perform the lighting computations I used code
that I had previously written for the SoftGL
Assignment for COMP236. This code implements the entire OpenGL Phong
lighting model. Shadows result when lighting rays are blocked and no color
contribution is made to the pixel. To handle transparent objects I scale
the lighting ray's weighting factor by the object's transparency value
and if the result is above a threshold allow the ray to continue. Thus
transparent objects lower the light's contribution to the destination pixel
and produces a partial shadow.
User Interface
I used the user interface tool QT to design a simple but functional user
interface for my raytracer. It allows simple camera navigation, enabling
and disabling of lighting, reflections and refractions and control of raytracer
parameters, such as the object epselon and ray contribution cutoff. The
preview window shows the resulting framebuffer and is updated after a prescribed
number of rays have been processed. The previous can show the actual pixels
produced by the raytracer, or can texture map the result onto a full screen
quad. This texture mapping allows you to easily blow up the image and see
pixels as much larger than they really are by simply stretching the window.
|
| The user interface for my raytracer. It has two tab menus; one to control
raytracer parameters and one to control how the image is previewed. |
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]