CPSC 324 Fundamentals of Computer Graphics Spring 2006

CPSC 324 Renderer Documentation

The first three projects for this course involve building a renderer for 3D scenes. (Actually, you'll be building several renderers which produce different styles of views.) While you will be filling in the essential core of the renderer, you have been provided with a lot of support code to handle necessary (but less central) parts of the renderer (such as reading scene files, handling the user interface, and providing basic linear algebra functionality). This document describes the provided code. The code is commented, and you should supplement your reading of this document with a perusal of the header files named.


Directory Organization

The renderer code (both the support code and the parts you will be writing) is arranged into the following directories:

Not all of the classes and methods present in the support code are described below, but those which you are most likely to need to use are included. Refer to the header files for additional details (including pre- and post-conditions).


camera Directory

The camera directory contains the virtual camera. The virtual camera encapsulates all but the modeling and workstation transformation steps of the 3D viewing pipeline. (The modeling transformation, derived from the scene graph, is stored with each scene object. The workstation transformation is computed when the scene is rendered, because window information is only provided at that point.)

Camera

Camera (camera.h) defines an interface for all virtual cameras and implements projection-independent functionality. (Subclasses will provide projection-dependent functionality.) The camera encapsulates all of the parameters and transformations involved in the viewing transformation, projection, and view volume normalization parts of the viewing pipeline.

The first set of methods deal with the camera's position and orientation. All of the values are in world coordinates.

    virtual void setVRP ( const Point4d & vrp );
    virtual Point4d moveVRP ( const Point4d & vrp );
    virtual void setLookAt ( const Point4d & lookat );
    virtual void setVUP ( const Vector4d & vup );
    virtual void setPositionAndOrientation ( const Point4d & vrp,
					     const Point4d & lookat,
					     const Vector4d & vup );

    virtual Point4d getVRP () const;
    virtual Point4d getLookAt () const;
    virtual Vector4d getVPN () const;
    virtual Vector4d getVUP () const;

Of particular note is moveVRP: it moves the VRP to the specified location, updating the look at point so that the VPN remains unchanged. This is convenient for moving the camera while keeping it pointed in the same direction (which means that the VPN doesn't change and thus the view plane remains the same). Using setVRP instead will cause the camera to move but to continue pointing at the same place (changing the VPN and thus the orientation of the view plane).

The next methods deal with projection. All quantities are in VC.

    virtual void setPRP ( const Point4d & prp );
    virtual void setDOP ( const Vector4d & dop );
    virtual void setProjectionAngles ( double alpha, double phi );

    virtual Point4d getPRP () const;
    virtual Vector4d getDOP () const;
    virtual double getAlpha () const;
    virtual double getPhi () const;

    virtual Vector4d getProjector ( const Point4d & point ) const = 0;

These methods are common to both parallel and perspective projections (though the implementation of getProjector depends on the type of projections). All projections are characterized by the PRP and the DOP vector or the equivalent alpha and phi values. getProjector returns the vector describing a projector which passes through the specified point. Since there are two such vectors (differing only in sign), it should return the vector with a non-negative z component. (Either vector may be return if the z component is 0; this case does not generally occur because it means that the projector is parallel to the film plane so no image would be seen.) It may return (0,0,0) as the vector if the point is the same as the PRP.

The third set of methods deal with the specification of the view volume. Since the view volume is defined in view coordinates (i.e. with respect to the camera), all of these values are in view coordinates.

    virtual void setViewPlaneZ ( double viewPlaneZ );
    virtual void setFrontClipZ ( double frontClipZ );
    virtual void setBackClipZ ( double backClipZ );

    virtual void setViewAspectRatio ( double viewAspectRatio );
    virtual void setViewWidth ( double viewWidth );

    virtual double getViewPlaneZ () const;
    virtual double getFrontClipZ () const;
    virtual double getBackClipZ () const;

    virtual double getViewAspectRatio () const;
    virtual double getViewWidth () const;
    virtual double getViewHeight () const;

    virtual Point4d getViewCenter () const;
    virtual vector<Point4d> getViewCorners () const;

The fourth set of methods construct and return individual matrices involved in the viewing pipeline.

    virtual Matrix4d getViewTranslateMatrix () const;
    virtual Matrix4d getViewRotateMatrix () const;
    virtual Matrix4d getProjShearMatrix () const = 0;
    virtual Matrix4d getProjSquareUpMatrix () const = 0;
    virtual Matrix4d getNormTranslateMatrix () const;
    virtual Matrix4d getNormScaleMatrix () const;

The getView*Matrix methods return the translation and rotation parts of the viewing transformation, the getProj*Matrix methods return the shear and square-up parts of the projection, and the getNorm*Matrix methods return the translation and scale parts of the view volume normalization. Note that the getProj*Matrix methods are pure virtual - they must be implemented in subclasses, since they depend on the type of projection.

The following method constructs and returns a portion of the viewing pipeline, from world coordinates up to and including the matrix indicated by the parameter (whose value will be one of the specified constants). This method is used by the "world" window to display intermediate stages of the viewing pipeline.

    virtual Matrix4d getStageMatrix ( ViewStage stage ) const;

    enum ViewStage { STAGE_WORLD,        // WC (no transformations applied)
                     STAGE_VIEW_TRANS,   // translation part of viewing transform
                     STAGE_VIEW,         // VC (complete viewing transform applied)
                     STAGE_PROJ_SHEAR,   // shear part of projection
                     STAGE_PROJ,         // PC (complete projection applied)
                     STAGE_CVV_TRANS,    // translation part of normalization
                     STAGE_CVV };        // in canonical view volume

Finally, the camera provides a method for constructing the entire world-coordinates-to-canonical-view-volume transformation matrix.

    virtual Matrix4d getCameraMatrix () const;

This should return the same matrix as getStageMatrix(STAGE_CVV). It does not include the modeling and workstation transformations.


gui Directory

The gui directory supports the renderer's user interface.

Drawing

The Drawing class (drawing.h) provides routines for drawing to the screen. Its main purpose is to hide the complexity of actually getting pixels on the screen. All of the methods are static, and draw to the current drawing window. (Even though there are multiple drawing windows in the program, the rendering code never specifies which window to draw into - it merely uses Drawing to draw to the current window, and the user interface handles making the correct window "current".)

Drawing provides routines for setting the current drawing color, and for drawing several kinds of primitives (points, lines, ovals, and text). All coordinates are screen coordinates, though the drawing window interprets (0,0) as the lower left corner of the window instead of the upper left. This is different from most drawing-in-windows, but it means the viewing pipeline doesn't have to worry about flipping y-coordinates.

    static void setColor ( double red, double green, double blue );
    static void setColor ( const Color & color );

    static void drawPoint ( int x, int y );
    static void drawLine ( int x1, int y1, int x2, int y2 );
    static void fillOval ( int x, int y, int rx, int ry );
    static void drawText ( const string & str, int x, int y );

Drawing commands are buffered (for efficiency), and do not appear on the screen until flushed. The support code flushes the drawing commands when the entire view has been rendered (unless the -flush commandline option is specified, in which case operations are flushed immediately). As a result, you shouldn't need to call Drawing's flushing routines directly - but if you need them, checking out drawing.h.

rendermain.cc

The main program is defined in rendermain.cc. The main program handles setting up the windows and user interface, and processes user interaction (e.g. mouse actions and keyboard presses). The user interface is implemented using the OpenGL graphics package, so don't worry if rendermain doesn't make much sense.


linalg Directory

The linalg directory provides the necessary mathematics involving points, vectors, and matrices. << is defined for points, vectors, and matrices.

Point4d

Point4d (linalg.h) defines a point in 3D homogeneous coordinates. Points with an h coordinate of 0 are degenerate and should be avoided. Retrieve and set individual coordinates of a point using [] e.g. p[2] = 4 sets the z coordinate of point p to 4. You can compare two points for equality using ==, and this works correctly for homogeneous coordinates (e.g. comparing (1,2,3,1) and (2,4,6,2) using == will return true, because these points are considered to be the same). The relevant arithmetic operations are defined for points (point+vector, point += vector, point-vector, point -= vector, point-point), and are defined correctly for homogeneous coordinates (e.g. adding the vector (1,1,1) to the point (2,4,6,2) will return a point equivalent to the one obtained by adding the same vector to (1,2,3,1)). Homogenize a point with p.homogenize().

Vector4d

Vector4d (linalg.h) defines a 3D vector. Since the h coordinate of a vector is always 0, this is not explicitly stored. Retrieve and set individual components of a vector using [] e.g. v[2] = 3 sets the z component of vector v to 3. You can compare two vectors for equality using == (two vectors are equal if they have the same length and point in the same direction). The relevant arithmetic operations are defined for vectors (-vector, vector+vector, vector += vector, vector-vector, vector -= vector, vector*scalar, vector *= scalar, scalar*vector, vector/scalar, vector /= scalar). Get the length of a vector with v.length() and normalize it with v.normalize(). Free functions dot and cross are provided for dot product and cross product, respectively.

Matrix4d

Matrix4d (linalg.h) defines a 4x4 matrix. The default constructor creates the identity matrix, and a second constructor allows a matrix to be constructed with specified entries. Retrieve and set individual components of the matrix using [][] e.g. m[2][3] = 10 sets the element in row 2, column 3 of the matrix to 10. The relevant arithmetic operations are defined for matrices (matrix*point, matrix*vector, matrix*matrix) - of note is that matrix*point returns a homogenized point. Get the transpose of a matrix with m.transpose() and the inverse with m.invert(). There are also three static methods which are provided as a convenience for creating special kinds of matrices: Matrix4d::getTranslateMatrix, Matrix4d::getRotateMatrix, and Matrix4d::getScaleMatrix.


render Directory

The render directory contains everything relevant to producing an image from a scene description, including support for visible surface determination, lighting models, and the code to actually compute and display the image.

Renderer

Renderer (renderer.h) defines an interface which must be supported by any class which produces an image of a scene. It defines a single method which renders the scene:

    virtual void render ( const Scene & scene, const LightingModel * lighting,
			  int x, int y, int width, int height ) const = 0;

It must be overridden in a subclass to do any actual work.

Lighting

Lighting (lighting.h) defines an interface for a lighting model (to determine the illumination for a particular point in the scene) and provides some convenience routines related to the implementation of a lighting model.

The main method is the following:

    virtual Color illuminate ( const Point4d & point, const Vector4d & normal,
			       const Material & material, const Scene & scene,
			       const map<Light *,Vector4d> & L, 
			       const map<Light *,Vector4d> & R,
			       const Vector4d & V ) const = 0;

illuminate method is what implements the lighting model; it must be overridden in a subclass.

Three static methods are provided. These methods aren't strictly part of a lighting model, but they compute information used in lighting.

    static Point4d computeViewer ( const Scene & scene );
    static void computeLR ( const Point4d & point, const Vector4d & normal,
			    const Scene & scene,
			    map<Light *,Vector4d> & L,
			    map<Light *,Vector4d> & R );
    static Vector4d computeV ( const Point4d & point, 
			       const Point4d & viewer );

The last method provides support for turning on and off shadowing computations.

    virtual void enableShadows ( bool shadows );

"Shadows enabled" means that objects between the light and the point being lit - which may partially or totally block light from reaching the point being lit - are taken into account. This method does nothing on its own (other than setting the value of the protected instance variable shadows_) - it is up to illuminate to check the value of shadows_ and act accordingly.


scene Directory

The scene directory contains classes relevant to the description and geometry of a scene. The << operator is defined for Segment, Triangle, all lights, and Color.

Scene

Scene (scene.h) contains the full scene description - it stores the elements of the scene (objects, lights, and camera) and handles reading in the scene description from a file. The main methods of interest are accessors:

    Camera * getCamera () const;

    int numObjects () const;
    SceneObject * getObject ( int n ) const;

    int numLights () const;
    Light * getLight ( int n ) const;

The remaining public methods probably don't need to be called directly:

    bool loadScene ( const string & filename );
    void initialize ();
    void clear ();

The loadScene method reads a scene description from a file. initialize handles any precomputation that should be done once per scene (such as computing normal vectors); it is called automatically by loadScene, but must also be called manually if the scene's camera parameters are changed. clear removes all scene elements from the scene; it is also called automatically by loadScene prior to reading the new scene description.

Object, SceneObject, and Subclasses

Object (object.h) is the top-level class for all things which contain a modeling transformation and a material. It has two methods:

    virtual const Matrix4d & getTransform () const;
    virtual const Material & getMaterial () const;

getTransform returns the modeling transformation for the object. getMaterial returns a material which encapsulates the object's color, shininess, transparency, and other display properties.

Object is an abstract class and is useful primarily to avoid repeating the instance variables and accessors for the modeling transformation and material, since many classes need to store this information.

SceneObject (sceneobject.h), a subclass of Object, defines the interface for all objects which can be found in a scene (excluding cameras and lights). It is also an abstract class - its purpose is to define the six methods which must be implemented by all scene objects.

The first method is used by wireframe renderers:

    virtual Wireframe * wireframe () = 0;

It returns a wireframe outline of the surface of the object (essentially a collection of line segments).

The next method is used by polygon pipeline renderers:

    virtual TriangleMesh * tessellate () = 0;

It returns a triangle mesh approximating the surface of the object.

The remaining methods are used in raytracing:

    virtual vector<double> intersectAll ( const Point4d & p0, 
                                          const Vector4d & u ) const = 0;
    virtual double intersect ( const Point4d & p0, 
                               const Vector4d & u ) const = 0;

    virtual Vector4d normal ( const Point4d & point ) const = 0;

    virtual BoundingVolume * getBoundingVolume () = 0;

intersectAll returns all of the intersections between a WC ray and the object, while intersect returns the s value for the first intersection between a WC ray and the object. normal returns the WC outward surface normal for a point on the surface of the object. getBoundingVolume returns a WC bounding volume for the object.

SceneObject has a number of subclasses which implement specific kinds of objects: Cone (cone.h), Cube (cube.h), Cylinder (cylinder.h), and Sphere (sphere.h). (Additional object types may be added.) All of the objects are canonical forms of the object:

Specify transformations in the scene file to change the position, orientation, and/or size of these objects.

Wireframe and Segment

The Wireframe class (wireframe.h), a subclass of Object, represents a wireframe view of an object. It consists of a collection of Segment objects (wireframe.h) which outline the object, plus the object's material and modeling transform. (Wireframe coordinates are in MC.) It provides several accessor methods:

    int numSegments () const;
    const Segment & getSegment ( int n ) const;

The Segment class (wireframe.h) supports the operator [] for retrieving the endpoints of the segment:

    const Point4d & operator[] ( int index ) const;

Like C++ arrays, the endpoints are indexed starting with 0 e.g. s[1] returns the second endpoint of the segment.

TriangleMesh and Triangle

TriangleMesh (trimesh.h) is a triangle mesh - a surface made up of triangles. The triangle mesh coordinates are in MC; the mesh is a subclass of Object and includes the material and modeling transform. It provides several accessor methods:

    virtual int numPoints () const;
    virtual const Point4d & getPoint ( int n ) const;

    virtual int numTriangles () const;
    virtual const Triangle & getTriangle ( int n ) const;

Triangle (trimesh.h) is a subclass of Object which represents a triangle. The operator [] is used for retrieving the points of the triangle:

    const Point4d & operator[] ( int index ) const;

Like C++ arrays, the endpoints are indexed starting with 0 e.g. tri[1] returns the second point of the triangle.

About the implementation of Triangle and TriangleMesh: Within the mesh, each point is assigned an integer index, and it is this integer index which is stored internally in Triangle (not the actual Point4d for the point). There are three reasons for this. One is efficiency, so that for points which are part of several triangles, only integers are stored repeatedly instead of the (larger) Point4ds. Another is precision - since only a certain number of bits are used to store doubles, only a certain number of decimal places can be stored. This can manifest itself in several ways, such as printing out a value which should be 1 and getting 0.99999999 or getting false for an == comparison when you expect the result to be true. Storing actual Point4ds - with double values for x, y, and z - for each point makes it harder to determine when two points in different triangles are meant to be the same point or not. Related to this is the third reason - sometimes a triangle mesh is meant to approximate a curved surface and sometimes it is used to exactly represent a polygonal surface. In the first case, it is important to identify points that are shared by multiple triangles because the normals for those triangles are averaged to compute a vertex normal for the point which is between the normals for each triangle; in the second case, edges between triangles should form sharp creases and the normal for each point should be the triangle's normal rather than the averaged surface normal. With Point4ds, it is hard to know which case applies; with integer IDs, use the same ID when the point is to be shared and different IDs when the point is along a crease.

Light and Subclasses

Light (light.h) defines a light in the scene. It is an abstract class providing functionality common to all kinds of lights. All lights have a color and may be on or off:

    virtual bool on () const;
    virtual Color getColor () const;

In addition, there is a protected constructor:

    Light ( bool on, double intensity, const Color & color );

Legal intensity values are between 0 and 1.

Internally, lights have both a Color and an intensity. The color returned by getColor is the product of the intensity and the color specified when the light was constructed. Yes, the intensity is redundant, but it makes it a little easier to adjust the brightness of lights in the scene.

AmbientLight (light.h), a subclass of Light, defines an ambient light. It adds only constructors:

    AmbientLight ();
    AmbientLight ( bool on, double intensity, const Color & color );

The first constructor creates an ambient light which is on, has an intensity of 0, and the color white.

PositionalLight (light.h), another subclass Light, defines a light with a position. It is an abstract class. It adds two accessors:

    Point4d getPosition () const;
    double getAttenuation ( int i ) const;

getPosition returns the WC position of the light. getAttenuation returns the constants for the terms of the attenuation function.

In addition, PositionalLight has a protected constructor:

    PositionalLight ( bool on, double intensity, const Color & color,
		      const Point4d & position, 
		      double att0, double att1, double att2 );

PointLight (light.h), a subclass of PositionalLight, defines a point light source which radiates equally in all directions. It adds only public constructors:

    PointLight ( const Point4d & position );
    PointLight ( bool on, double intensity, const Color & color,
		 const Point4d & position, 
		 double att0, double att1, double att2 );

Material

Material (material.h) defines a material - materials encapsulate all of the properties which affect the appearance of a surface including its color, shininess, transparency, and reflectiveness.

    Material ();
    Material ( const Color & color, double specular,
	       double ka, double kd, double ks, double ns,
	       double reflection, double transparency, 
	       double refractionIndex );

The default constructor creates a default material.

The remaining methods are accessors for the Material's properties:

    Color getDiffuseColor () const;
    double getSpecularFrac () const;
    double getAmbientRefl () const;
    double getDiffuseRefl () const;
    double getSpecularRefl () const;
    double getSpecularExp () const;
    double getTransparency () const;
    double getReflectivity () const;
    double getRefractionIndex () const;

Color

Color (color.h) defines an RGB color whose color components are between 0 and 1. Capping is not performed, so you must ensure that the color components are between 0 and 1 before passing them to the constructor.

There are two constructors:

    Color ();
    Color ( double red, double green, double blue );

The default constructor creates the color black.

The operator [] is used for retrieving the color components:

    const double & operator[] ( RGBComponent which ) const;

    enum RGBComponent { RED = 0, GREEN = 1, BLUE = 2 };

Note that the preferred way to access the color components is with RED, GREEN, and BLUE e.g. color[GREEN] to get the green color component of the object color - if you want to use integers instead, you must cast them to RGBComponent e.g. color[(RGBComponent)0] to get the red color component.

BoundingVolume

BoundingVolume (boundvol.h) defines an easy-to-intersect shape (it happens to be a sphere, though this is an implementation detail). Bounding volumes are used to speed up the intersection computations in raytracing and in handling shadows. The bounding volume coordinates are assumed to be in WC, and the ray provided to the isIntersected method must also be in WC. It has one method of interest:

    bool isIntersected ( const Point4d & p0, const Vector4d & u ) const;

util Directory

The util directory contains several useful routines.

defs.h

defs.h contains the definition for the constant EPSILON:

const double EPSILON = 1e-8;

This constant is useful for comparing two doubles for equality. Because of precision issues (a fixed number of bits for storing doubles means a limited number of decimal places can be stored), using == to compare two doubles will often return false when it should return true. Instead, treat two doubles as equal if they are within EPSILON of each other e.g.

  double a, b;
  // a, b are given values and manipulated
  if ( fabs(a-b) < EPSILON ) {           // test if a == b
    ...
  }

fabs returns the absolute value.

utiltemplates.cc

utiltemplates.cc contains template functions, in particular, a template function for retrieving an element from a map object which is const:

template<typename K, typename T>
const T & mapGet ( const map<K,T> & themap, const K & key );

[] doesn't work for this task (only for storing values in the map) due to various technicalities about what is const and what isn't. The short version? Use the mapGet function to retrieve elements from a map. Also keep in mind that since mapGet is a template function, you use it just like any other function but you'll need to #include "utiltemplates.cc" in files where you use mapGet (and utiltemplates.cc is not listed in the Makefile).

debug.h

debug.h defines a debugging mechanism - several debugging flag constants, and a DEBUG routine for printing debugging messages. The purpose of this mechanism is to facilitate turning selected debugging messages on and off without having to remove/comment out the messages. You are encouraged to use this debugging mechanism instead of using cout to print debugging messages. Two steps are required:

See debug.h to find out what debugging flags have been defined.


Valid HTML 4.01!