Assignment 2: Ray Tracing
Due: 10/13/00 at 11:59PM.
Overview
In this assignment you will implement a basic raytracer. 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, a BMP
image file importer/exporter (for images and textures, etc), and a couple
of supporting data structures for lights, materials, etc.
Additionally a utility is provided to allow you to view your ray-files.
This will help you out in debugging your code and ensuring that the images
come out looking as they should. The utility is implemented in OpenGL and
so does not support recursive ray-casting and transparency, but at least
for the first few parts of the assignment your image should agree with
the image generated by the viewer.
An overview of the code you will be using can be found here.
An overview of the .ray file syntax can be found here.
Some results at different steps of the process can be found
here.
What You Have to Do
The assignment is worth 20 points. The following is a list of features
that you may implement. The number in parentheses corresponds to how many
points it is worth. Options in bold are mandatory.
-
(1) Modify RayTrace(const
char* fileName, int width, int height, int rLimit,
float
cLimit) (in ray.[cpp/h]) to generate and cast rays from the camera's
position through pixels to construct an image of the scene.
-
(1) Implement the Group::intersect(Ray
ray, IntersectionInfo& iInfo) (in group.[cpp/h]) to cast
rays through scene-graph nodes. For now ignore the local transformation
and simply compute the intersection properties for the closest intersection
within the list of Shapes associated
to the Group.
-
(1) Implement the Sphere::intersect(Ray
ray, IntersectionInfo& iInfo) (in sphere.[cpp/h]) method
to compute ray intersections with a sphere.
-
(2) Implement the Triangle::intersect(Ray
ray, IntersectionInfo& iInfo) (in triangle.[cpp/h]) method
to compute ray intersections with a triangle.
-
(1) Modify GetColor(Scene
scene, Ray ray, IntersectionInfo iInfo, int rDepth,
float cLimit) (in ray.[cpp/h]) to return the color at
the point of intersection using the ambient and emissive properties of
the
Material, and call this function
in RayTrace(const char* fileName,
int width, int height, int rLimit,
floatcLimit)
to compute the color at a point of intersection.
-
(1) Implement:
in order to obatin the diffuse color contribution of the lights at the
point of intersection.
-
(1) Implement:
in order to obatin the specular color contribution of the lights at the
point of intersection.
-
(1) Implement:
-
(1) Use the Light::getDiffuse,
Light::getSpecular,
and Light::isInShadow
methods you implemented to modify GetColor(Scene
scene, Ray ray, IntersectionInfo iInfo, int rDepth,
float cLimit) so that the returned color takes into account
the diffuse and specular contribution of the light sources. (Taking into
account whether or not the point of intersection is in shadow with respect
to a particular light.)
-
(1) Modify the implementation of Group::intersect(Ray
ray, IntersectionInfo& iInfo) (in group.[cpp/h]) to take
into account the local transformation of the Group.
(You can do this by using the transformation to convert the ray into object
coordinates, computing the intersection, using the local transformation
to convert intersection properties back into world coordinates, etc.)
-
(1) Modify the implementation of GetColor(Scene
scene, Ray ray, IntersectionInfo iInfo, int rDepth,
float cLimit) to recursively cast reflected rays at the point
of intersection and add the reflected color contribution to returned color
value.
-
(1) Modify the implementation of GetColor(Scene
scene, Ray ray,IntersectionInfo iInfo, int rDepth,
float cLimit) to recursively cast refracted rays through the
point of intersection and add the refracted color contribution to returned
color value. (For now, you should ignore the refraction index.)
-
(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.
-
(2) Accelerate ray intersection tests with hierarchical bounding boxes.
To do this you will have to:
-
Implement BoundingBox::BoundingBox(Point3D*
pList, int listSize) constructor. (This will create a
box containing the specified list of points.)
-
Implement BoundingBox::operator+
(BoundingBox b) method. (This will return a bounding box which
contains the union of the two bounding boxes.)
-
Implement the BoundingBox::intersect(Ray
ray) method. (This will return the distance along the ray to the nearest
point of intersection with the bounding box.)
-
Implement the BoundingBox::transform(Matrix
m) method. (This will return the bounding box containing the transformed
-- no longer axis aligned -- bounding box.)
-
Implement the Shape::getBoundingBox(void)
for each of the Shape subclasses
that you have implemented. This method will have to return the bounding
box for that shape. Additionally, when modifying Group::getBoundingBox(void)
you will have to accumulate the bounding boxes of all the child Shapes,
transform them, find the bounding box of the transformed bounding box,
store that and return it. (Note: When the parser is done reading the .ray
file it automatically calls the Shape::getBoundingBox(void)
method for the root node, so that if you have implemented this method for
all of the subclasses of Shape, the
bounding boxes are already in place to be used for intersection querries,
and you do not have to reset them.)
-
Group::intersect(Ray
ray, IntersectionInfo& iInfo) to support testing ray intersection
with the bounding box before testing for intersection with all child Shapes.
-
Optimize the bounding box hierarchy so that when Group::intersect(Ray
ray, IntersectionInfo& iInfo) is called, the Group
checks all the bounding boxes first, chooses the one closest to the Ray,
tests for intersection with the Shape
corresponding to the bounding box and only tests those Shapes
whose bounding box intersection is closer then the current closest intersection
point.
-
(2) Modify Triangle::intersect(Ray
ray, IntersectionInfo& iInfo) (in triangle.[cpp/h]) to return
the texture coordinates at the point of intersection and modify GetColor(Scene
scene, Ray ray, IntersectionInfo iInfo, int rDepth,
float cLimit) to support texture mapping (with bilinear
interpolation of texture samples).
-
(1) Use the index of refraction and Snell's
Law to calculate the correct direction of rays trasmitted through transparent
surfaces and modify GetColor(Scene
scene, Ray ray, IntersectionInfo iInfo, int rDepth,
float cLimit) appropriately.
-
(1) Treat point/spot lights as having a finite 'area' and cast a collection
of rays during shadow checking to generate soft shadows. That is,
if all shadows rays are blocked or unblocked we have zero or full lighting
from the source in question just as before, but if a fraction of the shadow
rays are blocked the light is only partially attentuated. Something
randomized and/or adaptive scheme should be used to avoid banding.
-
(1) Implement the Box::intersect(Ray
ray, IntersectionInfo& iInfo) (in box.[cpp/h]) method to
compute ray intersections with a box.
-
(1) Implement the Cylinder::intersect(Ray
ray, IntersectionInfo& iInfo) (in cylinder.[cpp/h]) method
to compute ray intersections with a cylinder.
-
(1) Implement the Cone::intersect(Ray
ray, IntersectionInfo& iInfo) (in cone.[cpp/h]) method to
compute ray intersections with a cone.
-
(1) Modify Sphere::intersect(Ray
ray, IntersectionInfo& iInfo) (in sphere.[cpp/h]) to return
the texture coordinates at the point of intersection (longitude and latitude)
and modify GetColor(Scene
scene, Ray ray, IntersectionInfo iInfo, int rDepth,
float cLimit) to support texture mapping (with bilinear
interpolation of texture samples).
-
(1) 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) Implement depth of field camera effects.
-
(1) Simulate real camera lens behaviour. You must implement the procedure
in this SIGGRAPH
paper
-
(2) Accelerate ray intersections with grid, octree or BSP spatial data
structures.
-
(?) Impress us with something we hadn't considered...
By implementing all the required features, you get 13 points. There are
many ways to get more points:
-
(as listed above) implementing optional features,
-
(1) submitting 3D models you constructed,
-
(1) submitting images for the art contest,
-
(1) submitting a .mpeg movie with a sequence of ray traced images resulting
from a continuous camera path (e.g., use the
makemovie command on the SGIs), and
-
(2) winning the art contest.
It is possible to get more than 20 points. However, after 20 points, each
point is divided by 2, and after 22 points, each point is divided by 4.
Getting Started
You should use the code available at /u/cs426/2/, on the MECA
machines, as a starting point for your assignment. We provide you with:
-
ray.[cpp/h] The code responsible for casting rays, calling intersection
methods, computing colors, etc. etc. etc.
-
shape.h The abstract base class that all shapes must implement
-
group.[cpp/h] Code for the Shape subclass describing a scene-graph
-
rayFileInstance.[cpp/h] Code for the Shape subclass describing
the scene graph specified in a .ray file
-
triangle.[cpp/h] Code for the Shape subclass describing a
triangle
-
sphere.[cpp/h] Code for the Shape subclass describing a sphere
-
cone.[cpp/h] Code for the Shape subclass describing a cone
-
cylinder.[cpp/h] Code for the Shape subclass describing a
cylinder
-
box.[cpp/h] Code for the Shape subclass describing a box
-
line.[cpp/h] Code for the Shape subclass describing a line
segment
-
light.h The abstract base class that all l ights must implement
-
pointLight.[cpp/h] Code for the
Light subclass describing
a point light
-
directionalLight.[cpp/h] Code for the Light subclass describing
a directional light
-
spotLight.[cpp/h] Code for the
Light subclass describing
a spot light
-
main.cpp This parses apart the command line arguments and invokes
the raytracer
-
scene.[cpp/h] Code for the classes that store environmental information,
textures, materials, rayFiles etc.
-
geometry.[cpp/h] Most of the code for the geometric manipulation
you will need (matrix multiplication, vector addition etc.)
-
boundingBox.[cpp/h] Code for defining bounding boxes.
-
bmp.[cpp/h] Code responsible for reading and writing BMP files
-
implemented.[cpp/h] Code defining a global flag that specifies if
unimplemented methods should announce themselves when they are invoked.
-
RayFiles/: Directory containing a variety of .ray files.
-
tracer.dsp: Project file suitable for Visual C++ on Windows 2000
platform.
-
Makefile: A Makefile suitable for UNIX platforms.
-
viewer: A UNIX compiled ray-file viewer to look at .ray files. (Note
that the code for this assumes that if you are looking at the front of
the triangle the vertices are indexed in counter-clockwise order.)
-
viewer.exe: A Windows 2000 comiled ray-file viewer to look at .ray
files. (Note that the code for this assumes that if you are looking at
the front of the triangle the vertices are indexed in counter-clockwise
order.)
After you copy the provided files to your directory, the first thing to
do is compile the program.
If you are developing on a Windows NT machine, double click on tracer.dsp
and select build from the build menu.
If you are developing on a UNIX machine, type make.
In either case an executable called tracer (or tracer.exe)
will be created.
How the Program Works
The program takes in to mandatory arguments, the input (.ray) file
name and the output file name (.bmp). It is invoked from the command line
with:
% image -src in.ray -dst out.bmp
Additionally, you can specify height, width, recursion depth and contribution
limit as follows:
% image -src in.ray -dst out.bmp -width w -height
h -rlim r -clim c
What Has Not Been (Completely) Implemented
-
RayTrace(const char* fileName,
int width, int height, int rLimit,
float cLimit)
(in ray.[cpp/h])
-
GetColor(Scene
scene, Ray ray, IntersectionInfo iInfo, int rDepth,
float cLimit) (in ray.[cpp/h])
-
Sphere::intersect(Ray
ray, IntersectionInfo& iInfo) (in sphere.[cpp/h])
-
Sphere::GetBoundingBox(void)
(in sphere.[cpp/h])
-
Triangle::intersect(Ray
ray, IntersectionInfo& iInfo) (in triangle.[cpp/h])
-
Triangle::GetBoundingBox(void)
(in triangle.[cpp/h])
-
Group::intersect(Ray
ray, IntersectionInfo& iInfo) (in group.[cpp/h])
-
Group::GetBoundingBox(void)
(in group.[cpp/h])
-
Box::intersect(Ray
ray, IntersectionInfo& iInfo) (in box.[cpp/h])
-
Box::GetBoundingBox(void)
(in box.[cpp/h])
-
Cylinder::intersect(Ray
ray, IntersectionInfo& iInfo) (in cylinder.[cpp/h])
-
Cylinder::GetBoundingBox(void)
(in cylinder.[cpp/h])
-
Cone::intersect(Ray
ray, IntersectionInfo& iInfo) (in cone.[cpp/h])
-
Cone::GetBoundingBox(void)
(in cone.[cpp/h])
-
PointLight::getDiffuse(Point3D
cameraPosition, IntersectionInfo iInfo) (in pointLight.[cpp/h])
-
PointLight::getSpecular(Point3D
cameraPosition, IntersectionInfo iInfo) (in pointLight.[cpp/h])
-
PointLight::isInShadow(IntersectionInfo
iInfo, Shape* shape) (in pointLight.[cpp/h])
-
SpotLight::getDiffuse(Point3D
cameraPosition, IntersectionInfo iInfo) (in spotLight.[cpp/h])
-
SpotLight::getSpecular(Point3D
cameraPosition, IntersectionInfo iInfo) (in spotLight.[cpp/h])
-
SpotLight::isInShadow(IntersectionInfo
iInfo, Shape* shape) (in spotLight.[cpp/h])
-
DirectionalLight::getDiffuse(Point3D
cameraPosition, IntersectionInfo iInfo) (in directionalLight.[cpp/h])
-
DirectionalLight::getSpecular(Point3D
cameraPosition, IntersectionInfo iInfo) (in directionalLight.[cpp/h])
-
DirectionalLight::isInShadow(IntersectionInfo
iInfo, Shape* shape) (in directionalLight.[cpp/h])
-
BoundingBox::BoundingBox(Point3D*
pList, int pSize) (in boundingBox.[cpp/h])
-
BoundingBox::operator+
(BoundingBox b) (in boundingBox.[cpp/h])
-
BoundingBox::transform(Matrix
m) (in boundingBox.[cpp/h])
-
BoundingBox::intersect(Ray
ray) (in boundingBox.[cpp/h])
What to Submit
You should submit:
-
the complete source code with a Makefile,
-
any *.ray files you created (optional),
-
the .mpeg movie for the movie feature (optional),
-
the images for the art contest (optional), and
-
a writeup.
The writeup should be a HTML document called assignment2.html
which may include other documents or pictures. It should be brief, describing
what you have implemented, what works and what doesn't, how you created
the art contest images and/or movies, and any relavent instructions on
how to run your interface.
Make sure the source code compiles in the MECA
workstations. If it doesn't, you will have to attend to a grading session
with a TA, and your grade will suffer.
Always remember the late policy
and the collaboration
policy.
Hints
-
The Ray Tracing News is an
invaluable resource for information. The archives contain information of
everything you might want to know, and much more...
-
Visit the POVRAY site, home of a popular freeware raytracer.
Check out the links to the still
comptetions and animated
competitions for inspiration..
-
For information about the barycentric coordinate triangle intersection
test, check out Ray Tracing News issue RTNv5n3
which focuses on various polygon/ray intersection methods.
-
Precept notes about the general code structure are available here.
-
Precept notes about class description and lighting equations is available
here. (The page describing the getDiffuse method had an error in it and has since been removed.) If you want the power point version you can get that
here.
Stay tuned for more hints.
Notes
-
The GetColor(Scene
scene, Ray ray, IntersectionInfo iInfo, int rDepth,
float cLimit) (in ray.[cpp/h]) has been modified to take
in the Ray and the maxDepth variable is not passed in. You
should make the corresponding changes to the ray.cpp and ray.h files. (If
you have not begun working on the assignment, the code on the arizona machine
has been modified appropriately.)
-
There seems to be some trouble with .bmp writing on Windows machines. Trying
modifying the fopen command in main.cpp to: fopen(dst_filename,"wb")
to open the file in binary mode.
-
I have gotten a number of E-mails on this one: "What is the contribution
limit and how do I use it?"
Contribution limit is used to determine when the value of a color returned by
casting secondary rays will be too small to be worth computing. Specifically,
if you are casting specular (respectively transparent) rays, then before adding
the color obtained by casting secondary rays, you will scale this color by the
specular (respectively transparency) coefficient. Since the color
coefficients must be between 0.0 and 1.0 the specular
(respectively transparency) coefficient tells you in advance the upper bound
on the brightness of the returned color. Thus if the specular (respectively
transparency) contribution is less than the contribution limit you know that
you do not need to send off secondary rays in the specular (respectively
transparent) direction.
-
Sorry to be catching this one so late. In the Cone::read method, you should
switch the order of reading so that the radius gets read in before the height.
FAQ
Stay tuned for more questions.
Last update: 9/24/00
09:53 PM