ECS175

ECS 175 Introduction to Computer Graphics

Spring Quarter 2000




Programming Assignment 2: 3D Viewing


Due Date: Tuesday, May 9, 2000

This assignment extends the scan converter you implemented last time. You will add three-dimensional viewing capabilities to your graphics libraries. This means, you have to implement the essential parts of OpenGL's geometry pipeline to render images of polygons in space.

Input to the Geometry Pipeline

For this assignment, scene files will describe sets of polygons in space. The polygons are specified in the same way as in assignment 1, but this time each specified vertex has three-dimensional (x, y, z) coordinates. The polygons specified will still be convex, and you can assume that they will be planar as well. This is what a polygon description will look like (in GL calls):
glBegin(GL_POLYGON);

glColor3f(r1, g1, b1);
glVertex3f(x1, y1, z1);

glColor3f(r2, g2, b2);
glVertex3f(x2, y2, z2);

/* ... */

glColor3f(rn, gn, bn);
glVertex3f(xn, yn, zn);

glEnd();
The same in the input file format:
beginPolygon
color 1.0, 0.0, 0.0
vertex -1.0, 2.0, 5.0

vertex 2.0, 2.5, 4.0

vertex 4.0, 3.0, 1.0

...

vertex 5.0, 3.0, 2.0

end
Again, the only difference to the old file format is, that the vertex command now takes three floating-point values.

The Geometry Pipeline Stages

To render an image of a polygon in space, OpenGL has to transform and project the polygons onto the screen. In OpenGL, transformation is done in four steps:
  1. The modelview transform. This transformation, that can be an arbitrary projective transformation (expressed by a 4-by-4 matrix), transforms polygons from the coordinates they are given in into the so-called camera or eye coordinate system. (In later assignments, lighting will be performed in this system.)
  2. The projection transform. This transformation, usually either an orthogonal or a perspective transformation (expressed by a 4-by-4 matrix as well), transforms polygons from camera or eye coordinates into clip coordinates. After this transformation, all polygons are clipped to the view volume, i.e., all parts of a polygon that would fall outside the graphics window are cut away.
  3. The perspective division. This step converts polygons from clip coordinates to normalized device coordinates by dividing by the homogeneous weight.
  4. The viewport transformation. This transformation (that you already implemented for assignment 1) transforms polygons from normalized device coordinates into window coordinates.

For this assignment, you have to implement all functions and OpenGL calls necessary to set the transformation matrices in the four stages, and you have to write the code to do all the transformations. The output of the geometry pipeline (but the last stage) will be polygons in 2D coordinates - this is what the input for assignment 1 was, so you should be able to integrate the code you already wrote with the new code easily.

Setting up the Geometry Pipeline

OpenGL provides several functions to modify the geometry pipeline. The first command selects which of the two transformations is going to be affected by later commands:
glMatrixMode(mode)
If this function is called with the parameter GL_MODELVIEW, later commands will affect the modelview transformation. If it is called with the parameter GL_PROJECTION, later commands will affect the projection transformation.
The second batch of commands modifies the transformation selected by glMatrixMode:
glLoadIdentity()
This command resets the transformation to the 4x4 identity matrix.
glLoadMatrixf(m)
This command sets the transformation to the given 4x4 matrix m, specified as a two-dimensional array of type GLfloat. The matrix m is given in column-major order, that is, the array entries are arranged as follows:
m[0]m[4]m[8]m[12]
m[1]m[5]m[9]m[13]
m[2]m[6]m[10]m[14]
m[3]m[7]m[11]m[15]
glMultMatrixf(m)
This command multiplies the current transformation from the right with the given 4x4 matrix m, specified in the same way as in the glLoadMatrix call.
The third batch of command provides easy ways to multiply the current transformation from the right with often-used special transformations:
glTranslatef(translateX, translateY, translateZ)
Multiplies the current transformation with a tranlating transformation, moving by the given distances in the three primary directions.
glScalef(scaleX, scaleY, scaleZ)
Multiplies the current transformation with a scaling transformation, scaling with the given factors in the three primary directions.
glRotatef(angle, axisX, axisY, axisZ)
Multiplies the current transformation with a rotating transformation. The current transformation will be rotated by angle degrees in counter-clockwise direction, around the axis starting at the origin and going through the point (axisX, axisY, axisZ).
glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far)
This command is usually only used to modify the projection matrix (glMatrixMode(GL_PROJECTION)). It will create an orthogonal projection matrix and multiply the current transformation from the right with it. The parameters to the function specify the positions of the left, right, bottom, top, near and far clipping planes, respectively.
glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far)
This command is also only used to modify the projection matrix. It will create a perspective projection matrix and multiply the current transformation from the right with it. The parameters have similar meanings than those in the call to glOrtho.

