Rendering Techniques Assignment 3:

Ray Tracing Cylinders

Assigned: Thursday, October 28, 1999
Due: Wednesday, November 10, 1999 (before the start of class)


For this assignment, you will add cylinder primitives to your ray tracer. Using an object-oriented approach, most of your program will apply to primitives without distinguishing between spheres and cylinders. Only a small portion of the program will deal with the particular primitives being rendered.

Program Description

Your extended raytracer should run with the same command line as the one from the previous assignment:
raytrace <scene description filename> <image output filename>
The input format has been extended to describe a set of (right, circular) cylinders as well as the lights and spheres from the former assignments. Each cylinder is axis-aligned, and may be aligned with the X, Y, or Z axis. The cylinder may have infinite or finite length. Setting the length parameter to any negative value indicates an infinite length cylinder. Finite length cylinders begin with the base at the origin and extend for the specified length along the specified axis in the positive direction. Cylinders may be translated away from the actual axes by specifying the base center. For finite-length cylinders, this effectively specifies the location of the center of its base, and the rest of the cylinder extends in the positive direction of the specified axis. For inifinite-length cylinders, there is no actual base, but the base-center still describes the displacement of the cylinder from the coordinate axis.

Finite-length cylinders may also be rendered with or without caps (the caps will never be visible for infinite cylinders). These caps are planar discs that cover the ends of the cylinder, turning the finite cylinder into an enclosed solid.

The cylinders should be illuminated just the same as the spheres in the previous assignment. Thus, reflections and refractions are possible, as well as local illumination. As in the previous assignment, you should assume that transparent primitives do not overlap in space. Also, you may assume that finite cylinders without caps will not be transparent (because they are not really solids).

This assignment includes a sample scene plus a binary executable for verifying your test cases. Be careful, there are a number of tricky situations that may occur in both the intersection and normal computations.

Design Suggestions

For my implementation, I made a primitive class containing all the material properties and virtual functions for computing a ray intersection and for computing a normal vector given a surface point. Then I inherited all this to sphere and cylinder classes, each with their own intersection and normal functions.

Except for the file parser, intersection routines, and normal routines, all of my code knows only that it is dealing with a primitive or list of primitives. This makes the design much simpler and avoids coding things multiple times for the two primitive types.

Cylinder Mathematics

A cylinder is just a 2D circle extruded into three dimensions. This means that most of your computations may be performed effectively in 2D rather than 3D. In my implementation, I wrote a function to convert a point or vector from 3D to 2D, transferring the appropriate coordinates (depending on the cylinder axis) to the X and Y components of the 2D version, setting the Z component to 0.0.

Notice that a circle is just a sphere in one lower dimension. This means that all the forumas you used for spheres pretty much apply to the cylinder computations. The implicit function for the sphere or circle is:

(X-C).(X-C) - r^2 = 0
Here, X is a point on the sphere or circle, C is the center, and r is the radius. The equation for the ray is:
X = P + t*V
P is the start of the ray and V is the direction. Substituting this formua for X in the implicit equation yields:
(P+tV-C).(P+tV-C) - r^2 = 0
Regrouping the terms by powers of t yields:
(V.V)*t^2 + 2*(P.V - C.V)*t + P.P + C.C - 2*P.C - r^2 = 0
Now you just solve for t using the quadratic formula to get 0, 1, or 2 possible intersections.

For cylinders with caps, you will also have to consider the intersections of the ray with the planes of the cylinder caps. Planes have the implicit form:

(N.X) - d = 0
N is the normal of the plane. For axis-aligned cylinders, the normal will be a unit vector in the direction of the axis and d is the distance of the plane from the origin. Substituting the ray for X yields:
N.(P+tV) - d = 0
N.P + t*(N.V) = d
t = (d - N.P) / (N.V)
There will be either 0 or 1 intersection. The intersection only counts if it is within the cylinder's circle, of course.

For capped cylinders, you will need to consider the intersection of a ray with not only the cylinder walls, but with the two caps. As always, the closest positive intersection is the one used. Also, for the normal computation, you will have to decide if the point is on a wall or cap to figure out what normal to return.


E-mail me a single .zip file (uuencode the file or do mime attachment) containing the following: Again, please follow the above procedure to turn in the assignment to avoid delayed grading or reduced grades.

Scene Description Format (extension .rt3)

Numbers in angle brackets are real numbers, in square brackets are integers
Naturally, the brackets do not appear in the actual file (but the colons do).
Blank lines and white space should be ignored
Lines with a ‘#’ as the first character are comments and should be ignored.
Colors should be specified in the range [0.0, 1.0]
Angles should be in degrees, and refer to the full angle (as opposed to the half angle)
# comment lines start with # and may appear anywhere
RESOLUTION: [x resolution] [y resolution]
FIELD_OF_VIEW: <full x field-of-view>
BACKGROUND: <red> <green> <blue>
MAX_BOUNCES: [maximum recursion level]
NUM_LIGHTS: [number of lights]
 POSITION: <x> <y> <z>
 DIRECTION: <x> <y> <z>
 INTENSITY: <red> <green> <blue>
 EXPONENT: <exponent>
 CUTOFF: <angle>
#in other words, one LIGHT…ENDLIGHT block for each light
NUM_SPHERES: [number of spheres]
#similarly, one SPHERE…ENDSPHERE block for each sphere
 CENTER: <x> <y> <z>
 RADIUS: <radius>
 DIFFUSE_COLOR: <red> <green> <blue>
 SPECULAR_COLOR: <red> <green> <blue>
 TRANSMISSION_COLOR: <red> <green> <blue>
 REFRACTION_INDEX: <index of refraction>
# and finally, the cylinders
NUM_CYLINDERS: [number of cylinders]
 AXIS: <X, Y, or Z>
 CAPS: <Y or N>
 BASE_CENTER: <x> <y> <z>
 RADIUS: <radius>
 LENGTH: <length>
 DIFFUSE_COLOR: <red> <green> <blue>
 SPECULAR_COLOR: <red> <green> <blue>
 TRANSMISSION_COLOR: <red> <green> <blue>
 REFRACTION_INDEX: <index of refraction>

As I described in class, one way you can implement some pretty simple parsing is as follows. Open the file with fopen(). Write a simple procedure to get each line of input. This procedure can include a loop using fgets() to read a line and then test to see if it's all blank or a comment. Return the first non-blank, non-comment line. Use sscanf() to get the useful data out of the input line. sscanf() works in the presence of white space and allows you to check that the expected number of tokens were read. Of course you are free to continue using whatever method of parsing you used in the original assignment assuming that it works and allows the program to be executed with the specified command line.

Final Words of Encouragement

If you use some simple classes as I suggest, all notion of spheres and cylinders will be removed from most of your program, then you can focus on getting your cylinder intersection and normal computations correct. I recommend starting with infinite cylinders, then adding support for finite cylinders, and finally for rendering caps. Good luck, and have fun!

October 28, 1999