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 rayfiles. 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 roughly agree with the image generated by the viewer. The viewer is available for Linux, Mac and Windows architectures (you might need glut32.dll if you're using Windows).
An overview of the code you will be using can be found here.
An overview of the
.ray
file syntax can be found here.
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) To obatin the diffuse color contribution of the lights at the point of intersection, implement:
- PointLight::getDiffuse(Point3D cameraPosition, IntersectionInfo iInfo) (in
pointLight.[cpp/h]
);- SpotLight::getDiffuse(Point3D cameraPosition, IntersectionInfo iInfo) (in
spotLight.[cpp/h]
); and- DirectionalLight::getDiffuse(Point3D cameraPosition, IntersectionInfo iInfo) (in
directionalLight.[cpp/h]
).- (1) To obtain the specular color contribution of the lights at the point of intersection, implement:
- PointLight::getSpecular(Point3D cameraPosition, IntersectionInfo iInfo) (in
pointLight.[cpp/h]
);- SpotLight::getSpecular(Point3D cameraPosition, IntersectionInfo iInfo) (in
spotLight.[cpp/h]
); and- DirectionalLight::getSpecular(Point3D cameraPosition, IntersectionInfo iInfo) (in
directionalLight.[cpp/h]
).- (1) Implement:
- PointLight::isInShadow(IntersectionInfo iInfo, Shape* shape) (in
pointLight.[cpp/h]
)- SpotLight::isInShadow(IntersectionInfo iInfo, Shape* shape) (in
spotLight.[cpp/h]
)- DirectionalLight::isInShadow(IntersectionInfoiInfo, Shape* shape) (in
directionalLight.[cpp/h]
)- (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) (in
ray.[cpp/h]
) 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) (in
ray.[cpp/h]
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) (in
ray.[cpp/h]
) 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 queries, and you do not have to reset them.)
- Implement 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 the behavior of a real camera lens by implementing 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:
- implementing the optional features listed above,
- (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 themakemovie
command on the SGIs), and- (2) winning the art contest.
It is possible to get more than 20 points. However, as in the previous assignment, after 20 points, each point is divided by 2, and after 22 points, each point is divided by 4.
You should use the code available here (2.tar.gz, 2.zip), as a starting point for your assignment. We provide you with:After you copy the provided files to your directory, the first thing to do is compile the program. If you are working on a Windows machine, double click on
ray.[cpp/h]
: Code responsible for casting rays, calling intersection methods, computing colors, etc.shape.h
: Abstract base class that all shapes must implement.
group.[cpp/h]
: Shape subclass describing a scene-graph.rayFileInstance.[cpp/h]
: Shape subclass describing the scene graph specified in a .ray file.triangle.[cpp/h]
: Shape subclass describing a triangle.sphere.[cpp/h]
: Shape subclass describing a sphere.cone.[cpp/h]
: Shape subclass describing a cone.cylinder.[cpp/h]
: Shape subclass describing a cylinder.box.[cpp/h]
: Shape subclass describing a box.line.[cpp/h]
: Shape subclass describing a line segment.light.h
: Abstract base class that all lights must implement.
pointLight.[cpp/h]
: Light subclass describing a point light.directionalLight.[cpp/h]
: Light subclass describing a directional light.spotLight.[cpp/h]
: 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
: Visual C++ project file for Windows platforms.Makefile
: Makefile suitable for UNIX platforms.viewer
: A Linux-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-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.)tracer.dsp
and select build from the build menu. If you are developing on a UNIX machine, typemake
. In either case an executable calledtracer
(ortracer.exe
) will be created.
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:Additionally, you can specify image height, image width, recursion depth and contribution limit as follows:% image -src in.ray -dst out.bmp
Feel free to add new arguments to deal with the new functionalities you are implementing. Just make sure they are documented.% image -src in.ray -dst out.bmp -width w -height h -rlim r -clim c
The following functions have 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])
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 on the machines in Friend 017. 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.
- Visit the POVRAY site, home of a popular freeware raytracer. Check out the links to the still competitions and animated competitions for inspiration.
- Check your progress by comparing your results with these results.
- What is the "contribution limit" and how do I use it?
The 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.- What exactly is the scene graph?
The scene graph is basically a Group, which is described here. It is just a linked list of shapes.- I implemented some of the optional features, such as supersampling? Should I add new command line parameters for these features?
Yes, by all means. Just remember to document them (in your writeup and in the program itself).- It seems that Box, Cylinder, and Cone are all defined to be axis-aligned. What if I want them in some arbitrary orientation?
Just create a Group with the appropriate transformation.- What if a ray hits the background?
Just return the background color.- How exactly do textures work? Do they replace other colors in the material?
No. The usual way to deal with textures is simply to multiply the texture color by the color the object would have if no texture were present. For example, assume your calculations (disregarding texture) determine that the color of a given pixel should be (0.75, 0.60, 1.00). If the texture color in that point is (0.80, 0.50, 0.75), the final color should be (0.60, 0.30, 0.75).