Cover page images (elite)

Laboratorio Grafica al Calcolatore 2009/2010: Lecture 5 - Depth Buffering and Animations

Roberto Toldo, <nome.cognome@univr.it >




Hit the space bar for next slide

Previous Lecture Summary

Compositing Modeling Transformations

As each new modeling transformation command is introduced, its matrix is post-multiplied onto the current matrix. New matrices are composited onto the existing matrix, and the effect of the transformations accumulates.

Let's examine a few simple examples of this effect. For instance, what if we encounter these five lines in a program? (Let's assume no previous modeling transformations have been encountered.)

   glRectf(0.0, 0.0, 10.0, 10.0); 
   glTranslatef(30.0, 40.0, 0.0); 
   glRectf(0.0, 0.0, 10.0, 10.0); 
   glTranslatef(-10.0, 30.0, 0.0); 
   glRectf(0.0, 0.0, 10.0, 10.0); 

The first line draws a rectangle at the origin. The second line composites a translation onto the current matrix. The third line of code is a command which would normally draw another rectangle at the origin. However, the current matrix translates the rectangle to a new position. The object is actually drawn at (30.0, 40.0, 0.0).

The fourth line of code is another translation. This one is composited onto the previous translation. The cumulative effect is the sum of the two previous calls to glTranslatef, which is effectively glTranslatef(30.0 - 10.0, 40.0 + 30.0, 0.0 + 0.0). When the fifth line in this program is finally encountered, the rectangle will be translated to (20.0, 70.0, 0.0) and then drawn.

Compositing Dissimilar Modeling Transformations

Compositing translates with other translates, or scales with other scales, is straightforward. The wrench comes in when you composite dissimilar commands: rotates with translates, translates with scales, etc.

To think about how modeling transformations will affect one another, think of transforming the coordinate systems. With this method, we no longer think of a fixed coordinate system of the world. Instead, we can think of each modeling transformation moving the coordinate system of the object we wish to draw. Rotations re-orient the axes; scaling alters the graduation of the axes. We perform modeling transformations to position the object's axes. Then we draw the object relative to the temporary coordinate system. To understand this method, we will take a detailed investigation of a robot arm problem.

This approach is ideal for cases where the orientation of parts of an object, are dependent upon the orientation of other parts. The classic case of this is an object with joints, such as an arm. Examine your own arm. Bend it at the shoulder. The amount you bend it will influence where your elbow will be located. The amount you bend your elbow will influence where your wrist will be. This continues all the way down to the very last knuckle. The location of each joint is dependent upon previous joints.

Let's try to understand compositing modeling transformations by examining the movement of coordinate systems. To illustrate this let's look at how the finished robot arm should appear. Then, we'll discuss how to build it.

The robot arm will consist of four parts: two arms and two hinges (joints). The first arm connects the first two hinges. The second arm extends from the second hinge.

The arm is free to rotate about either of the two hinges. Any rotation about the first hinge affects the entire object. Any rotation about the second hinge only affects the second arm.

Now that we've seen what we're trying to build, let's go ahead and build it. First, we should specify a projection transformation to define the viewing volume. Perhaps, we'll opt to have a viewing transformation to move the eye around in the world. As of yet, no rotates, scales, and translates have been called.

The coordinate system is first moved to the center of the first hinge. This move requires a glTranslatef to the hinge location, which we will call point (a, b, c). Now glRotatef the first hinge (and the entire arm with it) about the z axis. Because the coordinate system has moved, the focus of the hinge is no longer at
(a, b, c), but is now at (0.0, 0.0, 0.0).

For this example, we'll say that we want to rotate the arm 20 degrees in the counterclockwise direction (as we look down the positive z axis towards the origin). If the rotation is counterclockwise, the angle of rotation is positive. So we call glRotatef(20.0,0.0,0.0,1.0). The rotation re-orients the entire coordinate system.

Until now, nothing has actually been drawn. Looking one step ahead, we know that the second hinge will not affect the position of the first hinge or first arm. The coordinate system is positioned for drawing the first arm and hinge.

In the program, the sub-routines to draw these parts are draw_hinge() and draw_arm(). The objects are drawn relative to the position of the coordinate system. Therefore the draw_hinge() sub-routine draws an object centered at the origin, and draw_arm() draws an object along the positive x axis.

Now move from the first hinge to the second hinge. This requires another glTranslatef to move the coordinate system. Let's assume the distance from the first hinge to the next hinge is 100.0 units along the re-oriented x axis. So, a glTranslatef(100.0, 0.0, 0.0) should do the trick. The coordinate system moves to the center of the second hinge.

