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:

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]