Image Synthesis Programming Assignment 1
In this assignment you will implement the camera and several primitive objects for a ray tracer. We will not do recursive ray tracing yet, just ray casting.
The input language of the ray tracer will be a subset of the geometry file format of POV-Ray raytracer.
Your program should read a file specified on the command line and create an image of the scene described in the file using ray casting.
In addition to the name of the file, on the command line you should be able to specify the size of the output image.
// This line is ignored
sphere { <0,0,0>, 1 } // a sphere of radius 1 centered at the origin
Note that a 3D vector or point is specified using angular brackets, and the components are separated by commas.
sphere { <CENTER>, RADIUS }
where <CENTER> is a 3d point, and RADIUS is a real value specifying the radius. You can also add translations, rotations, and scaling to the sphere. For example, this is a sphere of radius 10 centered at 25 on the Y axis:
sphere { <0,0,0>, 1.0
scale 10
translate <0,25,0>
}
Transformations can be also applied to all other objects (see details on transformations below).
box { <CORNER1>, <CORNER2> }
Where <CORNER1> and <CORNER2> are vectors defining the x,y,z coordinates of opposite corners of the box. For example:
box { <0, 0, 0>, <1, 1, 1> }
Note that all boxes are defined with their faces parallel to the coordinate axes. They may later be rotated to any orientation using a rotate parameter.
Each element of <CORNER1> should always be less than the corresponding element in <CORNER2> .
cone { <POINT1>, RADIUS1, <POINT2>, RADIUS2 }
where <POINT1> and <POINT2> are the centers of the end caps of the cone and RADIUS1 and RADIUS2 are radii of the caps of the cone. For example,
cone { <0,0,0>, 2, <0,3,0>, 0}
is a cone 3 units tall pointing up the y axis. The base has a radius of 2. The other end has a radius of 0 which means it comes to a sharp point. If neither radius is zero then the results look like a tapered cylinder or a cone with the point cut off.
The ends of a cone are closed by flat planes which are parallel to each other and perpendicular to the axis of the cone.
cylinder {
<POINT1>, <POINT2>, RADIUS
}
where <POINT1> and <POINT2> are the centers of the end
caps, and RADIUS is the cylinder radius.
cylinder { <0,0,0>, <0,3,0>, 2}
is a cylinder of height 3 with a radius of 2, and base centered at the origin in the XZ plane.
The ends of a cylinder are closed by flat planes which are parallel to each other and perpendicular to the length of the cylinder.
polygon { N,<VERTEX1>, <VERTEX2>, ..., <VERTEXN> }
where <VERTEXi> is a vector defining the x,y,z coordinates of each corner of the triangle.
quadric { <A,B,C>, <D,E,F>, <G,H,I>, J }
where A through J are float expressions.
This defines a surface of x,y,z points which satisfy the equation:
Different values of
will give different shapes. So, if you take
any three dimensional point and use its x, y, and z coordinates in the
above equation, the answer will be 0 if the point is on the surface of the
object. The answer will be negative if the point is inside the object and
positive if the point is outside the object. Here are some examples:
up <0,1,0> right <1.33333,0,0>
The main use of the camera model in ray tracing is to compute pixel rays. For the simplified model that we use, we have derived formulas in the class. Warning: POV-Ray has a relatively complicated camera model with many interdependent parameters; we are using a small subset. You may end up confused if you try to sort out the complete model from the POV-Ray documentation, so I suggest using this description together with the lecture notes.
Under many circumstances just two vectors in the camera statement are all you need: location and look_at. For example:
camera {
location <3,5,-10>
look_at <0,2,1>
}
In addition to the standard parameters, a camera may have a transformation attached to it; the parser reads it, if there is one, but you are not required to implement it.
rotate <0,45,0> // rotate around Y axis by degrees
The scale is a nonuniform scale and its argument is a vector of three scale factors in x,y and z directions. The translate takes the translation vector as an argument. The parser reads all transformations and converts them to 4 by 4 matrices.
It is possible to transform each object before rendering, and this is the most efficient approach. However, deriving the necessary transformations of the object parameters may be tricky. A simpler approach is to transform the ray to the coordiante system in which the object is defined. This means that each time we intersect a ray with an object that has a transformation attached to it, we need to apply the inverse of the transformation to the ray. Thus, it makes sense to store both the transformation matrix and its inverse together with the object.
Create a test image from the file that is provided on the Web page. Also, create 3 images of a 1x1 square in the XY plane for the following camera parameters:
camera {
location <0.5,0.5,1>
look_at <0.5,0.5,0>
angle 90
}
camera {
location <2,1,2>
right <1.5,0,0>
look_at <1,1,1>
angle 90
}
camera {
location <2,2,2>
look_at <0,0,0>
up <1,0,0>
angle 90
}
It is convenient to represent each shape with a class derived from a base shape class, and use virtual methods for operations like intersecting the object with a ray. You will need to perform a lot of vector arithmetics. Unless you already have it, write a small library of standard vector functions (addition, multiplication by a constant, dot product, cross product etc.) using the code provided with the parser as the starting point.
{, },
<, >. Numbers and identifiers have
to be separated from each other by whitespace characters
(space, tab, newline), but need not be separated from the comma or
{, }, <, >.
The parser calls getToken, determines the object type from the first token and calls the function that parses the object definition for each object. These functions, in turn, may call functions for parsing transform definitions, vectors etc. Only simple error processing is done: if there is a syntax error in the file, the parser prints a message and aborts.
In general, each object type (sphere, cone, cylinder, polygon, quadric) should correspond to a class in your program. Any object may have a transformation attached; assemble all transformations for each object into a single matrix, and transform the ray into the object's coordinate system when computing intersections. For this, you need to multiply both the direction and the origin of the ray by the inverse of the transformation matrix.
Create a data structure to describe rays. Initially, it should contain at least three fields:
double[3] orig; // origin of the ray double[3] dir; // direction of the ray float[3] color;
Note that we use doubles for all fields except color. In the geometric calculations, double precision is preferable, although the geometry itself can be described by floats. In lighting calculations single precision is sufficient. Feel free to use your 3D vector classes for the fields instead of double[3]. Implement the intersection algorithms for polygons and quadrics described in class. Boxes can be treated as collections of polygons.