Rotate the remainder of the robot arm about this second hinge. Give it a 70 degree counterclockwise about the z axis: glRotatef(70.0,0.0,0.0,1.0). We can reissue the same draw_hinge() and draw_arm() routines to draw the second hinge and second arm, relative to the new position of the coordinate system.

To review, the entire sequence of the program looks like this:

   glTranslatef(a, b, c);
   glRotatef(20.0,0.0,0.0,1.0);
   draw_hinge(); 
   draw_arm(); 
   glTranslatef(100.0, 0.0, 0.0); 
   glRotatef(70.0,0.0,0.0,1.0);
   draw_hinge(); 
   draw_arm();

Special Topic: Hierarchical Design

Now that you have an understanding of a variety of tools for transforming models, you can tackle more complicated problems. Previously, we explored compositing transformations, and the model was a robot arm that swivelled at its joints. Let's propose another problem for a robot arm.

Create a different robot arm with several objects attached to the same hinge. Suppose several objects are dependent upon the position of that hinge. The new robot arm looks like this

The position of two arms are dependent upon the position of the second hinge. Where an object has multiple dependencies, we wish to use OpenGL commands to save the state of the world. The state of the world is reflected in the current matrix which represents the transformed coordinate system. The glPushMatrix and glPopMatrix commands are sufficient to manipulate the matrix. The position of the second hinge should be saved before drawing the second arm. After the program draws the second arm, it should restore the saved matrix contents, and draw the third arm.

The section of code below creates our two-armed beast. The glPushMatrix command saves the matrix at the second hinge. After the second arm is drawn, the glPopMatrix command restores the coordinate system to the second hinge. Then the final arm is drawn.

   glRotatef(20.0,0.0,0.0,1.0);
   draw_hinge (); 
   draw_arm (); 
   glTranslatef (100.0, 0.0, 0.0); 
   glPushMatrix (); /* save coordinate system */ 
   glRotatef(70.0,0.0,0.0,1.0);
   draw_hinge (); 
   draw_arm (); /* draw second arm */ 
   glPopMatrix (); /* restore coordinate system */ 
   glRotatef(20.0,0.0,0.0,1.0);
   draw_arm (); /* draw final arm */ 

Lecture Objectives

At the completion of this module, you will be able to

Hidden Surfaces

Painter's Algorithm

  1. Sort objects by distance from the eye

  2. Render objects farthest from the eye first

  3. Continue rendering all objects from back to front

A Problem With the Painter's Algorithm

The Painter's Algorithm fails if objects intersect

Notes:

The polygon represents a rectangle that is at an angle. It should intersect with the grid; however, we do not get the right effect with the painter's algorithm.

Example: painter.c

/* painter.c - use the painter's algorithm to draw a solid polygon 
 *     in front of a line grid
 *
 *     <r> Key     - rotate polygon along X axis
 *     Escape Key  - exit program
 */

#include <GL/glut.h>    /* includes gl.h, glu.h */

#include <stdio.h>

/*  Function Prototypes  */

GLvoid  initgfx( GLvoid );
GLvoid  keyboard( GLubyte, GLint, GLint );
GLvoid  drawScene( GLvoid );
GLvoid  reshape( GLsizei, GLsizei );

void printHelp( char * );

/* Global Definitions */

#define KEY_ESC    27      /* ascii value for the escape key */

/* Global Variables */

static GLboolean doRotate = GL_FALSE;

void
main( int argc, char *argv[] )
{
   GLsizei width, height;

   glutInit( &argc, argv );

   width = glutGet( GLUT_SCREEN_WIDTH ); 
   height = glutGet( GLUT_SCREEN_HEIGHT );
   glutInitWindowPosition( 0, height / 4 );
   glutInitWindowSize( (width / 2) - 4, height / 2 );
   glutInitDisplayMode( GLUT_RGBA );
   glutCreateWindow( argv[0] );

   initgfx();

   glutReshapeFunc( reshape );
   glutKeyboardFunc( keyboard );
   glutDisplayFunc( drawScene ); 

   printHelp( argv[0] );

   glutMainLoop();
}

void
printHelp( char *progname )
{
   fprintf(stdout, "\n%s - demonstrates painter's algorithm\n\n"
           "<r> Key      - rotate polygon around X axis\n"
           "Escape Key   - exit the program\n\n",
           progname);
}

GLvoid
initgfx( GLvoid )
{
   glClearColor( 0.0, 0.0, 1.0, 1.0 );
   glShadeModel( GL_FLAT );
}

