Logistics
  Grading Policy
  Syllabus
  FAQ
  Links
  
  PAs:
 
   
  1. Raster Graphics
  2. 3D Scene
  3. Ray Tracing
    Posted Images
  4. Animation
  
  HWs:
 
   
  HW1
  HW2
 

Don't see the menu?

Programming Assignment 3: Ray Tracer

EECS487 F09 PA3: Ray Tracer

The project is due on Monday 16 November 2009 12:01pm (one minute after noon)

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 and provides point light sources and a few geometric primitives such as a triangle, ball, and triangular mesh. The specified primitives are affected by the current modeling transformation and current material properties. The scene description format is described here.

You should read Chapter 15 of the TP3 textbook and (accessible only on umich.edu hosts) excerpts from Chapter 10 of Shirley, Fundamentals of Computer Graphics, 2nd ed. [FCG2] and the lecture notes on ray tracing and distributed ray tracing. You may use or adapt the implementation details provided in the excerpts from FCG2 in this assignment, but be sure to cite it when you do so.

Support Code

First download the support code archive from /afs/umich.edu/class/eecs487/f09/PAs/pa3.tgz that contains the support code, some sample scenes, and a Makefile.

After building the project, 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 CAEN Linux setup provides both the library and the header files, and no setup is needed (just run make within the pa3 folder). On Windows, we provide the library and headers in the common folder. On Mac OS X, you should have installed libpng as part of PA1. If not, please refer to the instructions in PA1 spec on how to do so.

This project does not involve OpenGL and its ilk.

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() function should return the pointer to the jittered samples array for the area lights, and return the 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

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

        % pa3 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). When using scene files that import meshes (.ob extension) ensure that the .ob file is in the same directory as your pa3 executable. It is best if all your .sce files and .ob files are in the same directory as the executable. You could either copy all your scene files into your code directory or create a link to the pa3 executable in your scenes directory. The latter is usually a cleaner solution.   15 points

  2. Your next step will be to implement the simple shading model given in equation 12.26 (Section 12.4) of TP3. 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 PA2, 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 structure 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 15.2.1 of TP3. 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. 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 (this is not the same as the X3D spec). As described in Section 10.8 of FCG2 and Section 15.5.2 of TP3, a canonical cylinder can be arbitrarily positioned, oriented, and resized via a transform. (The same concept should be familiar from PAs 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 number of jittered samples (the constant n in the jitter sampling code in Section 10.11.1 of FCG2). You will need to modify the 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 FCG2 describes a method for handling area lights. Implement the simple method based on random (not jittered) sampling. 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 include it with your submission. Your scene can contain mesh files (in obj format) that you get on the Web or elsewhere (remember to cite your sources), but you should arrange your objects and define the viewpoint, scene lights, and material properties to produce an attractive image. Be creative! You can post images you produce by the following:

    linux% cd <to where your image.png file is located>
    linux% /afs/umich.edu/class/eecs487/scripts/postpng image.png [image2.png ...]

    You can then view the images posted at http://www.umich.edu/~eecs487/.    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. As well as listing which files were changed and how.   5 points

Submission Guidelines

As with PA1, to incorporate publicly available code in your solution is considered cheating in this course. To pass off the implementation of an algorithm as that of another is also considered cheating. For example, if the assignment asks you to implement sort using heap sort and you turn in a working program that uses insertion sort in place of the heap sort, it will be considered cheating. If you can not implement a required algorithm, you must inform the teaching staff when turning in your assignment, e.g., by documenting it in your writeup.

The creative portion of this project, Task #9, will take a long time if you are interested in getting a good score. This involves a lot of trial and error. The more complex your scene gets, the more time it will take to render. Do budget for long runtimes in your plan of action.

Code that doesn't compile will be heavily penalized. Make sure your code compiles and works on CAEN machines. You will need to turn in:
  • the source code files that you have modified, together with appropriate Makefile and/or IDE project files,
  • copies of images generated from Task #9,
  • a write-up in text format named writeup-<uniqname>.txt that describes
    • Your platform - Windows, Linux, or Mac OS X.
    • Anything about your implementation that is noteworthy (e.g. a new command in the sceneformat that you added)
    • Feedback on the assignment.

Copy your submission to the following directory on IFS: /afs/umich.edu/class/eecs487/f09/SUBMIT/<uniqname>/pa3. This path is accessible from any machine you've logged into using your ITCS (umich.edu) password. Report problems to ITCS.

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" without late-policy implications as long as you respect the deadline.

Test the compilation! Your submission must compile without errors and warnings on CAEN machines. Mention the platform in your writeup. 

General Information

The General Information section from PA1 applies. Please review it if you haven't read it or would like to refresh your memory.