EECS 487, Fall 2007

Project 4: Ray tracer

The project is due on Tuesday, December 11th, 2007 at 11:59pm.

Turning it in 48 hours earlier is worth a 4% bonus, turning it in 24 hours early is worth a 2% bonus.

ray traced cubes and cylinder


Overview

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 PNG image file. The assignment support code includes a routine for reading in the scene files. 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

First of all, download the support code archive

The executable should be called with two command-line arguments: the name of the input scene file (*.sce) and the name of the output PNG image file (*.png).

The code needs to be linked against PNG library, and the compilation requires the corresponding header files. The Windows version provides this library and its headers.

Windows specific:

Unzip the archive, and specify the command-line arguments in the Debugging options, or run the program from a command line.

Linux specific

Download the file p2.tgz into some folder. Open console and go to that folder. Then type
tar xvzf p4.tgz
cd p4/proj4
make
./proj4 ../scenes/simple.sce s.png

View the resulting file in some image viewer and you should see an empty image with blue background.

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. We use png image library for image output.

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 XVec3f = XVec<float> represents a three-component vector. The matrix transform class XFormf stores a 3x4 matrix. This matrix can be applied to points and vectors. When applied to points represented as XVec3f 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": XVec3f, XVec2f, XFormf.


Tasks

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.:

        % proj4 simple.sce out.png


    When you view the image (out.png) you should see  a simple scene rendered with ambient lighting.  See camera.h for details of the camera class (needed to compute the rays).   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 (note the change from X3D spec). 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 2 and 3). 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. You will need to modify code within RayTracerT::TraceAll() function to introduce jittered samples. Feel free to write your own auxilliary functions to achieve this goal.   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 4 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

Submission guidelines.

Make sure your code compiles and works on CAEN Linux or Windows machines. You will need to turn in

Your writeup should discuss:
  1. Anything about your implementation that is noteworthy
  2. Feedback on the assignment.
  3. Name your file writeup-<uniqname>.txt

We'll post the detailed submission instructions closer to the deadline.

The timestamp on your source files and your writeup will indicate the time of submission and if this is past the deadline your submission will be considered late. Therefore, you are allowed multiple 'submissions' as long as you respect the deadline. The submission folders will also be closed for writing right after the deadline.

Test the compilation: Your submission must compile without errors and warnings on supported platforms. Mention the platform in your writeup. Code that doesn't compile will be heavily penalized.


Last updated: November 18, 2007.