GLvoid
reshape( GLsizei width, GLsizei height )
{
   GLdouble    aspect;

   glViewport( 0, 0, width, height );

   glMatrixMode( GL_PROJECTION );
   glLoadIdentity();
   aspect = (GLdouble) width / (GLdouble) height;
   gluPerspective( 45.0, aspect, 3.0, 7.0 );
   glMatrixMode( GL_MODELVIEW );
}

GLvoid 
keyboard( GLubyte key, GLint x, GLint y )
{
   switch (key) {
   case 'r':
       doRotate = !doRotate;
       glutPostRedisplay();
       break;
   case KEY_ESC:   /* Exit whenever the Escape key is pressed */
       exit(0);
   }
}

GLvoid
drawScene( GLvoid )
{
   static GLfloat    whiteColor[] = { 1.0, 1.0, 1.0 };
   static GLfloat    purpleColor[] = { 0.8, 0.0, 1.0 };

   glClear( GL_COLOR_BUFFER_BIT );

   glPushMatrix();
       /* Move origin between the near and far clipping planes */
       glTranslatef( 0.0, 0.0, -4.0 );

       /* Draw a grid in the x-y plane centered at the origin */
       glColor3fv( whiteColor );
       WireGrid();

       glColor3fv( purpleColor );

       /* Draw a rectangle in front of the grid */
       glTranslatef( 0.0, 0.0, 0.1 );

       if (doRotate)
           glRotatef( -45.0, 1.0, 0.0, 0.0 );

       glRectf( -0.5, -0.5, 0.5, 0.5 );
   glPopMatrix();

   glFlush();
}

Notes:

This program draws a grid, and then draws a polygon in front of the grid. It uses the painter's algorithm to make sure the polygon looks like it is in front of the grid.

When the <r> key is pressed the polygon is rotated so that it intersects with the grid. Notice how the polygon looks a bit different. The top looks narrower to give the impression that it is farther away. However, the effect is not complete since the polygon is drawn last and overwrites the grid, even though it should look like part of it is behind the grid.

The Solution -- A Depth Buffer

Keep color and depth information for each pixel in the window

Notes:

When a pixel is about to be written, its Z value is compared to the corresponding pixel in the depth buffer, to see which is closer to the eye. Only if the new pixel is closer are its values written into the color buffer and depth buffer.

Pseudo code for the Depth Buffer Algorithm:

  1. Initialize the depth buffer to the largest possible value for every pixel

  2. For each pixel that is about to be drawn:

    1. compute the distance from the eye to the object at that point

    2. compare the distance computed with the value stored in the depth buffer

    3. if (distance < stored value)
      object is closer--update the color and depth buffer values

      otherwise

      object is obscured--discard new values

Requesting a Depth Buffer

GLvoid glutInitDisplayMode( GLuint mode )

Notes:

Depth buffer (also known as z-buffer) support may be implemented in hardware or software.

Clearing the Depth Buffer

GLvoid glClear( GLbitfield mask ) 

Notes:

The color and depth buffers should be cleared at the same time. Some hardware is optimized to clear buffers in parallel. If you made two separate calls, then it would not be able to take advantage of this optimization.

You can also change the value written to the depth buffer when it is cleared using glClearDepth():

   GLvoid glClearDepth( GLclampd depth )

Enabling the Depth Buffer

GLvoid glEnable( GLenum option )

GLvoid glDisable( GLenum option )

Example: depth_buffer.c

/* depth_buffer.c - draw a solid polygon that intersects the line grid
 *     using the depth buffer to remove hidden surfaces.
 *
 *     <r> Key     - rotate polygon along X axis
 *     Escape Key  - exit program
 */

#include <GL/glut.h>    /* includes gl.h, glu.h */

#include <stdio.h>

/*  Function Prototypes  */

GLvoid  initgfx( GLvoid );
GLvoid  keyboard( GLubyte, GLint, GLint );
GLvoid  drawScene( GLvoid );
GLvoid  reshape( GLsizei, GLsizei );

void printHelp( char * );

/* Global Definitions */

#define KEY_ESC    27      /* ascii value for the escape key */

/* Global Variables */

static GLboolean doRotate = GL_FALSE;

void
main( int argc, char *argv[] )
{
   GLsizei width, height;

   glutInit( &argc, argv );

   width = glutGet( GLUT_SCREEN_WIDTH ); 
   height = glutGet( GLUT_SCREEN_HEIGHT );
   glutInitWindowPosition( (width / 2) + 4, height / 4 );
   glutInitWindowSize( (width / 2) - 4, height / 2 );
   glutInitDisplayMode( GLUT_RGBA | GLUT_DEPTH );
   glutCreateWindow( argv[0] );

   initgfx();

   glutReshapeFunc( reshape );
   glutKeyboardFunc( keyboard );
   glutDisplayFunc( drawScene ); 

   printHelp( argv[0] );

   glutMainLoop();
}