Specifying Primitives

Polygons are specified to OpenGL the same way as in assignment one; only this time, each vertex has x, y and z coordinates. Vertices can be specified using two commands:
glVertex2f(x, y)
This command specifies a vertex with x and y coordinates only. The z coordinate of vertices is set to zero.
glVertex3f(x, y, z)
This command fully specifies a vertex with x, y and z coordinates.

Clipping

As mentioned above, polygons have to be clipped to the view volume (the volume enclosed by the left, right, bottom, top, near and far clipping planes) before they can be projected to the graphics window. You can implement any polygon clipping algorithm you want to do this; but keep in mind that the vertices of polygons are given as homogeneous coordinates at this point.

Hidden Surface Removal

In order to get a correct rendering of a three-dimensional scene, polygons which are invisible because they are hidden by other polygons closer to the viewpoint must not be rendered. There are several algorithms to solve this problem, and OpenGL uses one of the simplest ones - Z buffering.

During scan conversion, the z coordinates of polygon vertices in normalized device coordinates are interpolated across a polygon's interior as pixels are generated, and the z value for each drawn pixel is stored in a buffer (the Z buffer). Before a pixel is drawn to the graphics window, the scan converter checks whether the value currently stored in the Z buffer at the pixel's position is closer to the viewpoint than the new value; if this is the case, the pixel is not drawn and the value in the Z buffer is not changed. This strategy will cause "farther away" polygons to be overdrawn by closer polygons; the effect is, that the final image only contains the projection of the closest polygons.

To implement this algorithm, your graphics libraries have to keep an additional buffer of the size of the graphics window which will store the "depth values" of the pixels currently on the graphics window. Before rendering can start, the Z buffer has to be cleared in addition to the graphics window. In analogy to the graphics window being cleared to black, the Z buffer is cleared to the "farthest" possible Z value. The following OpenGL functions are used to maintain the Z buffer:

glViewport(x, y, width, height)
This command (that you already know from assignment one) tells the graphics library how large the graphics window is. This is the perfect place to dynamically allocate an array for the Z buffer.
glClear(mask)
This command tells the graphics library to clear the graphics window and/or associated buffers (like the Z buffer). The mask parameter is actually a bit field. Depending on which bits are set, the following actions are performed:
GL_COLOR_BUFFER_BIT
Clears the graphics window to the background color (black). The framework provides a function clearWindow() that can be called by the graphics library for this task.
GL_DEPTH_BUFFER_BIT
Clears the Z buffer by setting all entries to the "farthest away" Z value.
A typical way to call this function is glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); which will clear both the graphics window and the Z buffer.
glEnable(cap)
glEnable(...) is a multi-purpose function that switches features (capabilities) of the GL state machine on. In this assignment, it will only be called with the parameter GL_DEPTH_TEST, which will enable Z buffering. Also, Z buffering is enabled by default.
glDisable(cap)
This companion to the glEnable function turns a selected feature off. Again, it will only be called with the parameter GL_DEPTH_TEST, which will turn Z buffering off. (With Z buffering turned off, no Z value test will be performed, and farther away polygons can overdraw closer polygons.)

