| CS 381 Fall 2012 > Lecture Notes for Tuesday, September 11, 2012 |
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.
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.)
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.
0.,0.,1.,
as above.
The rotation will be counterclockwise.
The glScale* command scales about the origin.
Use the model/view transformation to do all positioning, orientation, and sizing of objects.
glTranslate*.glRotate*.glScale*.
See
anim.cpp.
Later in the semester, we will look at the math behind the above transformation commands.
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.
Function glPopMatrix also takes no parameters.
It pops the top matrix off.
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
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();
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:
glDrawBuffer(GL_BACK)).
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:
glFlush() (so that we no longer need to).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).
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.
ggchappell@alaska.edu