void
printHelp( char *progname )
{
   fprintf(stdout, "\n%s - demonstrates depth buffering\n\n"
           "<r> Key      - rotate polygon around X axis\n"
           "Escape Key   - exit the program\n\n",
           progname);
}

GLvoid
initgfx( GLvoid )
{
   glClearColor( 0.0, 0.0, 1.0, 1.0 );
   glShadeModel( GL_FLAT );

   glEnable( GL_DEPTH_TEST );
}

GLvoid
reshape( GLsizei width, GLsizei height )
{
   GLdouble    aspect;

   glViewport( 0, 0, width, height );


   glMatrixMode( GL_PROJECTION );
   glLoadIdentity();
   aspect = (GLdouble) width / (GLdouble) height;
   gluPerspective( 45.0, aspect, 3.0, 7.0 );
   glMatrixMode( GL_MODELVIEW );
}

GLvoid 
keyboard( GLubyte key, GLint x, GLint y )
{
   switch (key) {
   case 'r':
       doRotate = !doRotate;
       glutPostRedisplay();
       break;
   case KEY_ESC:   /* Exit when the Escape key is pressed */
       exit(0);
   }
}

GLvoid
drawScene( GLvoid )
{
   static GLfloat    whiteColor[] = { 1.0, 1.0, 1.0 };
   static GLfloat    purpleColor[] = { 0.8, 0.0, 1.0 };

   glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

   glPushMatrix();
       /* Move origin between the near and far clipping planes */
       glTranslatef( 0.0, 0.0, -4.0 );

       /* Draw a grid in the x-y plane centered at the origin */
       glColor3fv( whiteColor );
       WireGrid();

       glColor3fv( purpleColor );

       /* Draw a rectangle in front of the grid */
       glTranslatef( 0.0, 0.0, 0.1 );

       if (doRotate)
           glRotatef( -45.0, 1.0, 0.0, 0.0 );

       glRectf( -0.5, -0.5, 0.5, 0.5 );
   glPopMatrix();

   glFlush();
}

Notes:

This program is exactly the same as intersect.c except that it requests and enables the depth buffer. Notice how the polygon now looks like it cuts through the grid.

In this program the depth test is enabled in initgfx(). In many programs, this is done in drawScene() so that the depth test may be turned on and off for different parts of the scene.

Depth Buffer Precision

Depth buffer precision is non-linear for perspective projections

Depth Buffer Anomalies

Drawing order for coplanar objects is unpredictable

Other Hidden Surfaces

Some 3D objects have surfaces that are almost always hidden, for example, the inside of a sphere

Notes:

Objects like spheres are built up using lots of polygons (called facets) with the same orientation. Remember from Lecture 3 that clockwise (CW) or counterclockwise (CCW) orientation refers to the order in which a polygon face's vertices are specified.

The orientation representing the back face does not need to be drawn unless we are looking at the inside of the sphere. You will see how to do this in a later exercise.

Removing Other Hidden Surfaces

GLvoid glCullFace( GLenum mode ) 

Example: cullface.c

/* cullface.c - draw a solid torus with front/back face culling.
 *
 *     <f> Key     - toggle between front/back face culling
 *     <r> Key     - rotate polygon along X axis
 *     Escape Key  - exit program
 */

#include <GL/glut.h>    /* includes gl.h, glu.h */

#include <stdio.h>

/*  Function Prototypes  */

GLvoid  initgfx( GLvoid );
GLvoid  keyboard( GLubyte, GLint, GLint );
GLvoid  drawScene( GLvoid );
GLvoid  reshape( GLsizei, GLsizei );

void printHelp( char * );

/* Global Definitions */

#define KEY_ESC    27      /* ascii value for the escape key */

/* Global Variables */

static GLint     cullface = GL_BACK;
static GLboolean doRotate = GL_FALSE;

void
main( int argc, char *argv[] )
{
   GLsizei width, height;

   glutInit( &argc, argv );

   width = glutGet( GLUT_SCREEN_WIDTH ); 
   height = glutGet( GLUT_SCREEN_HEIGHT );
   glutInitWindowPosition( (width / 2) + 4, height / 4 );
   glutInitWindowSize( (width / 2) - 4, height / 2 );
   glutInitDisplayMode( GLUT_RGBA | GLUT_DEPTH );
   glutCreateWindow( argv[0] );

   initgfx();

   glutReshapeFunc( reshape );
   glutKeyboardFunc( keyboard );
   glutDisplayFunc( drawScene ); 

   printHelp( argv[0] );

   glutMainLoop();
}

