EECS 487, Winter 2007

Project 5: Ray tracer

ray traced cubes and cylinder


In this assignment you will implement a simple ray tracer.

Your ray tracer will be a command line application that takes two parameters: the name of a scene description file and the name of the output TGA image file. The assignment support code includes a routine for reading in the scene files, and the TGA image output library. The support code provides point light sources, and a few geometric primitives such as a triangle, a ball, and a triangular mesh. The specified primitives are affected by the current modeling transformation, and current material properties. The scene description format is described here.

It is highly recommended that you read chapter 10 of our text book: Fundamentals of Computer Graphics, 2nd Edition, by Peter Shirley et al.  Most of the implementation details are actually provided in the book.

Support Code

For this project, we are not using jot. Instead, the support code consists of a small number of C++ header and source files written by Prof. Guskov for this project.

Copy the project support code to your local machine.  (On windows, you can execute the following commands using cygwin, which we recommend):
  % scp -r .

(Note the 'dot' at the end of line.) The above command overwrites all proj5 files, including any files that you may have already edited. To copy 'old' versions of files while leaving the new untouched (sync-ing operation):
  % rsync -avz .

(Do this from the parent directory containing the proj5 subdirectory.)

Windows users can use set up an sftp session to and grab the files from the above path or, on (CAEN) machines with AFS mount, copy the files from:


(Change K to the correct drive letter, depending on how CAEN space is mounted on your computer.)

Overview of C++ Classes

The support code defines the following classes.

Class RayTracerT  contains most of the ray tracing functionality; the main loop that assigns color values to the pixels of the output image, as well as the ray tracing and shading code. The processing uses the scene object that was previously initialized, as well as some additional options and camera parameters.

Class SceneT provides the interface between the scene and the ray tracer; this includes the intersection testing between the scene and an arbitrary ray (SceneT::Intersect() function), the container of lights, and the background color. The SceneT class also stores pointers to all the geometric primitives (gels) present in the scene.

Class CameraT stores the perspective camera parameters, and provides the ability to initialize them from the input file information.

Class OptionsT contains some additional parameters useful for rendering.

The output image is stored in an XImageT object that provides some basic image manipulation functionality. It is hooked up with the LTGA image buffer, that performs the actual image output into a Targa (TGA) file.

As mentioned above, the scene contains the containers of lights and geometric elements. All lights should be derived from the ILight class, and provide color and sampling information. The ILight::SamplePosition() function should produce a sample on the light, possibly using the index hint provided via ILight::SamplePosition() call. The ILight::Jitters() should return the pointer to the jittered samples array for the area lights, and return NULL pointer if the light is a point light. The PointLightT class is present in the support code, but you will have to implement the area light class.

There are several geometric primitives provided in the code, which are all derived from the IGel class. Its only function is IGel::Intersect() whose purpose is to compute the intersection between a given ray and the primitive. If there is no intersection, the function returns false. Otherwise, the function should return true and compute the nearest non-negative intersection between the ray and the primitive, and fill in the hitinfo_t structure with the information about the intersection point material and geometry (position and normal). Note that the intersection can be affected by the transformation stored in the geometric primitive; see below for more details.

In order to simplify your geometric computations we provide the vector and matrix transformation classes. The class XVecf = XVecT<float> represents a three-component vector, whereas the XVecdimT template provides a vector of arbitrary dimensions. The matrix transform class XFormf stores a 3x4 matrix. This matrix can be applied to points and vectors. When applied to points represented as XVecf objects, the left three columns form a 3x3 transformation matrix and the last column gives an additional translation vector; the result is a transformed 3D point (see XFormT::apply() function). The same matrix when applied to vectors, omits the transation part (see XFormT::applyv() function). The function XFormT::Inverse() returns an inverse transformation. The function XFormT::applytv() applies a transpose of the stored matrix to a vector (this can be useful when dealing with normal vectors). Note that all of the vector and matrix classes are templated on the floating-point type of the stored elements. Since the code uses float data, we typedef the appropriate type names ending with suffix "f": XVecf, XVec2f, XFormf.


