CS 4810: Introduction to Computer Graphics
|
In this assignment you will implement a basic ray tracer. To allow you to focus on the nuts and bolts of the actual ray tracing, you are provided with a host of data structures for managing the ray traced objects, linear algebra functions (vector and matrix objects and operations), a function for loading scene graph files into a prescribed node tree stucture, BMP and JPEG image file importers/exporters (for images and textures, etc), and a couple of supporting data structures for lights, materials, etc.
An overview of the code you will be using can be found here or downloaded here.
An overview of the
.ray
file syntax can be found here.A description of what your ray-tracer should do as you implement the different parts of the assignment can be found here.
Skeleton code and project files for several platforms and common IDEs:
- Windows Visual Studio 2008
- Windows Visual Studio 2010
- Mac OS X
- Unix/Linux (use 'gmake' to build!!)
Notes on GLUT: This assingment relies on the external library GLUT which is distributed along with the Windows starter code. Those of you using Unix will need to be sure GLUT is installed on your machine. On most Linux distros the GLUT library is not installed by default, but there are many good (and free) GLUT implementations out there. Here is how you would install freeglut on a Debian system (this includes Ubuntu and other forks):
apt-get install freeglut3 freeglut3-dev . If you are running Ubuntu, chances are you will probably need to add sudo to run this command as root:sudo apt-get install freeglut3 freeglut3-dev . Other package managers such as YaST on SuSE and BSD ports may have different syntax. For Cygwin, use the Cygwin setup tool to install OpenGL and GLUT libraries. If you are using your own machine and need to install GLUT I recommend freeglut.Notes on Mac OS X: Apple uses a "framework" system for most of their libraries, including OpenGL and GLUT. You'll need to use 〈GLUT/glut.h〉 instead of 〈GL/glut.h〉. Similarly, you need to use a different syntax to link to the OpenGL and GLUT libraries: -framework OpenGL -framework GLUT. Finally, note that malloc.h is deprecated on OS X.
P.S. To learn more about how library dependencies and platform-dependent code are managed in real-world applications, take a look at GNU automake and autoconf tools.
We are providing starter code for this assignment (read above). There are a number of files, some of which will only need to be modified in future assignments. As in assignment 1, code modification should be relegated to the*.todo.cpp
files.
main.cpp
: Parses the command line arguments and calls the ray-tracer.Util/cmdLineParser.[cpp/h]
: Code for processing the command line arguments.Util/geometry.[cpp/h]
: Data structures and methods for a number of geometric objects, including points, rays, planes, and bounding boxes.Util/geometry.todo.cpp
: Code stub for bounding box intersection that you will need to implement.Util/time.[cpp/h]
: Code for computing the current time, in milliseconds.Image/bmp.[cpp/h]
: Code for reading and writing BMP images.Image/jpeg.[cpp/h]
: Code for reading and writing JPEG images.Image/image.[cpp/h]
: Code for image processing.Image/lineSegments.[cpp/h]
: Code for processing line segments.Image/image.todo.cpp
: Code stubs for image processing in assignment 1.Image/lineSegments.todo.cpp
: Code stubs for line segment processing in assignment 1.Ray/rayShape.h
: Abstract base class that all shapes must implement.
Ray/rayGroup.[cpp/h]
: RayShape subclasses describing a node in a scene-graph.Ray/RayFileInstance.[cpp/h]
: RayShape subclass that stores an instance of a scene graph specified in a .ray file.Ray/rayTriangle.[cpp/h]
: RayShapesubclass describing a triangle.Ray/raySphere.[cpp/h]
: RayShape subclass describing a sphere.Ray/rayCone.[cpp/h]
: RayShape subclass describing a cone.Ray/rayCylinder.[cpp/h]
: RayShape subclass describing a cylinder.Ray/rayBox.[cpp/h]
: RayShape subclass describing a box.Ray/rayLight.h
: Abstract base class that all lights must implement.
Ray/rayPointLight.[cpp/h]
: RayLight subclass describing a point light.Ray/rayDirectionalLight.[cpp/h]
: RayLight subclass describing a directional light.Ray/raySpotLight.[cpp/h]
: RayLight subclass describing a spot light.Ray/rayCamera.[cpp/h]
: Class representing a 3D camera.Ray/rayScene.[cpp/h]
: Code for the classes that store environmental information, textures, materials, rayFiles, etc.JPEG/*.[cpp/h]
: Code responsible for doing the back-end of JPEG file I/O.main.cpp
: This parses apart the command line arguments and invokes the raytracer.RayFiles/
: Directory containing a variety of .ray files.Assignment2.sln
: Visual C++ solution file for Windows platforms.Assignment2.vcproj
: Visual C++ project file for assignment 2 (Windows platforms).JPEG.vcproj
: Visual C++ project file for the JPEG library (Windows platforms).Makefile
: Makefile suitable for UNIX platforms.After you copy the provided files to your directory, the first thing to do is compile the program. To do this, you will first have to compile the
JPEG
library and then compile theAssignment2
executable.On a Windows Machine
Begin by openingAssignment2.sln
using Microsoft Visual Studio.
- Build the
Assignment2
executable:
Build -> Build Solution
orCtrl + Shift + B
. (If it has not been compiled yet, this will also compile theJPEG
library.)On a Unix Machine
- Type
make
to compile theAssignment2
executable. (If it has not been compiled yet, this will also compile theJPEG
library.)
The program takes in to mandatory arguments, the input (.ray
) .ray file name, the output image file name (.bmp
/.jpeg
/.jpg
), the dimensions of the output image, the recursion depth, and the cut-off value for early termination. It is invoked from the command line with:Feel free to add new arguments to deal with the new functionalities you are implementing. Just make sure they are documented.% Assignment2 --in in.ray --out out.bmp --width w --height h --rLimit r --cLimit c
The assignment is worth 30 points. The following is a list of features that you may implement. The number in parentheses corresponds to how many points it is worth.
The assignment will be graded out of 30 points. In addition to implementing these features, there are several other ways to get more points:
- (1) RayScene::GetRay (
Ray/rayScene.todo.cpp
):
Generate rays from the camera's position through (i,j)-th pixel of a widthxheight view plane.- (2) RayGroup::intersect (
Ray/rayGroup.todo.cpp
):
Cast rays through scene-graph nodes. For now, ignore the local transformation and bounding volume information, and simply compute the intersection properties for the closest intersection within the list of RayShapes associated to the RayGroup. (The number of shapes associated to the group is stored in RayShape::sNum and the list of shapes is stored in RayShape::shapes.)This method should return return -1.0 if there is no intersection. Otherwise, it should return the (positive) distance to the intersection. If the valuemx
is bigger than zero, then only look for intersections whose distance is closer thanmx
from the beginning of the ray.- (2) RaySphere::intersect (
Ray/raySphere.todo.cpp
):
Compute ray intersections with a sphere. (Ignore the texture coordinates for now.)- (2) RayTriangle::intersect (
Ray/rayTriangle.todo.cpp
):
Compute ray intersections with a triangle. (Ignore the texture coordinates for now.)
In computing the intersection of a ray with a triangle, there is a large amount of information that is re-computed, independent of the ray. Rather than computing this information each time, you can pre-compute the re-used information. To this end, the RayTriangle class has membersPoint3D v1
,Point3D v2
, andPlane3D plane
. You can set these values at the time that the triangle is set by implementing theRayTriangle::initialize
method. (Note that v1 and v2 should only be pre-computed for the "Ray-Triangle Intersection II" method).- (1) RayScene::GetColor (
Ray/rayScene.todo.cpp
):
Return the color at the point of intersection using the ambient and emissive properties of the RayMaterial of the intersected shape.
To implement this, you will need to intersect the ray with the root of the scene graph, this is theStaticRayGroup
pointed to by RayScene::group.- (2) To obtain the diffuse color contribution of the lights at the point of intersection, implement:
- RayPointLight::getDiffuse (
Ray/rayPointLight.todo.cpp
);- RaySpotLight::getDiffuse (
Ray/raySpotLight.todo.cpp
); and- RayDirectionalLight::getDiffuse (
Ray/rayDirectionalLight.todo.cpp
).- (2) To obtain the specular color contribution of the lights at the point of intersection, implement:
- RayPointLight::getSpecular (
Ray/rayPointLight.todo.cpp
);- RaySpotLight::getSpecular (
Ray/raySpotLight.todo.cpp
); and- RayDirectionalLight::getSpecular (
Ray/rayDirectionalLight.todo.cpp
).- (2) To determine if the point of intersection is in shadow from a particular light source, implement:
In implementing this method, you will cast a ray from the intersection point in the direction of the light source. Keep in mind that for point- and spot-lights, you only need to test for intersections along a line segment (not the full ray), so you can use the
- RayPointLight::isInShadow (
Ray/rayPointLight.todo.cpp
);- RaySpotLight::isInShadow (
Ray/raySpotLight.todo.cpp
); and- RayDirectionalLight::isInShadow (
Ray/rayDirectionalLight.todo.cpp
)mx
parameter of the RayShape::intersect to limit the extent of the ray along which intersections are tested.- (1) RayScene::GetColor (
Ray/rayScene.todo.cpp
):
Modify the computation of the color at the point of intersection to take into account the diffuse and specular contributions of all of the light sources (RayLight::getDiffuse, RayLight::getSpecular), and using the RayLight::isInShadow method to determine if the light contributes.
In order to implement this method, you will need to consider the contributions of all the light sources in the scene. The number of lights is stored in: RayScene::lightNum and the array of lights is stored in RayScene::lights.- (2) RayGroup::intersect (
Ray/rayGroup.todo.cpp
):
Modify this method so that it takes into account the local transformation returned by the call: RayGroup::getMatrix. You can do this either by applying the transformation to the shape (which is difficult) or by using the transformation to convert the ray into object coordinates, computing the intersection in local coordinates, and then using the local transformation to convert intersection properties back into world coordinates (which is much easier).
Several pieces of advide when implementing this:
- Even if the ray direction starts off as a unit vector, if you apply the transformation to the ray and the transformation involves a scale change, then the direction of the transformed ray need no longer be a unit vector. (This means that the distance returned by the RayShape::intersect method may be a scaled version of the true distance.) Make sure to normalize your ray direction after applying the transformation to ensure that it is unit-length!
- Keep in mind that transforming the surface normal at the point of intersection from local coordinates to global coordinates requires applying the inverse transpose of the matrix transformation.
- Because you will be transforming the ray from local to global coordinates and vice versa many times, it makes sense to compute the inverses and or transposes once and then store them, rather than computing the matrix inverse for each ray. To help you with this, the code definese members
inverseTransform
andnormalTransform
of the class StaticRayGroup for storing the necessary matrices. You should set these when the matrix is initialized in StaticRayGroup::set (Ray/rayGroup.todo.cpp
) and then use the calls RayGroup::getInverseMatrix and RayGroup::getNormalMatrix to get a copy of the matrices.- (1) RayScene::GetColor (
Ray/rayScene.todo.cpp
):
Implement RayScene::Reflect (inrayScene.todo.cpp
) so that given the normaln
at a point of intersection, it returns the reflected direction of a ray hitting the surface with directionv
and modify the implementation ofRayScene::GetColor
to recursively cast reflected rays from the point of intersection and add the reflected color contribution to returned color value.
(Be sure to modulate the reflected color contribution by the specularity of the surface.)- (1) RayScene::GetColor (
Ray/rayScene.todo.cpp
):
Recursively cast refracted rays through the point of intersection and add the refracted color contribution to returned color value, ignoring the refraction index for now. (Be sure to modulate the reflacted color contribution by the transparency of the surface.)- (2) RayScene::GetColor (
Ray/rayScene.todo.cpp
):
To allow transparent objects to cast partial shadows, implement:Then, replace the call to RayLight::isInShadow with a call to RayLight::transparency to allow for partial shadowing.
- RayPointLight::transparency (
Ray/rayPointLight.todo.cpp
);- RaySpotLight::transparency (
Ray/raySpotLight.todo.cpp
); and- RayDirectionalLight::transparency (
Ray/rayDirectionalLight.todo.cpp
);- (2) RayScene::GetColor (
Ray/rayScene.todo.cpp
):
Implement RayScene::Refract (inrayScene.todo.cpp
) so that, given the index of refractionir
of a the surface at an intersection point and given the normaln
at the point of intersection, it writes the refracted direction of a ray hitting the surface with directionv
into the vectorrefract
. The function should return a value of 0 if the ray could not be refracted (e.g. if computing the refraction direction would require evaluating the arcsin of a number with magnitude larger than 1 so that the angle of the ray is greater than the critical angle). Then, modify the implementation ofRayScene::GetColor
so that it uses the correct direction for computing the color contribution from refracted rays.- Accelerated Ray-Tracing
(3) Accelerate ray intersection tests with hierarchical bounding boxes. To do this you will have to:
- RayShape::setBoundingBox (
Ray/ray*.todo.cpp
):
For each of the RayShape subclasses that you have implemented (RaySphere
,RayTriangle
, etc.). This method will set the bounding box of the shape (RayShape::bBox) and return the bounding box.- RayGroup::setBoundingBox (
Ray/rayGroup.todo.cpp
):
To implement theRayShape::getBoundingBox
method for For aRayGroup
, accumulate the bounding boxes of all the child RayShapes, compute the bounding box of the transormed accumulation of bounding boxes, store and return it.
(Note: When the parser is done reading the .ray file it automatically calls the RayShape::setBoundingBox method for the root node, so if you have implemented this method for all of the subclasses of RayShape, the bounding boxes are already in place to be used for intersection queries, and you do not have to reset them.)- BoundingBox3D::intersect (
Util/geometry.todo.cpp
):
Return the distance along the ray to the nearest point of intersection with the interior of the bounding box. (If the ray starts off inside the bounding box, then a value of 0 should be returned.) If the ray does not intersect the bounding box, a negative value should be returned.- RayGroup::intersect (
Ray/rayGroup.todo.cpp
):
Modify this method to only test for an intersection or a ray with a child RayShape if the ray intersects the bounding box of the child. Keep in mind that if the value of themx
parameter is greater than 0 and the distance to the intersection of the ray with a child's bounding box is great thanmx
, you do not need to test for intersection with that child.
- (2) RayGroup::intersect (
Ray/rayGroup.todo.cpp
) Modify this method so that it first computes the intersection with all the bounding boxes of all the child shapes, then sorts the shapes based on the proximity of their bounding box intersection and finally, tests the child RayShapes in order, allowing for early termination of the ray tracing.
To optimize this method you may want to use the RayGroup::hits member to store the shapes and the distance to the intersection of their bounding boxes. (The size of this array is set to be equal to the number of children, so you needn't worry about memory allocation.) You can then use the static RayShapeHit::Compare method for sorting the shapes by calling something akin to:
qsort(hits,hitNum,sizeof(RayShapeHit),RayShapeHit::Compare);
(Note thathitNum
can be smaller than the number of child nodes, as you only need to consider those shapes whose bounding boxes were intersected by the ray.)- (2) RayTriangle::intersect (
Ray/rayTriangle.todo.cpp
):
Modify this method so that it returns the texture coordinates at the point of intersection and modify RayScene::GetColor (Ray/rayScene.todo.cpp
) to support texture mapping (with bilinear interpolation of texture samples). That is, instead of using the RayLight::getDiffuse method to compute the diffuse color contribution, use the value read out from the texture map for the diffuse color.- (1) RaySphere::intersect (Ray/raySphere.todo.cpp):
Modify this method so it returns the texture coordinates at the point of intersection (longitude and latitude).
(If you haven't already done so for the triangle, you will also need to modify RayScene::GetColor (Ray/rayScene.todo.cpp
) to support texture mapping.)- (1) Implement a jittered supersampling scheme to reduce aliasing by casting multiple rays per pixel, randomly jittered about pixel centers, and averaging the radiance samples.
- (1) Generate a 3D scene and save it as a .ray file. The scene should have both spheres and triangles, should have all three types of light sources in it, and should contain at least one primitive with a transparent material.
- (1) Treat RayPointLights and RaySpotLights lights as having a finite 'area' and cast a collection of rays during shadow checking to generate soft shadows. That is, instead of casting just a single ray towards the light to compute the shadow component of the intersection point, cast multiple rays in the direction of the light and use the average value as the shadow components.
- (1) RayBox::intersect (
Ray/rayBox.todo.cpp
):
Compute ray intersections with a box.- (1) RayCylinder::intersect (
Ray/rayCylinder.todo.cpp
):
Compute ray intersections with a cylinder.- (1) RayCone::intersect (
Ray/rayCone.todo.cpp
):
Compute ray intersections with a one.- (2) Implement procedural texture mapping with Perlin noise functions to create 3-D solid wood, marble, etc.
- (1) Implement bump mapping for either or both texturing schemes.
- (1) Simulate the behavior of a real camera lens by implementing the procedure in this SIGGRAPH paper.
- (2) Accelerate ray intersections with an octree or BSP spatial data structure.
- (?) Impress us with something we hadn't considered...
For images or movies that you submit, you also have to submit the sequence of commands used to created them, otherwise they will not be considered valid.
- (1) Submitting one or more images for the art contests.
- (1) Submitting one or more .ray files for the art contests.
- (2) winning the regular art contest,
- (2) winning the .ray file art contest,
- (?) Implementing an accelerated ray-tracer that can ray-trace a very large model in very little time (e.g. a few minutes for a model consisting of hundreds of thousands of triangles, with a recursion depth of 5).
It is possible to get more than 30 points. However, after 30 points, each point is divided by 2, and after 32 points, each point is divided by 4. If your raw score is 29, your final score will be 29. If the raw score is 33, you'll get 31.25. For a raw score of 36, you'll get 32.