void
printHelp( char *progname )
{
   fprintf(stdout, "\n%s - demonstrates front/back face culling\n\n"
          "<f> Key      - toggle between front/back face culling\n"
          "<r> Key      - rotate polygon around X axis\n"
          "Escape Key   - exit the program\n\n",
          progname);

   fprintf(stdout, "culling %s faces\n", 
       (cullface == GL_FRONT ? "FRONT" : "BACK" )); 
}

GLvoid
initgfx( GLvoid )
{
   glClearColor( 0.0, 0.0, 1.0, 1.0 );
   glShadeModel( GL_FLAT );

   glEnable( GL_DEPTH_TEST );

   /* Enable face culling */
   glEnable( GL_CULL_FACE );
}

GLvoid
reshape( GLsizei width, GLsizei height )
{
   GLdouble    aspect;

   glViewport( 0, 0, width, height );

   glMatrixMode( GL_PROJECTION );
   glLoadIdentity();
   aspect = (GLdouble) width / (GLdouble) height;
   gluPerspective( 45.0, aspect, 3.0, 7.0 );
   glMatrixMode( GL_MODELVIEW );
}

GLvoid 
keyboard( GLubyte key, GLint x, GLint y )
{
   switch (key) {
   case 'f':   /* toggle between front/back face culling */
       if (cullface == GL_FRONT) {
           cullface = GL_BACK;
       } else {
           cullface = GL_FRONT;
       }
       fprintf(stdout, "culling %s faces\n", 
           (cullface == GL_FRONT ? "FRONT" : "BACK" )); 
       glCullFace( cullface );
       glutPostRedisplay();
       break;
   case 'r':
       doRotate = !doRotate;
       glutPostRedisplay();
       break;
   case KEY_ESC:   /* Exit when the Escape key is pressed */
       exit(0);
   }
}

GLvoid
drawScene( GLvoid )
{
   static GLfloat    whiteColor[] = { 1.0, 1.0, 1.0 };
   static GLfloat    purpleColor[] = { 0.8, 0.0, 1.0 };

   glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

   glPushMatrix();
       /* Move origin between the near and far clipping planes */
       glTranslatef( 0.0, 0.0, -4.0 );

       /* Draw a grid in the x-y plane centered at the origin */
       glColor3fv( whiteColor );
       WireGrid();

       glColor3fv( purpleColor );

       /* Draw a rectangle in front of the grid */
       glTranslatef( 0.0, 0.0, 0.1 );

       if (doRotate)
           glRotatef( -45.0, 1.0, 0.0, 0.0 );

       /* Draw a torus */
       glutSolidTorus( 0.2, 0.7, 15, 31 );

   glPopMatrix();

   glFlush();
}

Notes:

This program draws a grid that intersects a torus, cutting it in half. It enables culling of the front faces of the torus, so what you actually see of the torus is the inside of the back half (behind the grid). Press the <f> key to toggle between front and back face culling.

Lab: Hidden Surfaces

  1. Change directory to ../tutorial/exercises.

  2. Modify your solar6.c program to do the following:

    • Enable depth buffering

    • Enable back face culling
    Note: There will be no visible difference in the scene unless the shapes overlap and are not drawn in back-to-front order. In the next module you will modify your view of the solar system and then you will see the effect of the depth buffering.

  3. * Copy your reshape.c program to depth.c and modify it to do one or more of the following:

    • Add several objects. Use the Painter's Algorithm to order the objects from back to front. Run your program.

    • Make one or more objects intersect. Add depth buffering.

    • Draw several coplanar polygons or outlined polygons and use polygon offset to make sure they are rendered correctly.

    • Add a 3D object that has an enclosed surface (for example, a sphere, torus, or box) and enable back face culling.

    • Change the orientation of front facing polygons or do front face culling and run your program again. What happens?

Notes:

* Indicates an optional laboratory exercise.



OpenGL C Quick Reference

void glClearDepth( GLclampd depth )

Specify the clear value for the depth buffer.


void glCullFace( GLenum mode )

Specify whether front- or back-facing facets can be culled. mode can be GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.


void glPolygonMode( GLenum face, Lenum mode )

Select a polygon rasterization mode. face can be GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK. mode can be GL_POINT, GL_LINE, or GL_FILL.


void glPolygonOffset( GLfloat factor, GLfloat bias )

Set the scale factor and bias used to offset polygon depth.

Making Objects Move

Notes:

In order to draw only the parts that moved (in this case the earth and moon), we would first have to draw over their old positions with whatever is supposed to be there in the new frame, and then we would have to draw them in their new positions.