Changes to the Scanner

To accomplish 3D viewing, you have to extend your scanner. These are the new commands the scanner has to understand:
vertex x, y, z
This command is not new, but now it will expect three floating point values for the x, y and z coordinates.
matrixModelView
This command will set the matrix mode to GL_MODELVIEW.
matrixProjection
This command will set the matrix mode to GL_PROJECTION.
matrixIdentity
Sets the transformation to the identity transformation.
matrixTranslate x, y, z
Translates the current transformation by the given distances.
matrixScale x, y, z
Scales the current transformation by the given factors.
matrixRotate angle, axisX, axisY, axisZ
Rotates the current transformation angle degrees counter-clockwise around the axis through the origin and the given point.
matrixOrtho left, right, bottom, top, near, far
Multiplies the current transformation with an orhogonal projection transformation.
matrixFrustum left, right, bottom, top, near, far
Multiplies the current transformation with a perspective projection transformation.
All the matrix commands can only appear outside a beginPolygon / end bracket. For those who like context-free grammars, here is how the grammar changes:
; Vertex definitions (with x,y,z values):
vertex: "vertex" float "," float "," float

; Matrix modification commands:

matrixCommand: "matrixModelView"
             | "matrixProjection"
             | "matrixIdentity"
             | "matrixTranslate" float "," float "," float
             | "matrixScale" float "," float "," float
             | "matrixRotate" float "," float "," float "," float
             | "matrixOrtho" float "," float "," float "," float "," float "," float
             | "matrixFrustum" float "," float "," float "," float "," float "," float

; A command that can appear outside a beginPolygon / end bracket:
outerCommand: color
            | matrixCommand
            | polygon

Changes to the User Interface

Add some widget to your user interface that allows the user to select whether to enable or disable Z buffering. You could use a popup menu like the one to select filled or outlined polygons.

Create a 3D Scene File

We will provide some test scene files you can use to check if your library works correctly; but it is also part of the assignment that you model at least one scene yourself. You are free to choose what you want, but it has to be something "real" and not just a bunch of polygons in space. For example, things you could consider are teapots (no, that has been done before...), room interiors, machine parts, flowers, animals, people, spacecraft (if you want to work at ILM later), etc.

Where to Start?

As you found out by now, this assignment is quite long. 3D viewing involves a lot of necessary steps (transformations, clipping, hidden surface removal) which all have to be programmed at once; a partial 3D viewing program would not make sense.

Therefore, it is crucial that you schedule enough time for this assignment, and that you tackle it in a systematic way. Here is what we recommend that you do:

  1. Understand how 3D viewing works, and how 3D transformations and projections can be expressed by using homogeneous vectors and homogeneous matrices. You might get away with expressing all operations only using "normal" affine vectors, but it will be a lot more complicated.
  2. Figure out which new variables you have to add to the GL state, and how you have to change / extend the data structures you use to store polygon vertices.
  3. Implement the geometry pipeline first. You don't have to implement all the matrix arithmetics yourself; there is a pretty complete class framework to do exactly this on the graphics web page. You can download those classes and integrate them into your programs.
  4. Extend the scanner to read the new grammar.
  5. Implement a 3D polygon clipping algorithm of your choice (don't forget that it has to be able to handle homogeneous coordinates!). We recommend the Sutherland-Hodgman algorithm that clips polygons to a cube by clipping them to each of the six faces of the cube in turn. This way, you only have to implement clipping a polygon to a plane, and can then use that algorithm six times. Also remember that you can assume all polygons given to you to be planar and convex.
  6. Extend your scan converter such that it interpolates the Z coordinates of polygon vertices across the interior of polygons. Then you can check the Z coordinate of each generated pixel against the Z buffer to determine whether a pixel has to be drawn or not.