Note: Implementation details (and notation) are based on our text book: Fundamentals of Computer Graphics, 2nd Edition, by Peter Shirley et al.

  1. In order to get your first image you will need to add viewing ray generation code (as described in section 10.2 of the book). For this, modify the RayTracerT::TraceAll() function in raytracer.cpp. Once that is done, test the results by running the program. E.g.:

        % proj5 simple.sce out.tga

    When you view the image (out.tga) you should see  a simple scene rendered with ambient lighting.  See camera.h for details of the camera class (needed to compute the rays). Also see below for information about how to view targa (.tga) images.   15 points

  2. Your next step will be to implement the simple shading model given in equation 9.9 (page 196). For this, modify RayTracerT::Shade() in raytracer.cpp. Use the material constants and the light parameters stored in the provided data structures (see material.h and light.h). Only add contributions from a given light if there is no occluder blocking that light from the surface point being shaded. (Use a ray test for this). The shading calculations are similar to what you did in project 2, except we don't deal with spot lights or attenuation due to distance, and we use the halfway vector instead of the reflection vector when computing the specular contribution.    10 points

  3. Add the Phong shading functionality to the mesh class, by using the u,v barycentric coordinates computed by the ray-triangle intersection routine to compute an interpolated normal at the hit point. (See mesh.h: the triangle_t struct stores indices of the 3 vertices, and each vertex stores a normal. Vertex indices are given with respect to the list of vertices stored in the mesh.) Modify the code in MeshT::Intersect().   5 points

  4. Next, add specular reflections, as described in Section 10.5 and 10.6 of the book. Note that here you will be making recursive calls to the ray-coloring function, and hence you should limit the maximum recursion level to the number given in the scene file. Modify the code inside RayTracerT::Shade() and RayTracerT::Trace().    15 points

  5. Add a new geometric primitive to the ray tracer -- the cylinder, represented by the class CylinderT which you should define in cylinder.h. Note that the scene description command for the cylinder does not include any geometric information. Therefore, your primitive should define a canonical cylinder whose axis lies along the world z-axis, with radius 1, top plane at z = 1, and bottom plane at z = 0. As described in Section 10.8 of the book ("instancing"), a canonical cylinder can be arbitrarily positioned, oriented, and resized via a transform. (The same concept should be familiar from projects 3 and 4). Store this transform as member data in the CylinderT class. (The provided class BallT, defined in ball.h, is similar, and can serve as an example.) When loading the scene, store the current transformation in the cylinder. Modify LoadScene() in loadscene.cpp to create a cylinder instance with the current transform applied.   15 points

  6. You may notice that without any post-processing, the images created by your simple ray tracer exhibit aliasing. To address this, add anti-aliasing functionality. It should be turned on by a non-zero value of the "aasample" parameter in the input file. The number specified in the aasample option corresponds to the constant n in the jittered sampling code of section 10.11.1. Modify the function MakeJitterSamples() in raytracer.cpp  10 points

  7. Your next task is to add area lights to the scene to create soft shadows. Section 10.11.2 of the book describes a method for handling area lights. Implement the simple method based on random (not jittered) sampling described in that section. Derive a new AreaLightT class from the ILight interface and implement the virtual AreaLightT::SamplePos() method to return a randomly chosen position on the light. Modify LoadScene() in loadscene.cpp to create an instance of your AreaLightT class when an area light is specified in the scene file.   5 points

  8. When you load a large mesh file in your scene, you may notice that your ray tracer runtime performance drops significantly. In order to make it faster, implement a simple bounding sphere test that checks whether the given ray intersects the bounding sphere of the mesh. Only if this test is passed should it proceed to check all the individual triangles of the mesh. Modify your code in MeshT::ComputeBV() and MeshT::Intersect(). We recommend you use the provided BallT class to represent the bounding sphere. Initialize the bounding sphere in the function MeshT::ComputeBV().   10 points

  9. Create a scene file, render an image, and post it to the phorum in the thread for project 5 images. Your scene can contain mesh files (in obj format) that you get on the internet or elsewhere, but you should arrange your objects and define the viewpoint, scene lights, and material properties to produce an attractive image.   10 points

  10. Hand in a short write-up in text format describing: (1) anything about your implementation that is noteworthy, including high-level descriptions of the strategies you used to implement the required functionality, and (2) feedback on this assignment, including suggestions for how it should be changed next time.   5 points
Viewing the images

The support code code outputs images in targa format (.tga). This format can be viewed directly or converted to other formats by many image viewing programs, e.g. xv and gimp on linux, and Irfanview on windows. The command-line pnm library also is handy for converting 1 or more files. We wrote a simple shell script for you that makes it extra simple to do batch conversions:



   % tga2jpg bunny.tga spheres.tga other.tga

will produce corresponding jpg images: bunny.jpg, spheres.jpg, and other.jpg.

Handing in

Copy all your project source files (no object files or binaries), your Makefile, your write-up (named writeup.txt), and copies of any images you posted to the phorum, to:


This path is accessible from any machine you've logged into using your ITCS ( password. Report problems to ITCS.
Multiple submissions:

Due date

The project is due on April 13, 2007, by 11:59pm. Turning it in 48 hours earlier is worth a 4% bonus; turning it in 24 hours early is worth a 2% bonus.

Last updated: March 30, 2007.