It is much simpler to just redraw the entire scene with the earth and moon in their new position.

The problem with this is that the eye sees the intermediate clear and incremental drawing. This leads to a flickering effect.

Continuous Animation

void glutIdleFunc( void (*func)(void) ) 

Notes:

This method of performing continuous animation is similar to the X Window System work procedure.

You can either pass a pointer to your drawScene function to glutIdleFunc(), or set up a separate function that just modifies variables used by your drawing function. If you use the latter method, your idle function will probably want to tell GLUT to redisplay the scene using glutPostRedisplay(). The examples use the latter method.

Disabling Animation When Not Visible

void glutVisibilityFunc(void (*func)(int state)) 

Notes:

Turning off animation avoids wasting CPU cycles when your program is not visible. For example, when it is iconified.

Example: flicker_arm.c

/*  flicker_arm.c - animates a robot arm in single buffer mode to 
 *                  demonstrate the flicker problem
 *
 *  <r> Key       - toggle arm rotation on / off
 *  Escape key    - exit the program 
 */

#include <GL/glut.h>    /* includes gl.h, glu.h */

#include <math.h>      /* for fmod */
#include <stdlib.h>    /* for NULL */
#include <stdio.h>     /* for printf */

/*  Function Prototypes  */

GLvoid  initgfx( GLvoid );
GLvoid  animate( GLvoid );
GLvoid  visibility( GLint );
GLvoid  drawScene( GLvoid );
GLvoid  reshape( GLsizei, GLsizei );
GLvoid  keyboard( GLubyte, GLint, GLint );

void printHelp( char * );

/*  Global Variables */

static GLfloat shoulderAngle = 0.0;    /* controls shoulder rotation */

static GLboolean   rotateFlag = GL_TRUE;

/* Global Definitions */

#define KEY_ESC    27      /* ascii value for the escape key */

void
main( int argc, char *argv[] )
{
   GLsizei width, height;

   glutInit( &argc, argv );

   width = glutGet( GLUT_SCREEN_WIDTH ); 
   height = glutGet( GLUT_SCREEN_HEIGHT );
   glutInitWindowPosition( 0, height / 4 );
   glutInitWindowSize( (width / 2) - 4, height / 2 );
   glutInitDisplayMode( GLUT_RGBA | GLUT_DEPTH );
   glutCreateWindow( argv[0] );

   initgfx();

   glutIdleFunc( animate );
   glutVisibilityFunc( visibility );
   glutKeyboardFunc( keyboard );
   glutReshapeFunc( reshape );
   glutDisplayFunc( drawScene ); 

   printHelp( argv[0] );

   glutMainLoop();
}

void
printHelp( char *progname )
{
   fprintf(stdout, "\n%s - animate a robot arm\n\n"
           "Axes: X - red, Y - green, Z - blue\n\n"
           "<r> Key        - toggle arm rotation on / off\n"
           "Escape Key     - exit the program\n",
           progname);
}

GLvoid
initgfx( GLvoid )
{
   glClearColor( 0.0, 0.0, 0.0, 1.0 );
   glShadeModel( GL_FLAT );
   glEnable( GL_DEPTH_TEST );
}

GLvoid
reshape( GLsizei width, GLsizei height )
{
   GLdouble    aspect;

   glViewport( 0, 0, width, height );

   aspect = (GLdouble) width / (GLdouble) height;

   glMatrixMode( GL_PROJECTION );
   glLoadIdentity();
   gluPerspective( 45.0, aspect, 1.0, 20.0 );
   glMatrixMode( GL_MODELVIEW );
}

GLvoid 
keyboard( GLubyte key, GLint x, GLint y )
{
   switch (key) {
   case 'r':
       rotateFlag = !rotateFlag;
       if (rotateFlag) {
           glutIdleFunc( animate );
       } else {
           glutIdleFunc( NULL );
       }
       break;
   case KEY_ESC:   /* Exit when the Escape key is pressed */
       exit(0);
   }
}

GLvoid 
animate( GLvoid )
{
   /* update the rotation of the shoulder for each scene */
   shoulderAngle = fmod( (shoulderAngle + 1.0), 360.0 );

   glutPostRedisplay();    /* Tell GLUT to redraw the scene */
}

GLvoid
visibility( int state ) 
{
   /* restart the animation function if we were
    * animated when the window was hidden
    */
   if (state == GLUT_VISIBLE && rotateFlag) {
       glutIdleFunc( animate );
   } else {
       glutIdleFunc( NULL );
   }
}

