1. Generating Primary Rays
You have been given the pseudo-code for a recursive ray tracer. Given a ray (point and direction), the function color trace(Ray r, int depth) will compute and return the color at the pixel corresponding to that ray.
The obvious question is, where do these rays come from? The initial rays created by casting a ray from the camera through a pixel (u, v) are called primary rays, and you need to write some code to generate them.
Basic Approach
The basic approach (for full marks on the assignment) is to assume the camera is positioned along the z axis, and the image plane is in the x-y plane. Therefore, you can define a camera and image plane with just a few parameters (in a sample scene description format):
# Scene description file 1.0
Eye z # z value of eye position, x = y = 0
ImagePlane xMin xMax yMin yMax # Size of image plane in world coords
Image Iw Ih # Size of output image in pixels
Now, for each pixel in the output image, we need to 1) compute the world position of each pixel (u, v), and 2) cast a ray from the eye through that pixel.
In this setup, computing the pixel position is easy. First, consider the distance between adjacent pixels, dx and dy.
dx = (xMax - xMin) / Iw; // x distance from (u, v) to (u + 1, v) dy = (yMax - yMin) / Ih; // y distance from (u, v) to (u, v + 1)
Then, the position p_uv = (x, y, z) of pixel (u, v) would be
x = xMin + u * dx; y = yMax - v * dy; // Origin of an image is top-left, so invert y z = 0;
Finally, we want to cast a ray from the eye through the pixel.
ray.p = eye; ray.d = p_uv - eye;
General Approach
Consider the last assignment, where you used gluLookAt(...) to position the camera, along with glFrustum() or gluPerspective() to define the view frustum. We can use a similar setup to define a general camera system in our ray tracer.
That is, we can specify a camera position by three points/vectors: a position, a direction, and an up vector. We also need to specify the size of the image plane, which can be done with a distance z from the camera to the image plane, and a width and height for the image plane. We should also specify the size of the output image in pixels. Again in a scene description format:
# Scene description file 2.0 - general camera
Eye x y z dX dY dZ upX upY upZ # position, direction, up
ImagePlane w h z # width, height, distance from camera
Image Iw Ih # size of output image in pixels
The following figure illustrates this setup.
Again, we need to somehow compute the position of pixel (u, v) based on this information. Consider the following approach. We can find two vectors orthogonal to the view direction and each other. The image plane is also orthogonal to the view direction, so these vectors can define the "axes" of the image plane. Use the standard orthogonalization approach:
ex = cross(d, up); // "x" axis relative to eye up = cross(ex, d); // make up vector orthogonal to others
Then, the vectors ex and -up define the axes of the image plane. We can compute the top-left corner p_min of the image plane, from which each pixel can be found as an offset along ex and -up.
p_min = eye + z * d - (w/2) * ex - (h/2) * (-up);
We can then compute the distance between adjacent pixels as follows:
dx = (w / Iw) * ex; dy = (h / Ih) * (-up);
Finally, the position p_uv of pixel (u, v) can be found much as before:
p_uv = p_min + u * dx + v * dy;
2. Scene Descriptions
The other major component of a ray tracer is the scene description: objects, lights, the camera, and all their associated properties must be represented internally to find the color of each pixel.
You can hard-code all of this information, but a common and easily-implemented approach is to read the scene description from file. In the sections above I wrote the camera properties in a format that could be easily included in a scene description file.
You can also define your own commands for specifying lights, materials, objects, and anything else you need. For example:
# Scene description file
Eye x y z # dX dY dZ upX upY upZ
ImagePlane w h z
...
Light x y z r g b # position and color
...
Material r g b ka kd ks n # ambient and diffuse color, coefficients
Sphere x y z r # center and radius; use previous material
Triangle x1 y1 z1 x2 y2 z2 x3 y3 z3
3. Wrapping it up
Putting it all together, the code wrapped around the trace() function will look something like this:
// Init scene from file or code
Light[] lights;
Object[] objects;
Camera cam;
Image imgOut;
// Fire ray through every pixel
Ray ray;
ray.p = cam.pos;
for i = 0 to imgOut.w - 1
for j = 0 to imgOut.h - 1
ray.d = imgOut.PixelLoc(i, j) - cam.pos;
color = trace(ray, 0); // see pseudocode in notes
imgOut.setPixel(i, j, color);
imgOut.writeToFile("foo");
