CS 381 Fall 2012  >  Lecture Notes for Tuesday, September 11, 2012

CS 381 Fall 2012
Lecture Notes for Tuesday, September 11, 2012

A Little about Transformations

Overview

As vertices pass through the Vertex Processing portion of the pipeline, they are acted on by various transformations. These modify the coordinates of vertices according to certain rules. The first is the model/view transformation. We use this to position, orient, and size the objects we draw, by moving, rotating, and scaling vertex coordinates. The second is the projection transformation. We use this to project objects onto the plane of the framebuffer.
Image not displayed: detail of Vertex Processing portion of pipeline

Transformations are OpenGL states; they are affected by transformation commands. Which transformation is affected by a command, is determined by the current matrix mode (so called because transformations are stored as matrices). The matrix mode is a GL state set by the glMatrixMode command.

glMatrixMode(GL_PROJECTION);

The above sets the matrix mode, so that future transformation commands modify the projection transformation.

glMatrixMode(GL_MODELVIEW);

And the above is the equivalent for model/view (note that GL thinks “modelview” is one word). Strong suggestion: When a callback, or the initialization function, ends, always ensure that the matrix mode is GL_MODELVIEW. Then you can execute transformation commands in some other function, without worrying about what the matrix mode is.

We will discuss the projection transformation in detail later in the semester. Now we take a closer look at the model/view transformation.

Some important transformation commands are glLoadIdentity, glTranslate*, glRotate*, and glScale*. In the display function we can use these to position, orient, and size an object with code like this:

glLoadIdentity();
glTranslated(x, y, z);
glRotated(angle_degs, 0.,0.,1.);
glScaled(scale, scale, scale);
// Do drawing here

Order matters here! The above order is what you want if you are doing simple placement of an object in the scene. (Later, we will do more complicated things, e.g., a flight simulator, and we will order the transformation commands differently.)

Details of Transformation Commands

The glLoadIdentity command take no parameters. It resets the transformation to a do-nothing state. The other commands modify the existing transformation.

The glTranslate* command does a translation.

The glRotate* command rotates vertices through a specified angle about a line through the origin.

The glScale* command scales about the origin.

Use the model/view transformation to do all positioning, orientation, and sizing of objects.

See anim.cpp.

Later in the semester, we will look at the math behind the above transformation commands.

Matrix Stacks

For each OpenGL transformation, there is an associated stack (here we use “stack” in the data structure sense). The top of the stack is the current transformation matrix; this is the matrix that affects vertices passing through the pipeline, and it is the one modified by transformation commands.

Two commands allow stack manipulation: glPushMatrix and glPopMatrix.

Function glPushMatrix takes no parameters. It pushes a copy of the current matrix on the top of the stack.
Image not displayed: glPushMatrix effect on stack

Function glPopMatrix also takes no parameters. It pops the top matrix off.
Image not displayed: glPopMatrix effect on stack

Thus, we can save & restore our transformations by writing glPushMatrix-glPopMatrix pairs around any code that changes and uses a matrix.

// Set up initial transformation matrix here

glPushMatrix();
// Modify transformation matrix here
// Use modified transformation matrix here
glPopMatrix();  // Restore unmodified transformation matrix

Animation

Moving an Object

By animation we mean producing the illusion of motion by rapidly displaying images with small changes from one image to the next. Each such image is called a frame.

We do animation in much the same way we do interaction; we simply need to change the scene all the time, instead of in response to some user event. Thus, we change the scene in the idle function. We communicate between idle and display just as we did between keyboard and display: using global variables. (Recall: DICE = Declare, Initialize, Change, Employ.)

We can gradually move an object by adding a small number to its position each time idle is called.

xpos += xvel;
ypos += yvel;

glutPostRedisplay();

The above produces different speeds on different systems. We can make speed more uniform by using elapsed time. A useful function call is glutGet(GLUT_ELAPSED_TIME), which returns the amount of time since GLUT was initialized, in milliseconds. We can declare a global double named savetime to store the time, in seconds, of the previous idle function call. Initialize savetime in the initialization function as follows.

savetime = glutGet(GLUT_ELAPSED_TIME)/1000.;

In the idle function, we can compute the elapsed time since the previous idle call and use this to move an object.

// Compute elapsed time since last movement
double currtime = glutGet(GLUT_ELAPSED_TIME)/1000.;
double elapsedtime = currtime – savetime;
savetime = currtime;    // Save current time for next idle
if (elapsedtime > 0.1)
    elapsedtime = 0.1;  // It's usually good to set a max value

xpos += xvel * elapsedtime;
ypos += yvel * elapsedtime;

glutPostRedisplay();

Double Buffering

So far, we have been using single buffering, in which drawing goes directly to the screen. This means that the user gets to see partially completed frames. When we do animation, this can produce flickering.

In double buffering, there are two framebuffers: the front buffer, which is displayed, and the back buffer, an off-screen framebuffer that is drawn into. When a frame is completed, these two buffers are swapped. The result is that the user only sees completed frames, and animation no longer flickers.

We enable double buffering in a GLUT program, first, by replacing GLUT_SINGLE with GLUT_DOUBLE in the glutInitDisplayMode parameter:

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);

This makes GLUT do two things:

Second, we replace the call to glFlush, at the end of the display function, with a call to glutSwapBuffers():

glutSwapBuffers();

This function call does two things:

Acceleration

Velocity is the change in position. Similarly, acceleration is the change in velocity. Thus, we can simulate acceleration as follows:

// elapsedtime has already been computed

xvel += xaccel * elapsedtime;
yvel += yaccel * elapsedtime;

xpos += xvel * elapsedtime;
ypos += yvel * elapsedtime;

glutPostRedisplay();

Note that what we are really computing here is a numerical approximation of the solution to a system of differential equations. As such, it may vary in accuracy. We will not say much this semester about techniques for improving accuracy; but you should know that such techniques do exist (see CS 482, MATH 310).

Multiple Objects

We can animate different objects differently by using different transformations. The matrix stacks make this convenient. Remember: The model/view transformation is a GL state. As such, it affects subsequent drawing, until it is modified.

Example:

glLoadIdentity();

glPushMatrix();
glTranslated(pos1x, pos1y, 0.);
// Draw object #1 here
glPopMatrix();

glPushMatrix();
glTranslated(pos2x, pos2y, 0.);
// Draw object #2 here
glPopMatrix();

glPushMatrix();
glTranslated(pos3x, pos3y, 0.);
// Draw object #3 here
glPopMatrix();

See anim.cpp.


CS 381 Fall 2012: Lecture Notes for Tuesday, September 11, 2012 / Updated: 11 Sep 2012 / Glenn G. Chappell / ggchappell@alaska.edu