GLvoid
drawScene( GLvoid )
{
    static GLfloat    upperArmColor[] = { 0.5, 0.5, 0.8 };
    static GLfloat    lowerArmColor[] = { 0.5, 0.8, 0.5 };

   glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

   glPushMatrix();
       gluLookAt( 0.0, 0.0, 8.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 );
       XYaxes();

       glPushAttrib( GL_LINE_BIT );
       glLineWidth( 2.0 );

       /* rotate the shoulder by shoulderAngle degrees */
       glRotatef( shoulderAngle, 0.0, 0.0, 1.0 );

       /* Draw the upper arm */
       glTranslatef( 1.0, 0.0, 0.0 ); 
       glColor3fv( upperArmColor );
       WireBox( 2.0, 0.4, 1.0 );

       /* Draw the lower arm */
       glTranslatef( 1.0, 0.0, 0.0 );
       glRotatef( 45.0, 0.0, 0.0, 1.0 );
       glTranslatef( 1.0, 0.0, 0.0 );
       glColor3fv( lowerArmColor );
       WireBox( 2.0, 0.4, 1.0 );

       glPopAttrib();

   glPopMatrix();
   glFlush();
}

Notes:

This program adds animation to the robot_arm program shown earlier. It does this by rotating the coordinate system each time drawScene() is called.

Notice the call to glutIdleFunc() to set up the animation function. This tells the GLUT library to continuously call your animate() function when no window system events are being received.

Each time animate() is called, it updates the rotation angle, and then tells GLUT that the window needs to be redisplayed. When drawScene() is invoked, it rotates the coordinate system around the z axis by shoulderAngle degrees and draws the new scene.

The program uses fmod to calculate the floating point modulo of 360.0. The header file <math.h> is needed for this function to work correctly.

The Flicker Problem

Every time you clear the screen, you get flicker

Double Buffering

void glutInitDisplayMode( GLenum mode )

Notes:

The framebuffer is split in half. Do all of your drawing to the half that is not currently being displayed (usually the back buffer). When you are ready, switch which half is the front buffer and which half is the back buffer.

Swapping the Buffers

void glutSwapBuffers( void )

Notes:

The picture illustrates what happens with the two buffers.

glutSwapBuffers() performs an implicit glFlush().

The buffers are typically swapped during the monitor's vertical retrace, which requires waiting for the retrace to begin. This provides smoother animation than a single buffer because modifying the framebuffer contents as they are being displayed can cause "tearing" and other artifacts.

Example: smooth_arm.c

/*  smooth_arm.c - animates a robot arm in double buffered mode to 
 *                     create smooth motion
 *
 *  <r> Key      - toggle arm rotation on / off
 *  Escape key   - exit the program 
 */

#include <GL/glut.h>    /* includes gl.h, glu.h */

#include <math.h>      /* for fmod */
#include <stdlib.h>    /* for NULL */
#include <stdio.h>     /* for printf */

/*  Function Prototypes  */

GLvoid  initgfx( GLvoid );
GLvoid  animate( GLvoid );
GLvoid  visibility( GLint );
GLvoid  drawScene( GLvoid );
GLvoid  reshape( GLsizei, GLsizei );
GLvoid  keyboard( GLubyte, GLint, GLint );

void printHelp( char * );

/*  Global Variables */

static GLfloat shoulderAngle = 0.0;    /* controls shoulder rotation */

static GLboolean   rotateFlag = GL_TRUE;

/* Global Definitions */

#define KEY_ESC    27      /* ascii value for the escape key */

void
main( int argc, char *argv[] )
{
   GLsizei width, height;

   glutInit( &argc, argv );

   width = glutGet( GLUT_SCREEN_WIDTH ); 
   height = glutGet( GLUT_SCREEN_HEIGHT );
   glutInitWindowPosition( (width / 2) + 2, height / 4 );
   glutInitWindowSize( (width / 2) - 4, height / 2 );
   glutInitDisplayMode( GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE );
   glutCreateWindow( argv[0] );

   initgfx();

   glutIdleFunc( animate );
   glutVisibilityFunc( visibility );
   glutKeyboardFunc( keyboard );
   glutReshapeFunc( reshape );
   glutDisplayFunc( drawScene ); 

   printHelp( argv[0] );

   glutMainLoop();
}

void
printHelp( char *progname )
{
   fprintf(stdout, "\n%s - smoothly animate a robot arm\n\n"
           "Axes: X - red, Y - green, Z - blue\n\n"
           "<r> Key        - toggle arm rotation on / off\n"
           "Escape Key     - exit the program\n",
       progname);
}

GLvoid
initgfx( GLvoid )
{
   glClearColor( 0.0, 0.0, 0.0, 1.0 );
   glShadeModel( GL_FLAT );
   glEnable( GL_DEPTH_TEST );
}

