|
CS 428 - Fall 2011 Project 4: Ray tracing
Due electronically: Wednesday, December 7th, 12 noon
Image and scene file due: Tuesday, December 14th, 12 noon |
|
Description
All of the assignments so far have used OpenGL to render the objects
in the scene. Now it's your turn! Ray tracing is capable of
producing more interesting lighting effects than a scan-line rendering
approach. (Well, it is possible now to implement some of these
effects using programmable hardware.) While the lighting model used
is very similar to that of OpenGL, the difference comes in the
rendering of shadows, reflections, and refractions (as seen in the
image above), by using a better model of light transport.
Objective
From this assignment, you will understand how a recursive ray tracer works; from sending out rays through each pixel, to intersecting it with objects in the scene, to recursively sending out more rays. In the end, you'll find that it's just a large number of relatively simple geometric computations.
Program
You will be provided with skeleton code for this program, which supplies the main program structure. There is no graphical user interface -- you specify a scene file, and the program produces an image. Use the program "eog" to view the resulting images.
If you want to work on a Windows PC, you can use a program like pic2pic to convert from PPM files to something you can view (like TIF).
The relatively boring stuff (parsing the scene file, reading and writing image files, computing ray-object intersections, and transformations) has been done for you. You will have to write the main parts of the ray tracer; generating the rays (for each pixel), determining the relevant intersections with the scene, and computing the resulting color of the ray. You will also have to recursively send out additional rays (for shadows, reflections and refractions).
In some cases, you will find code that is a placeholder for what you need to write -- just remove it once you start on that part. In addition, you will probably have to add code elsewhere, and not just where the comment markers are. So don't feel confined by where these comments are placed.
The skeleton code for this project can be found (on the cereal
machines) in the directory
~decarlo/428/proj4
Also included is a Makefile.
Note that we are not using OpenGL in this assignment.
A description of the scene file format is given in
"README.running". Several sample scene files are provided
(easy, easytex, hard, hardchecker)
in the project directory, as well as their corresponding output in the
directory (at 256x256 resolution)
~decarlo/428/proj4ex
Handing in
Hand in the following:
Hand in your image in PPM format (this is the format that your program will save it in, so you won't need to do anything here -- please don't convert it to another format). Your scene file should be substantially different from the sample ones provided (which simply are there to help you test your program). Feel free to hand in more than one scene file and image. Keep in mind you might need to let it run for quite a while (a day or more!) to get your image, if you implement a lot of the extra features.
You will need to create a tar archive to hand in, that contains your java files, Makefile, and description:
tar cf proj4.tar Makefile *.java descrip.txtHere are the instructions on how to hand in this file.
This assignment is a bit vague so that you can make a lot of the design decisions yourself. There are several approaches to the problems here. Use your best judgement to try and get the most useful result. Ask for help or clarification when you need it.
Program use
To run the program on a particular scenefile: java Trace scenefile
A description of the scene file format is provided in the README.running file.
There are a number of command line options available (italicized words indicate you need to specify a particular value):
Data structures
The Scene class stores all of the information about a scene (objects, lights, materials, the camera, the matrix stack, some scene parameters, and the image being rendered). Aside from the image, all of this information comes from the scene file. The method Scene.render() is the main loop of the ray tracer, where a ray is cast out for every pixel in the image. An image of type RGBImage is produced.
The elements of the scene are subclasses of the abstract class RaytracerObject, which holds the specification of the scene element as taken from the scene file. Most of the methods in this abstract class are for parsing the scene file (the other classes that handle parsing are Parser, ParamSpec, MatrixStack and, SceneCommand). Note that you can print out objects in the RaytracerObject class---this is useful for debugging.
The following are the scene element classes: Camera, Light, Material and Shape. Each provides a specification of the scene element, along with methods for their manipulation and computation.
Particular shapes are defined as subclasses of Shape, and provide the low-level ray-object intersection routines. There are four types of objects defined: sphere, cylinder, cone and box (in classes Sphere, Cylinder, Cone, and Box). You are welcome to add more of your own (ask us for references for ray-object intersections for a particular object, if you can't find it).
The (matrix) transformations of the Shape are accessible using a number of accessors, which return a Matrix4d object: Shape.getMatrix(), Shape.getInvMatrix(), and Shape.getInvTMatrix() (for M, the inverse of M, and the inverse transpose of M). You'll use these matrices, and the various forms of the Matrix4d.transform method to transform the ray from the world to object space, and the intesection point and normal back to world space. Keep in mind that it affects the result whether you tranform a Point3d or Vector3d (it will add the proper w component of the homogenous coordinate for you when you pass these), so don't just convert back and forth between these indiscriminately.
There are three classes used for geometric computation: Ray (which represents a ray), ISect (which represents an intersection point, and other accompanying information), and Tools (which provide utility routines you will be using to compute reflected and refracted vectors, as well as scale colors).
You should be using the javax.vecmath package for all of your vector and matrix operations. Don't write out the code yourself for the calculations! (You will lose points this time if you do this.) Also, be sure to use Vector3d for vectors and Point3d for points, so your computations are correct.
The classes you will primarily be modifying are Scene, Light, and Camera.
Requirements
The following is a description of what is required to produce a fully-functional recursive ray tracer from the skeleton code. I suggest you proceed in the order they are listed, and test it when suggested (or even more often!).
Writing this program is a good opportunity to learn how to
sanity-check your code. Once you finish this part, you should
be thinking about how can you test out
Camera.pixelRay()? One simple but not exhaustive test
(on the scene easy) would be to look at the return
value when you pass it the origin: the return value should be a
ray where the point is
(0,0,-near) (on the near plane) and the direction is
(0,0,-1) (looking down negative z-axis, normalized). So
you can just temporarily add this line at the top of
Scene.render() after the call to
camera.setup():
System.out.println(camera.pixelRay(0,0));
The default value of near is 1, so you can do a
much better test by temporarily adding camera { near=0.5
} at the top of your scene file. (Note this won't change
the field-of-view because that is controlled separately! Look
at Camera.computeUVN() for details.) Also, this works
on easy because of the default values of eye, look and
up (there is no camera in the scene file). This is what I
meant by this not being exhaustive, and suggests you should probably
be thinking about what other tests you can do...
Here is the equation for light i which has intensity Li and if it's a positional light, the distance between the light and surface point is di, ri is the reflected light direction, and ti is the tint. The function a(d) models light attenuation for point lights, and is described in section 10.1 of the text (and in a comment at the top of Light.java); it's value is one for a directional light (which are thus not attenuated). The material properties are given by the reflectances ka, kd, and ks, and the texture function T(u), where u are the texture coordinates of that surface point.
With transparent objects, you will also have rays coming from inside an object. This ray should still reflect off (internally) or refract through the surface. But the normal vector at the intersection point returned by Scene.intersects() always points outside the surface. For the refraction computations to proceed correctly, be sure that the normal vector you pass Tools.refract()) is pointing inside for just these rays. What you need to do is simply check if the ray is coming from inside the object (it is if the dot product of the ray direction and the normal vector is positive, and if it is pass in a negated copy of the normal vector. Also keep track of the fact that it has been flipped, as this tells you whether you are traveling into or out of the object---this determines the order of the refraction indices (the last two arguments to Tools.refract()).
Make sure your reflection and refraction rays are pointing the right way (and that their direction is not backwards). Test them separately using the sample output to check your results (hard-reflectonly.ppm and hard-refractonly.ppm).
The surface normals in the intersection data structure always point outward. With refraction rays, a ray might hit the surface from the inside. For this ray tracer, we will assume that the appropriate thing to do here is to perform a direct lighting computation, on the inside of the surface. But the surface normal is facing the wrong way. (Remember how OpenGL dealt with this problem using a two-sided lighting model.) Anyway, this means you have to detect situations where this occurs (you can easily do this by examining the dot product of the surface normal and the incoming ray direction), and then negate the surface normal when using it in the lighting computation, so that it points inward. If this step is skipped, then there is only one specularity visible on the transparent sphere for hard-refractonly.ppm, instead of two (the second one is on back of the sphere, on the inside).
Extensions
The following is a list of optional extensions. The first three are required for graduate students.
Consider firing more rays based on how much spread there is in
the distribution; that way, you don't slow down your program too
much for the easy cases. For instance, when the shininess value
is very high, you will need fewer rays. Come up with a
heuristic which produces reasonable results, letting the easier
cases (i.e. almost perfect reflections) go faster than harder
ones (very blurry reflections). One effective algorithm
computes an online measure of variance in the ray colors, and
stops firing rays in situations where the colors don't vary as
much (assuming enough rays have been fired to get a good
estimate of the variance). (If you don't understand what this
paragraph means, either ask about it, or skip it.)
You must add a command line switch "-distrib D" that
you use to turn this on (it should be off by default). The
value of D is a multiplier that says how many rays are
fired at each interaction. When D is zero, your code
should reduce to a normal recursive ray tracer (with perfect
reflections, etc.). If you take the ceiling of D times
the number of rays your model uses, then for very tiny values
(say, D = 0.001), it will fire one random ray at each
interaction. This means your ray tracer won't be significantly
slower, but you can still see what the effects will be
(approximately, although they'll be noisy). Set it up so that
on average, D rays will be fired at each interaction,
although this will be modulated by the heuristics you have in
place above to use more rays for the harder cases.
Reference: Cook, Porter, Carpenter, "Distributed ray tracing",
SIGGRAPH '84, pp. 137-145.
[PDF]
Here are examples:
| |
|
|
| Backward ray tracing
(with adaptive supersampling) CPU time = 10 sec @ 256x256 |
Distribution ray tracing with
penumbra, glossy reflections/refractions (no adaptive supersampling) CPU time = 40 sec @ 256x256 |
Distribution ray tracing
(with adaptive supersampling) CPU time = 16.6 min @ 256x256 CPU time = 48.2 min @ 512x512 |
Reference: excerpt from Hanrahan, "A Survey of Ray-Surface Intersection
Algorithms", in "An Introduction to Ray Tracing", Glassner ed.
[PDF]
Reference: Bailey and Clark, "Using ChromaDepth to Obtain
Inexpensive Single-image Stereovision for Scientific
Visualization", Journal of Graphics Tools 3(3) 1998, pp. 1-9.
[PDF]
Note that this paper describes how to do it in OpenGL, you'll find it easy to translate this to ray tracing. Be sure to use the full extent of the color range (tailored to your scene) for the most substantial depth percepts. You should look at the RGBimage class (specifically the setPixel and adjustColor methods) so you get this part right.
Can you get the depth of image content seen in reflections and refractions to appear at the correct depth? (I'm quite sure this bit isn't too difficult, but haven't actually tried it!)
Hints
The following are some suggestions...