GLvoid
reshape( GLsizei width, GLsizei height )
{
   GLdouble    aspect;

   glViewport( 0, 0, width, height );

   aspect = (GLdouble) width / (GLdouble) height;

   glMatrixMode( GL_PROJECTION );
   glLoadIdentity();
   gluPerspective( 45.0, aspect, 1.0, 20.0 );
   glMatrixMode( GL_MODELVIEW );
}

GLvoid 
keyboard( GLubyte key, GLint x, GLint y )
{
   switch (key) {
   case 'r':
       rotateFlag = !rotateFlag;
       if (rotateFlag) {
           glutIdleFunc( animate );
       } else {
           glutIdleFunc( NULL );
       }
       break;
   case KEY_ESC:   /* Exit when the Escape key is pressed */
       exit(0);
   }
}

GLvoid 
animate( GLvoid )
{
   /* update the rotation of the shoulder for each scene */
   shoulderAngle = fmod( (shoulderAngle + 1.0), 360.0 );
   
   glutPostRedisplay();    /* Tell GLUT to redraw the scene */
}

GLvoid
visibility( int state ) 
{
   /* restart the animation function if we were
    * animated when the window was hidden
    */
   if (state == GLUT_VISIBLE && rotateFlag) {
       glutIdleFunc( animate );
   } else {
       glutIdleFunc( NULL );
   }
}

GLvoid
drawScene( GLvoid )
{
    static GLfloat    upperArmColor[] = { 0.5, 0.5, 0.8 };
    static GLfloat    lowerArmColor[] = { 0.5, 0.8, 0.5 };

   glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

   glPushMatrix();
      gluLookAt( 0.0, 0.0, 8.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 );
      XYaxes();

      glPushAttrib( GL_LINE_BIT );
      glLineWidth( 2.0 );

      /* rotate the shoulder by shoulderAngle degrees */
      glRotatef( shoulderAngle, 0.0, 0.0, 1.0 );

      /* Draw the upper arm */
      glTranslatef( 1.0, 0.0, 0.0 ); 
      glColor3fv( upperArmColor );
      WireBox( 2.0, 0.4, 1.0 );

      /* Draw the lower arm */
      glTranslatef( 1.0, 0.0, 0.0 );
      glRotatef( 45.0, 0.0, 0.0, 1.0 );
      glTranslatef( 1.0, 0.0, 0.0 );
      glColor3fv( lowerArmColor );
      WireBox( 2.0, 0.4, 1.0 );

      glPopAttrib();

   glPopMatrix();

   glutSwapBuffers();
}

Notes:

This program is the same as flicker_arm.c except that double buffering has been added. To do this, request a window that supports double buffering and call glutSwapBuffers() instead of glFlush() at the end of drawScene().

Recall that glutSwapBuffers() waits for vertical retrace before swapping the front and back buffers. Therefore, smooth_arm may appear to run slower than flicker_arm. The angle of rotation could be increased to speed up the animation.

Lab: Animation

  1. Modify your solar7.c program to

    • Add an idle function that updates the position of the planets and forces a redisplay

    • Add double buffering to perform smooth animation

    • Add a toggle to turn animation on and off

  2. * Copy your depth.c program to animate.c and modify it to smoothly animate one of the objects in your scene

  3. ** Use mouse input to give an object "momentum"

Notes:

* Indicates an optional laboratory exercise.

** Indicates an optional advanced laboratory exercise.

Hints for step 2: Declare two floating point variables: year and day. Use the following formulas to update them each frame:

   year = fmod( (year + 0.5), 360.0 ); 
   day = fmod( (day + 5.0), 360.0 ); 

Use year for the rotation of the earth around the sun. Use day for the rotation of the moon around the earth.

Note: You must #include <math.h> for the fmod function.

You may need to increase the distance between your near and far clipping planes so that the moon and earth do not get clipped as they rotate around the sun.

Hint for step 5: You could have a model continue moving at the same rate as the user was moving the mouse. To do this, compare the previous and current mouse positions to determine how fast the mouse was moving.

OpenGL C Quick Reference

void glutIdleFunc( void (*func)(void) )

Sets up a function to be called when no window system events are being received.


void glutSwapBuffers( void )

Swaps the buffers of the current window, if it is double buffered.


void glutVisibilityFunc( void (*func)(int state) )

Sets the visibility callback function for the current window.

mElite 05: Let's animate planet and satellite

Using your knowledge about animation you have to


Click >>here<< for a possible solution. You can compile it with the following Makefile.
For compiling you need coriolis.h, eliteutils.h, and eliteutils.c in the same directory.