OpenGL Graphics

Introduction to OpenGL - Index of Lessons

You should start at Lesson 0 and work your way through all the pages within each lesson. You should also download and run the example program associated with each lesson and try some of the suggested modifications to understand better the particular point being made in that lesson. This index is provided to make coming back to the lessons an easier task.

  • - Compiling example programs
  • L0 - Setting up the Framework
  • L1 - Extending the Setup code and adding some graphics
  • L2 - Adding some object rendering to the drawing code
  • L3 - More on Colour
  • L4 - Querying the State of the OpenGL state machine
  • L5 - Moving into 3D
  • L6 - Scaling in x,y and z
  • L7 - Rotating in x,y and z
  • L8 - Drawing in 3D
  • L9 - Drawing another 3D object
  • L10 - Getting the 3D right!
  • L11 - Getting the 3D right! Again!
  • L12 - Drawing a GLUT 3D object
  • L13 - Double Buffering and Idle Function
  • L14 - Cpu utilisation issues
  • L15 - Using a timer
  • L16 - Texture Maps
  • L17 - More texture Mapping
  • L18 - Multiple Levels of Detail
  • L19 - Transforming the Texture Map
  • L20 - Using Many Different Textures
  • L21 - Depth Cueing, Fog etc.
  • L22 - Automatic mipmaps
  • L23 - Displaying Text
  • L24 - Mouse Interaction and Full Screen
  • L25 - Display Lists New
  • L26 - Buffer tests; Scissor New
  • L27 - Buffer tests; Alpha New
  • L28 - Buffer tests; Stencil
  • L29 - Buffer tests; Depth
  • L30 - Accumulation Buffer
  • L31 - Environment Mapping
  • L32 - Evaluators, Curved surfaces
  • L33 - Selection and Feedback

(c) 2001 Dr R P Fletcher, Computing Service


Compiling and Running a Program

1. Running the program under IRIX on an SGI system

OpenGL is well supported on the Silicon Graphics systems in D/104. If you are developing graphics, you may find these Unix systems the best ones to use. If you are an undergraduate, you will need to get your supervisor to write a note for the Information Desk.

A simple command makeglut has been set up to help the building process, and all you have to do is:

    % makeglut <name of program>

Note: do not give the file extension ".c" - e.g.

    % makeglut L0
the executable will be called "L0" and to run it you simply type:
    % L0

2. Running the program on a Supported Windows 95 PC

The necessary files are set up for use with Borland C++ (not C++ Builder).

Go to the Start Menu/Programs/Programming/Borland C++/C++ IDE

1. Set up a new project with File/New/Project and set up the form with the following entries:

    a. Project Path and Name (assuming you have the source in m:\L0.c)
      m:\L0.ide

    b. Target Model

      Console

    c. Click the Static check item.

    d. Make sure that in the Frameworks panel Class Library is checked.

    e. Then click "OK"

2. Change the source code file to type ".c"

    a. In the Project Window right click on the source code item L0.cpp

    b. Select Edit node attributes

    c. Change the name of the source file from L0.cpp to L0.c (the Node Type item will change to .c).

    d. Then click "OK"

3. Delete the unwanted project ".def" and ".rc" file items.

    a.Select the next two items L0.def and L0.rc

    b. Right click and delete them both.

4. Add a new library node to the project

    a. Click on the top level node L0.exe

    b. Right click and select Add a node

    c. On the panel set:

      Files of type to Libraries (*.lib) from the list.

    d. Navigate to R:\Applications\Borland\Bc5\lib\

    e. Select glut32.lib

5. Now set some options.

    a. Select menu item Options/Project

    b. Click on Directories and set:

      Include to R:\Applications\Borland\Bc5\include

      and

      Library to R:\Applications\Borland\Bc5\lib

    c. Click on the + next to Compiler

    d. Click on Defines and enter the following into the window:

      WIN32;_STDCALL_SUPPORTED

        (Note the underscores, semicolon and all UPPERCASE)

    e. Click on Compiler Output and check:

      Generate underscores

    f. Then click "OK"

6. Now you are ready to build the program

    a. Select Project/Build All menu item.

    b. If you build successfully you can double click on the node L0.exe to run the program.

    c. You will get a Console window (for text messages and for other program input/output functions) as well as a graphics window.

    d. You should be able to resize the graphics window.

    e. Pressing the Esc key will exit the program and shut down both windows.

7. If you do not have enough disk space to build and run the program(s)

    a. If you are going to do much development work, you will need to ask for extra space. Go to the Information desk for this.

    b. You can put many of the intermediate (and final) output files onto a temporary area on the C: drive.

      i. Click on menu item Options/Project.

      ii. Select Directories and enter C:\win95\temp into both of the Output Directories text input boxes.

      iii. Now select Compiler/Precompiled Headers and in the box Precompiled header name enter c:\win95\temp\L0.csm

8. Giving the program away to someone else on a Windows 98 system (or a Windows 95 system with Opengl installed).

    a. Simply give them them the executable (.exe) file and also the GLUT dynamic library (glut32.dll - note, it's the ".dll" you need), which you can find in R:\95applib\

You may find that simply copying the ".ide" file and changing a few parameters, (e.g. program source), may be easier than setting up a new project every time.


Introduction to OpenGL - L0

Setting up the framework

The first function we need to perform is the setting up of the interface to the underlying operating system. This code will do just the basics and we will expand on this general set up code.

First we need to include some standard headers, the GLUT library, which itself includes the OpenGL library headers and also a section where we will put any variables which we may wish to keep global to all the functions.

#include <stdio.h>
#include <GL/glut.h>

/* Here we will put variables which we wish to be global */
GLint window;           /* The number of our GLUT window */

We now need a simple generalised window and viewing transformation routine. Here we set up the viewport (the mapping of our coodinate system onto the screen, or part of the screen), and then setting up a simple perspective transformation. The first parameter is the field of view, where 90 degrees represents a view of an 11 inch screen at about 7.8 inches away, to give undistorted viewing. To get an undistorted view you will have to calculate the appropriate angle. We will use 45 degrees, which be reasonable for our examples. The second parameter is the aspect ratio of the window, the third and fourth are the near and far Z axis clipping planes. We set the Z axis planes to have a large range. It is very common for an OpenGL program to simply render a black screen! The usual cause is an incorrect viewing transformation e.g. in the example below, objects are drawn with a Z axis value greater than 100.0 or less than 0.1. The data is simply clipped away, and nothing is sent to the OpenGL pipeline for rendering.

GLvoid Transform(GLfloat Width, GLfloat Height)
{
  glViewport(0, 0, Width, Height);              /* Set the viewport */
  glMatrixMode(GL_PROJECTION);                  /* Select the projection matrix */
  glLoadIdentity();				/* Reset The Projection Matrix */
  gluPerspective(45.0,Width/Height,0.1,100.0);  /* Calculate The Aspect Ratio Of The Window */
  glMatrixMode(GL_MODELVIEW);                   /* Switch back to the modelview matrix */
}

We will now set up our OpenGL initialisation function. At present all we will do is setup the viewing transformation. We call the transformation function because (if you remember) we will need to call this routine every time we re-render the scene. This becomes important later.

/* A general OpenGL initialization function.  Sets all of the initial parameters. */
GLvoid InitGL(GLfloat Width, GLfloat Height)
{
  Transform( Width, Height );                   /* Perform the transformation */
}

If we resize the window, we will want to call the transformation routine again in order to transform any of our data to the new window size. This routine can be set to be called automatically when the window is resized using the system window manager (we will add this in the next lesson).

/* The function called when our window is resized  */
GLvoid ReSizeGLScene(GLint Width, GLint Height)
{
  if (Height==0)    Height=1;                   /* Sanity checks */
  if (Width ==0)    Width=1;
  Transform( Width , Height );                   /* Perform the transformation */
}

Now we come to the routine which will do all our OpenGL drawing each time the scene is required to be rendered. Note that this is a dummy function and will simply print the message "drawing called" in the terminal (console) window.

/* The main drawing function

   In here we put all the OpenGL and calls to routines which manipulate
   the OpenGL state and environment.

   This is the function which will be called when a "redisplay" is requested.
*/

GLvoid DrawGLScene()
{
printf("drawing called\n");
}

We now need to put it all together with the main program code. OpenGL is a state machine, and your program can query the state of graphics at any time. GLUT will also provide you with the hooks to capture any system events which you may wish to process (e.g. window manager resize, keyboard input, mouse input, timers etc). This functionality is provided via a series of callback registrations. In this case the only callback is the for the display which will get called on the window manager event "expose". This event will occur when the window is first rendered, when it is redrawn after having been iconised, and also after resizing. The program drops into an "event loop" from which it never returns. We will see how to process events as we progress through the sessions.

/*************************** Main ***************************************************************/
int main(int argc, char **argv)
{
/* Initialisation and window creation */
  glutInit(&argc, argv);               /* Initialize GLUT state. */

  glutInitDisplayMode(GLUT_RGBA |      /* RGB and Alpha */
                      GLUT_SINGLE|     /* Single buffer */
                      GLUT_DEPTH);     /* Z buffer (depth) */

  glutInitWindowSize(400,400);         /* set initial window size. */
  glutInitWindowPosition(0,0);         /* upper left corner of the screen. */
  window = glutCreateWindow("L0");     /* Open a window with a title. */
  InitGL(400,400);                     /* Initialize our window. */
/* Now register the various callback functions */
  glutDisplayFunc(DrawGLScene);        /* Function to do all our OpenGL drawing. */
/* Now drop into the event loop from which we never return */
  glutMainLoop();                      /* Start Event Processing Engine. */
   return 1;
}

Now proceed to the next part of the lesson.


Introduction to OpenGL - L1

Extending the Setup code and adding some graphics

We will now add some code to the previous program. First a little "define" to help with processing the ESCAPE key and a couple of variables for the window size.

/* ASCII code for the escape key. */
#define ESCAPE 27

GLint window;           /* The number of our GLUT window */
GLint Xsize=400;
GLint Ysize=400;

We will add some more code to the initialisation routine too. We will set the colour to which any window is cleared by a call to the glClear() function. The values to the glClearColor() function are the red, green, blue and alpha values, as floating point numbers in the range 0.0 to 1.0.

GLvoid InitGL(GLfloat Width, GLfloat Height)
{
  glClearColor(0.0, 0.0, 0.0, 0.0);		/* This Will Clear The Background Colour To Black */
  Transform( Width, Height );                   /* Perform the transformation */
}

And linked with the above addition, we will add in the clear window function call into our scene drawing routine.

GLvoid DrawGLScene()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	/* Clear The Screen And The Depth Buffer */
  printf("drawing called\n");
}

We will also add the callback to call the resize function. This will be called whenever the windowing system detects that we have resized the window.

glutReshapeFunc(ReSizeGLScene);


Introduction to OpenGL - L2

Adding some object rendering to the drawing code

In this example we will now add in some drawing of basic primitives, points, lines and quadrilaterals. In order to see the results clearly we will set the point size and linewidths in our setup routine. We will also add in a function call to setup a blending function. This will be switched in and out with an extra case in the keyboard handling function.

GLvoid InitGL(GLfloat Width, GLfloat Height)
{
  glClearColor(0.0, 0.0, 0.0, 0.0);		/* This Will Clear The Background Color To Black */
  glPointSize(4.0);                             /* Add point size, to make it clear */
  glLineWidth(2.0);                             /* Add line width,   ditto */
  glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
  Transform( Width, Height );                   /* Perform the transformation */
}

Next we will add some drawing functionality into the scene drawing function. Remember OpenGL is a state machine. Thus, objects are rendered with the current state which includes transformations as well as colour. So, we need to set the colour of the object we are rendering before we render it!

GLvoid DrawGLScene()
{
int i;
float xy;
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	/* Clear screen, depth Buffer */
  glColor3ub(255,0,0);                   /* Set the colour rgb, 0-255 */
  glBegin(GL_POINTS);                    /* Points */
   for (i=0, xy=0.0 ;i<10;i++, xy+=0.1)
     glVertex3f(xy,xy,-6.0);             /* where */
  glEnd();
  glColor3f(0.0,1.0,0.0);                /* Set colour rgb, 0.0 - 1.0 */
  glBegin(GL_LINES);                     /* Lines this time */
   for (i=0, xy=0.0 ;i<10;i++, xy+=0.1) {
     glVertex3f(xy,xy,-6.0);             /* need start and end points */
     glVertex3f(xy+1.0,xy,-6.0);
   }
  glEnd();
  glBegin(GL_QUADS);                     /* Quads this time */
   for (i=0, xy=0.0 ;i<10;i++, xy-=0.1) {
     glColor4f(1.0,1.0+xy,0.0,0.4);      /* Set colour rgba, 0.0 - 1.0 */
     glVertex3f(xy,xy,-6.0);             /* Now define 4 corners */
     glVertex3f(xy-1.0,xy,-6.0);
     glVertex3f(xy-1.0,xy-1.0,-6.0);
     glVertex3f(xy,xy-1.0,-6.0);
   }
  glEnd();
  printf("drawing called\n");
}

The next part of this lesson will discuss the rendering of each of the 3 different primitives.

Now proceed to the next part of lesson.


Introduction to OpenGL - L3

More on Colour

So far we have looked at solid colour but actual colour rendered on the screen is subject to a quite lengthy process before being assigned. We have seen how the colours can change once we use a blending function to specify the interaction of different colour "layers". However, we can also specify the shading model, the interaction of light sources and of course texturing.

In this lesson we will look at the shading model, which is switched using the gl function glShadeModel()

glShadeModel(GL_FLAT);

glShadeModel(GL_SMOOTH);

The default is GL_FLAT and in this mode the color of one vertex is duplicated across all the primitives faces. For a line, GL_LINES, then the current colour when the second vertex is specified is the colour used. For polygons it's a little more complex. OpenGL will be counting the vertices as it supplies the data to the graphics pipeline. So, when a particular vertex number is specified for a polygon, the colour is then set. For primitives which essentially draw multiple polygons this can be important. The table shows which vertex will be used for the "i"th polygon.

    Type                  Vertex
single polygon               1
triangle strip              i+2
triangle fan                i+2
triangle                     3i
quad strip                  2i+2
quad                        4i

To avoid uncertainty, the best advice is to supply only one colour for the primitive.

With GL_SMOOTH the colour at each vertex is treated individually. So, for a line primitive, GL_LINES, then the colours along the line segment are interpolated between the vertex colours. For polygon primitives, GL_POLYGONS, GL_QUADS etc., the colours for the interior are interpolated between all the vertex colours.

Now proceed to the next part of next lesson.


Introduction to OpenGL - L4

Querying the State of the OpenGL state machine

As we have said, OpenGL is a state machine. At any point in the execution there will always be a state which can be queried, e.g. the current colour, transformation details, line and polygon stipple patterns, drawing modes, information about the lights and so on. Some state variables have their own query functions, but most can be retrieved with one of four gl calls:

glGetBooleanv()
glGetDoublev()
glGetFloatv()
glGetIntegerv()

In addition you can save and restore the values of a collection of state varibles on an attribute stack and restore them later using 2 gl function calls. There is a set bits which can be logically OR'd to make the mask to specify the collection of state variables which are pushed onto the stack.

glPushAttrib(GLbitfield mask)
glPopAttrib()

The glut toolkit also retains a certain amount of state information which can be easily retrieved using simple function calls.

glutGet(GLenum state)        simple state represented by integers
glutLayerGet(GLenum info)    layer information
glutDeviceGet Glenum info)   tests what devices are available
glutGetModifiers(void)       tests if shift,ctrl,alt are pressed
glutExtensionSupported(char * EXT)       is this OpenGL EXT supported?


Introduction to OpenGL - L5

Moving into 3D

We have seen how to render objects in 2D. However the set up code has established a 3D perspective viewing transformation. We can now use this to draw 3D objects very easily. Of course, it's up to you to create your own objects built up from the basic set of graphics primitives. Glut has a set of 3D objects already predefined which you can use. They are especially useful when testing code before you insert your own models. The glut models are:

glutSolidSphere()
glutWireSphere()
glutSolidCube()
glutWireCube()
glutSolidCone()
glutWireCone()
glutSolidTorus()
glutWireTorus()
glutSolidDodecahedron()
glutWireDodecahedron()
glutSolidOctahedron()
glutWireOctahedron()
glutSolidTetrahedron()
glutWireTetrahedron()
glutSolidIcosahedron()
glutWireIcosahedron()
glutSolidTeapot()
glutWireTeapot()

These routines do not generate display lists for the objects they create (more about this mechanism in later Lessons). They do generate the appropriate normals for lighting but do not generate texture coordinates.

Before we use one of these calls, we will look at drawing a simple plane and applying transformations to it in the 3D space we have defined.


Introduction to OpenGL - L6

Scaling in x,y and z

In this lesson we will look at the scaling transformation which allows you to scale the objects in the x, y, and z dimensions.

To implement this we will use the 3 keys x,y and z again to add a small increment to the arguments to the gl scale function and upper case X, Y and Z to subtract the same increment. We need to keep these 3 variables in the global section. The program will draw a simple shaded quad originally in the centre of the screen which can be scaled using these keys.

/* ASCII code for the escape key. */
#define ESCAPE 27

GLint window;           /* The number of our GLUT window */
GLint Xsize=400;
GLint Ysize=400;

GLfloat xs=1.0,ys=1.0,zs=1.0;   /* x,y,z scale */

In the keyboard handling routine we will add in the simple simple code to add or subtract a value from these variables.

     case 'x':                          /* scale x up */
        xs += 0.2;
        glutPostRedisplay();
        break;
     case 'X':
        xs -= 0.2;                      /* scale x down */
        glutPostRedisplay();
        break;

      case 'y':                          /* scale y up */
        ys += 0.2;
        glutPostRedisplay();
        break;
     case 'Y':
        ys -= 0.2;                      /* scale y down */
        glutPostRedisplay();
        break;

      case 'z':                          /* scale z up */
        zs += 0.2;
        glutPostRedisplay();
        break;
     case 'Z':
        zs -= 0.2;                      /* scale z down */
        glutPostRedisplay();
        break;

Notice that we request a redisplay of the scene, using glutPostRedisplay(), in order to see the effect of changing the value of the variables xs,ys and zs.

Of course, we now need to implement the scale in the GL scene drawing function.

GLvoid DrawGLScene()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	/* Clear The Screen And The Depth Buffer */

  glPushMatrix();

  glLoadIdentity();
  glTranslatef(0.0,0.0,-6.0);
  glScalef(xs,ys,zs);

  glBegin(GL_QUADS);
    glColor3f(1.0,1.0,0.0);
    glVertex3f( 1.0f, 1.0f, 1.0f);
    glColor3f(1.0,0.0,0.0);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glColor3f(1.0,1.0,0.0);
    glVertex3f(-1.0f,-1.0f, 1.0f);
    glColor3f(1.0,0.0,0.0);
    glVertex3f( 1.0f,-1.0f, 1.0f);
  glEnd();

  glPopMatrix();

}

Notice that this program is almost identical to the previous program L5.c, and we have simply changed the variable names and made a different gl function call to perform the scaling.


Introduction to OpenGL - L7

Rotating in x,y and z

In this lesson we will look at the rotation transformation which allows you to rotate the objects in the x, y, and z dimensions.

In order to implement this we will use the x,y, and z keys to add or subtract an increment to the angle of rotation parameter for that axis in a similar way to the previous programs.

/* ASCII code for the escape key. */
#define ESCAPE 27

GLint window;           /* The number of our GLUT window */
GLint Xsize=400;
GLint Ysize=400;

GLfloat xangle=0.0,yangle=0.0,zangle=0.0;   /* axis angles */

In the keyboard handling routine we will add in the simple simple code to add or subtract a value from these variables.

     case 'x':
        xangle += 5.0;
        glutPostRedisplay();
        break;
     case 'X':
        xangle -= 5.0;
        glutPostRedisplay();
        break;

     case 'y':
        yangle += 5.0;
        glutPostRedisplay();
        break;
     case 'Y':
        yangle -= 5.0;
        glutPostRedisplay();
        break;

     case 'z':
        zangle += 5.0;
        glutPostRedisplay();
        break;
     case 'Z':
        zangle -= 5.0;
        glutPostRedisplay();
        break;

Notice that we request a redisplay of the scene, using glutPostRedisplay(), in order to see the effect of changing the value of the variables xangle,yangle and zangle.

Of course, we now need to implement the rotation in the GL scene drawing function.

GLvoid DrawGLScene()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	/* Clear The Screen And The Depth Buffer */

  glPushMatrix();

  glLoadIdentity();
  glTranslatef(0.0,0.0,-6.0);
  glRotatef(xangle,1.0,0.0,0.0);
  glRotatef(yangle,0.0,1.0,0.0);
  glRotatef(zangle,0.0,0.0,1.0);

  glBegin(GL_QUADS);
     glColor3f(1.0,1.0,0.0);
     glVertex3f(-1.0,-1.0,-1.0);
     glColor3f(1.0,0.0,0.0);
     glVertex3f(-1.0,1.0,-1.0);
     glColor3f(1.0,1.0,0.0);
     glVertex3f(1.0,1.0,-1.0);
     glColor3f(1.0,0.0,0.0);
     glVertex3f(1.0,-1.0,-1.0);
  glEnd();

  glPopMatrix();

}

The function glRotatef will multiply the current transformation matrix by a matrix which will rotate the local coordinates by the amounts specified in a counterclockwise direction. Notice that this time the specifiction for the rotation is now in 3 separate function calls. The glRotatef() function call requires the angle of rotation followed by three parameters which represent a 3D vector which define a ray to the origin (0.0,0.0,0.0) about which the rotation is performed.


Introduction to OpenGL - L8

Drawing in 3D

So far we have drawn all our graphical objects in the same Z plane, i.e. they have all been co-planar. Now we will draw some lines with different 3D depths. We will simply modify program L7.c with some different drawing code.

First we will add back in the code in the initialisation function to set the line width in order to make the lines very visible.

  glLineWidth(2.0);                             /* Add line width,   ditto */

Next the drawing code.

  glPushMatrix();
  glLoadIdentity();
  glTranslatef(0.0,0.0,-6.0);
  glRotatef(xangle,1.0,0.0,0.0);
  glRotatef(yangle,0.0,1.0,0.0);
  glRotatef(zangle,0.0,0.0,1.0);

  glBegin(GL_LINE_STRIP);                     /* Linestrip this time */
    glColor3f(0.0,1.0,0.0);                /* Set green */
    glVertex3f(-1.0,-1.0,-1.0);
    glVertex3f(1.0,-1.0,-1.0);
    glVertex3f(1.0,1.0,-1.0);
    glVertex3f(-1.0,1.0,-1.0);
    glVertex3f(-1.0,-1.0,-1.0);
  glEnd();

  glBegin(GL_LINE_STRIP);                     /* Linestrip this time */
    glColor3f(1.0,0.0,0.0);                /* Set colour red */
    glVertex3f(-1.0,-1.0,1.0);
    glVertex3f(1.0,-1.0,1.0);
    glVertex3f(1.0,1.0,1.0);
    glVertex3f(-1.0,1.0,1.0);
    glVertex3f(-1.0,-1.0,1.0);
  glEnd();

  glBegin(GL_LINES);                     /* Lines this time */
    glColor3f(0.0,0.0,1.0);                /* Set colour blue */
    glVertex3f(-1.0,-1.0,1.0);
    glVertex3f(-1.0,-1.0,-1.0);
    glVertex3f(-1.0,1.0,1.0);
    glVertex3f(-1.0,1.0,-1.0);
    glVertex3f(1.0,-1.0,1.0);
    glVertex3f(1.0,-1.0,-1.0);
    glVertex3f(1.0,1.0,1.0);
    glVertex3f(1.0,1.0,-1.0);
  glEnd();
  glPopMatrix();

The code will draw simple 3D cube, the front lines in red, the back in green and the connecting side lines in blue.


Introduction to OpenGL - L9

Drawing another 3D object

In this lesson we will draw the cube again, but this time each face will be drawn as a solid quadrilateral, each of a different colour. The following code draws one face.

  /* top of cube*/
  glColor3f(0.0f,1.0f,0.0f);            /* Set The Color To Blue*/
  glVertex3f( 1.0f, 1.0f,-1.0f);        /* Top Right Of The Quad (Top)*/
  glVertex3f(-1.0f, 1.0f,-1.0f);        /* Top Left Of The Quad (Top)*/
  glVertex3f(-1.0f, 1.0f, 1.0f);        /* Bottom Left Of The Quad (Top)*/
  glVertex3f( 1.0f, 1.0f, 1.0f);        /* Bottom Right Of The Quad (Top)*/

We continue this for all six faces. Again, notice we are drawing a cube 2 units in size in each direction, with the origin (0.0,0.0,0.0) in the centre of the cube. In order to bring the cube into view, we keep the same transformations as the wireframe model in the previous lesson L8.c.


Introduction to OpenGL - L10

Getting the 3D right!

We have now drawn 2 objects in 3D, but have seen that once we transform them such that the faces or lines change which ones are in front or behind there does appear to be a problem with the order in which the graphical objects (lines and planes) are rendered.

Luckily, OpenGL comes to our rescue here. We have the notion of a depth buffer into which all pixels are drawn, provided we have asked for this state information to be used. In our case we have in the call

glutInitDisplayMode(GLUT_RGBA |      /* RGB and Alpha */
                    GLUT_SINGLE|     /* Single buffer */
                    GLUT_DEPTH);     /* Z buffer (depth) */

So, why then are we still getting incorrect depth information? Well, simple, we have not asked OpenGL to actually do the depth testing. We just asked for a depth buffer to exist. This is yet another OpenGL state variable which can be enabled or disabled. The relevant function calls are

glEnable(GL_DEPTH_TEST)         glDisable(GL_DEPTH_TEST)

We can also specify the type of depth testing done using the function call

glDepthFunc(GLenum func)

where func can be one of

GL_NEVER      GL_ALWAYS     GL_EQUAL
GL_GEQUAL     GL_GREATER    GL_NOTEQUAL
GL_LESS

Each pixel coming into the depth buffer passes the depth test if its z value has the specified relation to the value already stored in the depth buffer. The default value is GL_LESS, which means that an incoming pixel passes the test if its z value is less than that already stored in the depth buffer. So, in the normal case, pixels with a smaller z value are closer to the viewpoint and so should be rendered.


Introduction to OpenGL - L10a

Rotation

Shift-Click here to obtain the source for Lesson 10. Save it in your account as L10.c

Now compile and link this program using the system-wide simple command

% makeglut L10
Note that there is NO extension required. This will compile the program L10.c, link it with the GLUT libraries and OpenGL libraries to make an executable program called L10. You can now run this by giving the command

% L10

This program will draw a wireframe cube. Pressing the x, y, and z keys (and the upper case keys) will perform the rotation, and move the object around.

The keys d and D will switch the depth testing on and off. You should rotate the object and toggle the test on and off and observe the behaviour of the depth buffer.

You may wish to modify the program to set up the different depth test functions and observe their behaviour.

Now proceed to the next lesson.


Introduction to OpenGL - L11

Getting the 3D right! Again!

In this lesson we simply apply the new information about depth testing to the example of the shaded cube. The only change to the program L9.c will be the addition of the code to toggle the depth test.


Introduction to OpenGL - L11a

Rotation

Shift-Click here to obtain the source for Lesson 11. Save it in your account as L11.c

Now compile and link this program using the system-wide simple command

% makeglut L11
Note that there is NO extension required. This will compile the program L11.c, link it with the GLUT libraries and OpenGL libraries to make an executable program called L11. You can now run this by giving the command

% L11

This program will draw a shaded cube. Pressing the x, y, and z keys (and the upper case keys) will perform the rotation of the cube.

The keys d and D will switch the depth testing on and off. You should rotate the object and toggle the test on and off and observe the behaviour of the depth buffer. You may wish to modify the program to set up the different depth test functions and observe their behaviour.

Another interesting change would be to give different colours at the various vertices of the cube and to enable smooth shading, and then rotate the object.

Now proceed to the next lesson.


Introduction to OpenGL - L12

Drawing a GLUT 3D object

In this lesson we will draw a standard 3D object, the cube, from the GLUT toolkit. This is a solid object which provides normals for the lighting calculations to operate, so it will be useful to set up a lighting source as well as draw the object. We will keep our x, y, and z rotation code.

Our initialisation function now becomes more complex because we have to define the light source.

GLfloat LightAmbient[] = { 0.8, 0.0, 0.0, 1.0 };  /* reddish ambient light  */
GLfloat LightDiffuse[] = { 0.1, 0.1, 0.9, 1.0 };  /* bluish  diffuse light. */
GLfloat LightPosition[] = { 1.5, 1.5, 1.5, 0.0 };    /* position */

These vectors will be used to define the lighting setup associated with a GL light. In this example we will specify an ambient light source which is a dim red and a bluish diffuse light. Think of the diffuse light as the colour of the light added to the scene. The final vector specifies the position/direction of the light source. If the fourth parameter is zero, the coordinates x,y,z specify the direction of the light source, whilst if nonzero, the coordinates x,y,z specify the position of the light source. Given these we can specfiy our light source.

glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmbient);  /*  add lighting. (ambient) */
glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiffuse);  /*  add lighting. (diffuse). */
glLightfv(GL_LIGHT0, GL_POSITION,LightPosition); /*  set light position. */

All three function calls are defining a charactistic of the same light source, GL_LIGHT0. We now need to enable this light source, the lighting for the whole scene, and depth testing.

glEnable(GL_LIGHT0);                      /*  turn light 0 on. */

The code for the cube is now simplified somewhat ...

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glLoadIdentity();
glTranslatef(0.0,0.0,-6.0);
glRotatef(xangle,1.0,0.0,0.0);
glRotatef(yangle,0.0,1.0,0.0);
glRotatef(zangle,0.0,0.0,1.0);
glutSolidCube(2.0);
glPopMatrix();

We will also add in an extra control into the keyboard function to enable and disable the lighting using the "l/L" keys.

     case 'l':
       glEnable(GL_LIGHTING);  /* enable lighting */
       glutPostRedisplay();
        break;
     case 'L':
       glDisable(GL_LIGHTING);  /* Disable lighting */
       glutPostRedisplay();
        break;


Introduction to OpenGL - L12a

Rotation

Shift-Click here to obtain the source for Lesson 12. Save it in your account as L12.c

Now compile and link this program using the system-wide simple command

% makeglut L12
Note that there is NO extension required. This will compile the program L12.c, link it with the GLUT libraries and OpenGL libraries to make an executable program called L12. You can now run this by giving the command

% L12

This program will draw a shaded cube. Pressing the x, y, and z keys (and the upper case keys) will perform the rotation.

The keys "d" and "D will switch the depth testing on and off, and the "l" and "L will switch the lighting on and off. You should rotate the object and toggle the tests on and off and observe the behaviour of the depth buffer and the lighting.

You may wish to modify the program to display the other solid models available from the GLUT toolkit, or perhaps combinations of them as well. They are in the program, and all you need to do is to remove the comments. In order to display more than one object you will need to think about how to move the local coordinate system to the correct place before you call the object rendering code.

Now proceed to the next lesson.


Introduction to OpenGL - L13

Double Buffering

So far we have been using OpenGL to display static objects. We have been performing various transformations on them and simply asking for the object to be rendered again using the function call glutPostRedisplay(). You will have noticed that you do not get smooth motion and the more complex the object the more flickering of the image you get when performing these transformations.

The technique used to get smooth movement is called double buffering. In this the graphics system has two buffers into which graphics can be drawn. Whilst the first is displaying what has been drawn, any new drawing goes into the second buffer (the back buffer). When the drawing of the scene has been completed a simple command asks for the buffers to be swapped. Once swapped, the contents of the back buffer are undefined, and so it is not possible to swap back again! In any case, the program will be wanting to create the next frame of the scene, drawing this into the back buffer again. You do not need to keep track of which buffers you are using, this is done for you.

An additional feature is that the graphics hardware will only actually perform the swapping of the buffers when the screen refresh period is over. Many monitors have refresh rate of 60, 75, 120 frames per second. If you have the former, then the maximum frame rate you can achieve is of course, 60 frames per second. However, if your graphics are very complex, and it takes more then 1/60th of a second to actually draw it into the back buffer, then the system will wait for the next refresh period. So, your graphics will only display at 30 frames a second.

This fact can have unexpected performance consequences as you can only have 60,30,20,15,12,10 frames per second from a 1/60th of a second refresh monitor (60/1,6/2,60/3,60/4,60/5,60/6 etc). This means that when you are developing your application, you may get very good frame rates to start with, but as the graphics get more complex, the first performance hit can be as much as halving the frame rate!

Worst still is if you are close to the edge of one of the magic times for your monitor, then random elements in the timing of writes can lead to some frames happening on time and some delayed by a full refresh cycle. This can lead to animations which are visually disturbing. In this case efforts must be made to simplify the graphics or time the swapping of the buffers to make sure that these random time factors do not figure.


Introduction to OpenGL - L13a

Double Buffering

When we initialised our graphics environment (the graphics context), we made the function call

glutInitDisplayMode(GLUT_RGBA |      /* RGB and Alpha */
                    GLUT_SINGLE |    /* Single buffer */
                    GLUT_DEPTH);     /* Z buffer (depth) */

Notice we specifically asked for a single buffer. We must therefore change this to request double buffering, and so it will come as no surprise that the call becomes

glutInitDisplayMode(GLUT_RGBA |      /* RGB and Alpha */
                    GLUT_DOUBLE |    /* Double buffer */
                    GLUT_DEPTH);     /* Z buffer (depth) */

We will modify the program to rotate the solid cube so that the increment is added to the angle each time we refresh the graphics, and will use the keys to change the value of this increment (basically to make the cube rotate faster or slower).

So, we add in 3 new variables to hold the increments

GLfloat xinc=0.0,yinc=0.0,zinc=0.0;         /* Axis increments */

and the code in the keyboard function is modified to now increment or decrement these variables.

     case 'x':
        xinc += 0.2;
        break;
     case 'X':
        xinc -= 0.2;
        break;
     case 'y':
        yinc += 0.2;
        break;
     case 'Y':
        yinc -= 0.2;
        break;
     case 'z':
        zinc += 0.2;
        break;
     case 'Z':
        zinc -= 0.2;
        break;
     case 'd':
        glEnable(GL_DEPTH_TEST);
        break;
     case 'D':
        glDisable(GL_DEPTH_TEST);
        break;

Notice we have also removed all the function calls to glutPostRedisplay().


Introduction to OpenGL - L14

Timing Issues

We have already discussed the frame rate issues and how these can start to become serious as the graphics become complex. We have not discussed the cpu utilisation issues and these of course to some extent have a bearing on the frame rate problems.

It is instructive to run the previous program L13 again and monitor the cpu usage. On the SGI systems simply run the gr_osview program

% gr_osview -a

and then run the program. You should also iconise the window in which the graphics are displaying. You should notice, contrary to expectations, that the cpu usage goes up. If you monitor with the top program you will find that the program consumes nearly 8% of the cpu when in a window and when iconised up to 9.5%. Clearly the OpenGL graphics are still trying to be rendered into the iconised window and are consuming cpu cycles. The same goes when the graphics window is completely obscured by another. There is a solution to help us code up well behaved graphics programs.


Introduction to OpenGL - L14a

Timing Issues

The first technique is a simple modification to the previous program with the addition of yet another callback. This one is used to detect the visibility status of the graphics window.

glutVisibilityFunc(Viz);

and of course, we need to provide the function Viz() which will be called whenever the window if fully obscured or iconised.

GLvoid Viz(int state)
{
 switch (state)
 {
   case GLUT_VISIBLE:
             glutIdleFunc(DrawGLScene);
             break;
   case GLUT_NOT_VISIBLE:
              glutIdleFunc(NULL);
              break;
   default:
             break;
 }
}

Notice the parameter state which can take 2 values GLUT_VISIBLE and GLUT_NOT_VISIBLE. Depending on the value of this parameter we switch the Idle function between running the graphics display code and NULL, i.e. do nothing.


Introduction to OpenGL - L15

Using a Timer

If we want to run animations at a specified frame rate, then using the idle function is not an option because this will always try to redisplay frames as fast as the hardware will run (and of course, in part this is dependent on the complexity of the graphics you are rendering).

The way round this is to use a system timer. The glut toolkit helps here by providing a timer callback function which can be set to run at specifed intervals. we can use this mechanism to display our frames. The format of the function call is:

glutTimerFunc(40,Timer,0);

Where the user supplied function Timer is set to be triggered every 40 milliseconds (which equates to 25 frames/second). The last argument is a value which can be passed to the Timer function and we can use this in conjunction with the visibility callback to suspend drawing. We cannot stop the timer from triggering though. Our visibility callback function now looks like this:

GLvoid Viz(int state)
{
 switch (state)
 {
   case GLUT_VISIBLE:
             Timer(1);
             break;
   case GLUT_NOT_VISIBLE:
             Timer(0);
             break;
   default:
             break;
 }
}

Notice that all we do is call the Timer function with a different value and the code of Timer function looks like this:

GLvoid Timer( int value )
{
   if( value ) glutPostRedisplay();
   glutTimerFunc(40,Timer,value);
}

The function simply registers a redisplay and then sets the timer up again. However, we only request a redisplay if the value of the argument is 1.


Introduction to OpenGL - L15a

Using a Timer

Shift-Click here to obtain the source for Lesson 15. Save it in your account as L15.c

Now compile and link this program using the system-wide simple command

% makeglut L15
Note that there is NO extension required. This will compile the program L15.c, link it with the GLUT libraries and OpenGL libraries to make an executable program called L15. You can now run this by giving the command

% L15

Run the program now and check the cpu utilisation of the program when it is iconised or fully obscured. Also, it would be useful to also try out different values in the timer function to achieve different frame rates.

Now proceed to the next lesson.


Introduction to OpenGL - L16

Texture Maps

In this lesson we will see how to use a texture map. This is a 2D image which can be used to "paint" over a graphic object. It is of course transformed in 3D etc along with the rest of the OpenGL graphics. Many modern games make use of this technique (e.g. Quake), and demonstrate the power of OpenGL.

Before we can use a texture we need to be able to read the image from a file. In this example we will use a "quick and dirty" BMP loader which will read an image in a standard "Windows" bit map format. We also need to define an image structure into which we read the image for use by the OpenGL texture. This structure must be defined before the image loader code is included into your program.

/* Image type - contains height, width, and data */
struct Image {
    unsigned long sizeX;
    unsigned long sizeY;
    char *data;
};
typedef struct Image Image;
#include "BMPloader.c"                      /* include the BMP loader code */

Having included this code, the next function to write is one which will read the image file and create the texture.

GLvoid LoadTexture(GLvoid)
{
Image *TextureImage;

    TextureImage = (Image *) malloc(sizeof(Image));
    if (TextureImage == NULL) {
        printf("Error allocating space for image");
        exit(1);
    }
    if (!ImageLoad("crate.bmp", TextureImage)) {
        exit(1);
    }
    /*  2d texture, level of detail 0 (normal), 3 components (red, green, blue),            */
    /*  x size from image, y size from image,                                              */
    /*  border 0 (normal), rgb color data, unsigned byte data, data  */
    glTexImage2D(GL_TEXTURE_2D, 0, 3,
                 TextureImage->sizeX, TextureImage->sizeY,
                 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage->data);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); /*  cheap scaling when image bigger than texture */
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); /*  cheap scaling when image smalled than texture*/
    free( TextureImage );
}

First we allocate memory to store the image and then we proceed to load the image from the file crate.bmp. The function ImageLoad returns 1 for success and 0 for failure.


Introduction to OpenGL - L16b

Texture Maps

The next two calls specify what the OpenGL implmentation should do when the image is smaller than 256 pixels or larger than 256 pixels.

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);

In both cases we have requested that the texel (a texture map pixel) with the coordinates nearest the centre of the pixel is used for both magnification and minification. This can result in severe aliasing effects. If GL_LINEAR is used a weighted linear average of a 2x2 array of texels that lie nearest to the centre of the pixel is used, but this will take more cpu time to compute.

Finally we free the memory used when we first read the image data because the pixels have now been transferred into texture memory.

free( TextureImage->data );
free( TextureImage );

Before we can use the texture map we need to enable this function in our initialisation function using:

glEnable(GL_TEXTURE_2D);

The image we will be using looks like the side of a wooden crate.


Introduction to OpenGL - L17

More Texture Mapping

In this example we will show how the same texture map can be applied to more than one object (without reading it in again, of course!). The program will also map part of the texture map and will demonstrate the magnification that will occur automatically.

The quadrilateral from before will be moved to the left to make room for a triangle on the right. In order to do this we simply make a change to the glTranslatef function call to move the local coordinates to the left. Notice we still rotate by the 3 angles.

Smooth shading will be used to interpolate colours over the surface of the quadrilateral and the triangle. The program will also demonstrate the fact that the colour selected remains in force until changed. We should see that we have 3 corners of the quad as green, and the bottom right should be red. Also, maybe surprisingly, the triangle will be completely red!

Also note that we use glTranslatef() to move the local coordinates to the right before we draw the triangle, and then rotate again by the same angles. For each object we clear the transformation matrix using glLoadIdentity().

  glLoadIdentity();
  glTranslatef(-1.0,0.0,-6.0);
  glRotatef(xangle,1.0,0.0,0.0);
  glRotatef(yangle,0.0,1.0,0.0);
  glRotatef(zangle,0.0,0.0,1.0);
  glBegin(GL_QUADS);                /* start drawing the quad.*/
    glColor3f(0.0,1.0,0.0);            /* Green */
    glTexCoord2f(0.0, 0.0);
    glVertex3f( 1.0, 1.0, 1.0);        /* Top Right Of The Quad (Front)*/
    glTexCoord2f(1.0, 0.0);
    glVertex3f(-1.0, 1.0, 1.0);        /* Top Left Of The Quad (Front)*/
    glTexCoord2f(1.0, 1.0);
    glVertex3f(-1.0,-1.0, 1.0);        /* Bottom Left Of The Quad (Front)*/
    glColor3f(1.0,0.0,0.0);            /* Red */
    glTexCoord2f(0.0, 1.0);
    glVertex3f( 1.0,-1.0, 1.0);        /* Bottom Right Of The Quad (Front)*/
  glEnd();
  glLoadIdentity();
  glTranslatef(1.0,0.0,-6.0);
  glRotatef(xangle,1.0,0.0,0.0);
  glRotatef(yangle,0.0,1.0,0.0);
  glRotatef(zangle,0.0,0.0,1.0);

  glBegin(GL_TRIANGLES);
    glTexCoord2f(0.0, 0.0);          /* Bottom left of texture */
    glVertex3f( -1.0, -1.0, 1.0);
    glTexCoord2f(0.25, 0.5);         /* 25% along and 50% up !! */
    glVertex3f(0.0, 1.0, 1.0);
    glTexCoord2f(0.5, 0.0);          /* 50% along and back to 0 in the Y */
    glVertex3f(1.0,-1.0, 1.0);
  glEnd();

And to enable smooth shading we must make the call to glEnable() in our initialisation routine.

glShadeModel(GL_SMOOTH);


Introduction to OpenGL - L17a

More Texture Mapping

Shift-Click here to obtain the source for Lesson 17. Save it in your account as L17.c

Now compile and link this program using the system-wide simple command

% makeglut L17
Note that there is NO extension required. This will compile the program L17.c, link it with the GLUT libraries and OpenGL libraries to make an executable program called L17. You can now run this by giving the command

% L17

Notice how the colours are interpolated and also the magnification of the texture map onto the surface of the triangle. Try rotating using the (x,y,z) keys again, and see that BOTH objects rotate by the same amount and at the same speed.

Given that each object was in fact drawn with its own local transformations, try modifying the program to make the triangle spin faster than the quad. Even better, try modifying the program so that the triangle has its own independent rotation keys! You should be able to get the triangle to pass through the quad.

Now proceed to the next lesson.


Introduction to OpenGL - L18

Multiple Levels of Detail

As mentioned in a previous lesson it is clear that the human visual system sees less detail as an object moves farther away and becomes smaller! Clearly, in a 3D graphics system we need some method of reducing the details drawn as objects get smaller (usually by virtue of moving away from the viewer!). This not only makes the objects look more realistic, but also reduces the amount of cpu time the system has to spend trying to render a complex (and possibly large) image onto a small object. The technique is to provide levels of detail (LOD). OpenGL can do this by using the filtering methods assigned by the function call to:

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);

However, this technique can easily lead to unwanted artefacts and so a mechanism to allow the user to provide images to use at various decreasing levels of detail (resolution) is available in OpenGL. This technique is known as mipmapping, and is derived from the latin

multim im parvo (hence mip)

which means "many things in a small place".

In order to use mipmapping, you must provide all sizes of your texture in powers of 2 between the largest map down to a 1x1 map. For example, if you started with a 64x16 texture map (note that a texture map does not have to be square), then you must provide texture maps for 32x8, 16x4, 8x2, 4x1, 2x1 and 1x1.

The smaller maps are usually derived form the largest map by some form of interpolation and average, but in fact OpenGL does not require you to do this, and in fact the smaller texture maps can be totally unrelated!

In order to specify these textures we come back to the original function call:

    glTexImage2D(GL_TEXTURE_2D, 0, 3,
                 TextureImage->sizeX, TextureImage->sizeY,
                 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage->data);

and use different valeus for level, width, height and image parameters.


Introduction to OpenGL - L18a

Multiple Levels of Detail

Starting with our crate image at a resolution of 256x256 the 8 smaller texture map images have been made. Each one was also recoloured so the when the OpenGL system changes the texture map it should be obvious! we therefore have to modify the texture map loading function to loop round and read 9 texture maps. To aid this process the image files have been names crate0.bmp, crate1,bmp to crate8.bmp. The code changes are quite simple.

GLvoid LoadTexture(GLvoid)
{
char file[11];
int i;
Image *TextureImage;
        for( i = 0; i<9; i++ )
    {
     sprintf(file,"crate%1d.bmp",i);  /* create the name */
    TextureImage = (Image *) malloc(sizeof(Image));
    if (TextureImage == NULL) {
        printf("Error allocating space for image");
        exit(1);
    }
    if (!ImageLoad(file, TextureImage)) {
        exit(1);
    }
          /*  2d texture, level of detail i , 3 components (red, green, blue),            */
    /*  x size from image, y size from image,                                              */
    /*  border 0 (normal), rgb color data, unsigned byte data, and finally the data itself. */

    glTexImage2D(GL_TEXTURE_2D, i, 3,
                 TextureImage->sizeX, TextureImage->sizeY,
                 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage->data);
    free( TextureImage );
   }

All we have done is create a simple mechanism for providing the filename as per the naming scheme and make sure the texture map level variable is now the loop variable i. The size information is actually stored in the image structure to make life easier.

The last part of the setting up is to inform the OpenGL system that we want to use the mipmaps for the minification functions. That is, when the texture images get smaller, then simply choose from the available maps.

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,
                    GL_NEAREST); /*  cheap scaling when image bigger than texture */
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,
                    GL_NEAREST_MIPMAP_NEAREST);  /* use mipmaps */

And that is all there is to it!


Introduction to OpenGL - L19

Moving the Texture Map

In this part we will look at how we can make transformations of the texture map in exactly the same way we make transformations of drawn objects.

OpenGL maintains various stacks of transformation matrices. For most purposes we can ignore them bar the occasional push and pop in order to save and restore a transformation state. OpenGL has a matrix for transforming the texture maps, and so we can use the same OpenGL function calls to transform our texture.

It is surprisingly simple to do, requiring a call to

glMatrixMode( GL_TEXTURE );

followed by the standard OpenGL function calls to

glTranslatef();
glRotatef();
  etc

In this example we will use the program with the textured quad and triangle and move the texture. At the same time we will register another callback function to handle the "special" keys (F1 to F12, PageUp, Arrow keys etc ...) which is done as part of the setup code.

glutSpecialFunc(SpecialKey);

where SpecialKey is a function supplied by us to process the key presses, in the same way as the normal keys are processed.

void SpecialKey(GLint key, GLint x, GLint y)
{
     switch ( key )    {
     case GLUT_KEY_UP :
       Texturey += 0.01;
       break;
     case GLUT_KEY_DOWN :
       Texturey -= 0.01;
       break;
     case GLUT_KEY_LEFT :
       Texturex += 0.01;
       break;
     case GLUT_KEY_RIGHT :
       Texturex += 0.01;
       break;

     default:
    break;
    }
}

Note we are adding (or subtracting) a small increment to the variables Texturex and Texturey, which we will make global.

GLfloat Texturex, Texturey;                 /* Texture map movements */


Introduction to OpenGL - L19a

Moving the Texture Map

Now all we need to do is insert the code to move the texture map in the drawing function before we do the drawing.

glMatrixMode( GL_TEXTURE );
glTranslatef(Texturex, Texturey, 0.0 );
glMatrixMode( GL_MODELVIEW );

Note that we do not save the current texture map matrix state, and thus the effect of these transformations will be cumulative.


Introduction to OpenGL - L20

Using Many Different Textures

So far we have used a single texture mapped onto different objects, and at multiple levels of detail. Of course, in most scenes using textures more than one texture will be required, and with the current set of programs this would not be possible.

OpenGL (from version 1.1 onwards) solves this by creating and using named textures. The same function call (confusingly) is used for both purposes. The first time it is called it creates the named texture, subsequent calls use the named texture. The call to use is:

glBindTexture( GLenum target, GLuint texture );

where target is one of

GL_TEXTURE_1D
GL_TEXTURE_2D

and texture is the name of the texture. You can use the function

glGenTextures( GLsizei n, GLuint *textures )

to create texture names. Where n is the number of texture names to be created and textures is an array in which the names are stored. We need to declare this array again as a global object as it will be used in different contexts.

GLuint	textures[2];		/* Storage for 2 textures. */


Introduction to OpenGL - L20a

Using Many Different Textures

Having declared the array for storing the texture names we must generate the names, assemble the texture maps and assign (bind) them to the names.

GLvoid LoadTexture(GLvoid)
{
Image *TextureImage;

    glGenTextures( 2 , textures );   /* Generate 2 "texture names" */

    TextureImage = (Image *) malloc(sizeof(Image));
    if (TextureImage == NULL) {
        printf("Error allocating space for image");
        exit(1);
    }

    if (!ImageLoad("crate.bmp", TextureImage)) {
        exit(1);
    }

    /*  2d texture, level of detail 0 (normal), 3 components (red, green, blue),            */
    /*  x size from image, y size from image,                                              */
    /*  border 0 (normal), rgb color data, unsigned byte data, and finally the data itself. */

    glBindTexture(GL_TEXTURE_2D, textures[0]);
    glTexImage2D(GL_TEXTURE_2D, 0, 3,
                 TextureImage->sizeX, TextureImage->sizeY,
                 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage->data);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); /*  cheap scaling when image bigger than texture */
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); /*  cheap scaling when image smalled than texture*/

    free( TextureImage->data );
    free( TextureImage );

This code is repeated (except for the glGenTextures() call), for the second texture and this is bound to the second name with:

    glBindTexture(GL_TEXTURE_2D, textures[1]);

So again, a fairly simple process. The next stage of course is to select which texture is to be used when assigning the texture coordinates in the drawing function.


Introduction to OpenGL - L21

Depth Cueing, Fog etc

As objects get further away from the viewpoint they get smaller, have less detail and also fade into the distance. OpenGL uses the concept of fog to describe similar forms of atmospheric effects (e.g. haze, mist, fog etc.). with the OpenGL fog functions you can describe the colour of the fog, its density and the rate at which objects fade as they move into the distance.

To use fog we simply set up the parameters and enable it. In the exmaple program the following code is inserted into the initialisation function.

GLfloat fogcolour[4]={0.0,0.0,0.0,1.0};         /* BLACK fog */

  glFogfv(GL_FOG_COLOR,fogcolour);              /* Define the fog colour */
  glFogf(GL_FOG_DENSITY,0.1);                   /* How dense */
  glFogi(GL_FOG_MODE,GL_EXP);                   /* exponential decay */
  glFogf(GL_FOG_START,-30.0);                   /* Where wwe start fogging */
  glFogf(GL_FOG_END,5.0);                       /* end */
  glHint(GL_FOG_HINT, GL_FASTEST);              /* compute per vertex */
  glEnable(GL_FOG);                             /* ENABLE */

The GL_FOG_MODE can be one of

GL_LINEAR f = (end-z)/(end-start)

GL_EXP f = e-(density.z)

GL_EXP2 f= e-(density.z)2

where f is used to calculate the colour of a graphics object as per

Colour = fCi + (1-f)Cf

where Ci is the incoming colour, Cf is the fog colour, and z is the eye-coordinate distance between the viewpoint and the object centre. end and start are defined using glFog.


Introduction to OpenGL - L21a

Depth Cueing, Fog etc

Shift-Click here to obtain the source for Lesson 20. Save it in your account as L21.c

Now compile and link this program using the system-wide simple command

% makeglut L21
Note that there is NO extension required. This will compile the program L21.c, link it with the GLUT libraries and OpenGL libraries to make an executable program called L21. You can now run this by giving the command

% L21

Press the "z" key and the quad and triangle should now move in and out of the distance. You should see the effect of the fog as they move away from the viewpoint.

Try changing the colour of the fog. Try changing the density, and also try changing the fogging functions.

Now proceed to the next lesson.


Introduction to OpenGL - L22

Automatic mipmaps

We have seen in a previous lesson how to set up a series of texture maps at lower resolutions to be used by the texture mapping software in the minification process. Setting up a multitude of different texture maps for all the textures in a scene which may be subject to the minification process could turn out to be very laborious. To help in this there is a GL Utility function which will create the series of maps for you once the initial high resolution image has been read in. This is the first use of a glu library function:

GLint gluBuild2DMipmaps( GLenum target,
                         GLint components,
                         GLsizei width,
                         GLsizei height,
                         GLenum format,
                         GLenum type,
                         const void *data )

where the parameters are the same as for glTexImage2D().

We simply need to read in the level 0 texture map and then call this function to create the series of maps.

   glTexImage2D(GL_TEXTURE_2D, 0, 3,
                 TextureImage->sizeX, TextureImage->sizeY,
                 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage->data);
   gluBuild2DMipmaps(GL_TEXTURE_2D, 3,
                 TextureImage->sizeX, TextureImage->sizeY,
                 GL_RGB, GL_UNSIGNED_BYTE, TextureImage->data);


Introduction to OpenGL - L22

Automatic mipmaps

Shift-Click here to obtain the source for Lesson 22. Save it in your account as L22.c

Now compile and link this program using the system-wide simple command

% makeglut L22
Note that there is NO extension required. This will compile the program L22.c, link it with the GLUT libraries and OpenGL libraries to make an executable program called L22. You can now run this by giving the command

% L22

Now proceed to the next lesson.


Introduction to OpenGL - L23

Displaying Text

Unfortunately displaying text is not one of the strong points of OpenGL. However, it is possible to display bit mapped text relatively easily, and there are various 3rd party solutions which can provide 3D extruded text and so on. In this lesson we will use the GLUT library text. The available bit mapped fonts are:

GLUT_BITMAP_8_BY_13
GLUT_BITMAP_9_BY_15
GLUT_BITMAP_TIMES_ROMAN_10
GLUT_BITMAP_TIMES_ROMAN_24
GLUT_BITMAP_HELVETICA_10
GLUT_BITMAP_HELVETICA_12
GLUT_BITMAP_HELVETICA_18

and the available stroke fonts are

GLUT_STROKE_ROMAN
GLUT_STROKE_MONO_ROMAN

To render the text on to the scene we use the GLUT library functions

void glutBitmapCharacter(void *font, int character);
void glutStrokeCharacter(void *font, int character);

where font is the bitmap font to use, and character is the character to render.

Notice that each of these functions render a single character. Also, note there is no specification of the location of the character. The characters are drawn at the current raster position. This can be set with the OpenGL funtcion call

glrasterPos2f( x,y );

or one of the variants. It is therefore useful to write a function which will display a string of text starting at a specific scene location.


Introduction to OpenGL - L23a

Displaying Text

The generalised function for 18 point text could look like this

void
DrawText(GLint x, GLint y, char* s, GLfloat r, GLfloat g, GLfloat b)
{
    int lines;
    char* p;

    glMatrixMode(GL_PROJECTION);
     glPushMatrix();
     glLoadIdentity();
     glOrtho(0.0, glutGet(GLUT_WINDOW_WIDTH),
        0.0, glutGet(GLUT_WINDOW_HEIGHT), -1.0, 1.0);
     glMatrixMode(GL_MODELVIEW);
      glPushMatrix();
      glLoadIdentity();
      glColor3f(r,g,b);
      glRasterPos2i(x, y);
      for(p = s, lines = 0; *p; p++) {
      if (*p == '\n') {
          lines++;
          glRasterPos2i(x, y-(lines*18));
      }
      glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, *p);
      }
      glPopMatrix();
     glMatrixMode(GL_PROJECTION);
     glPopMatrix();
     glMatrixMode(GL_MODELVIEW);
}

Notice we save the current projection matrix and set up a new projection which is an orthographic projection (basically a parallel projection with defined clipping). Next we switch to the modelview matrix and save that and then set up unitary scaling, which means we work in window pixel coordinates.

We then set the colour, move to the required raster position and then loop through our string (simulating a new line if need be), and then write the character one at a time.

Finally, we restore the modelview matrix, then the projection matrix and last but not least, set the matrix mode to modelview. We need to have some convention which matrix mode we are in if we are playing with matrix operations - always assuming modelview is a good idea (and of course always making sure this is the case after matrix operations.


Introduction to OpenGL - L24

Mouse Interaction and Full Screen

So far we have done all our interaction via the keyboard. It would be useful to be able to move objects around using the mouse. Again we have to register more callback functions to deal with the various mouse and mouse pointer related events. The mouse callback is

void glutMouseFunc(void (*func)(int button, int state,
                             int x, int y));

where button is one of

GLUT_LEFT_BUTTON    GLUT_MIDDLE_BUTTON   GLUT_RIGHT_BUTTON

and state is one of

GLUT_UP   GLUT_DOWN

If a GLUT_DOWN was detected there will of course be a GLUT_UP event generated when the button is released, even if the mouse moves out of the window.

and finally x and y are the window relative coordinates when the mouse button state changed.

It is possible to get continuous x and y coordinate update using the callbacks

void glutMotionFunc(void (*func)(int x, int y));
void glutPassiveMotionFunc(void (*func)(int x, int y));

where MotionFunc returns the coordinates when the mouse is moving with a mouse button pressed and PassiveMotionFunc returns coordinates continuously with no button pressed when the mouse pointer is in the current window.

Lastly, it is possible to get a callback event when the mouse pointer enters or leaves the current window using

void glutEntryFunc(void (*func)(int state));

where state is one of

GLUT_ENTERED   GLUT_LEFT

With careful use of these callbacks, mouse interaction can be implemented.


Introduction to OpenGL - L25

Display Lists

A display list is a convenient and efficient way to name and organise a set of OpenGL commands. A display list is a group of OpenGL commands which have been stored for later execution.

When a display list is invoked, the commands are executed in the order in which they were stored. OpenGL display lists are designed to optimize performance and graphics displayed as display lists typically run twice as fast as in immediate mode.

An OpenGL display list is actually a cache of commands, not a dynamic database, and thus the contents once created cannot be modified. The graphics commands stored will be optimized for efficient use of the particular hardware in use on the machine. Over a network, the display list contents will reside on the server, not on the client machine.

A display list is started by a call to:

glNewList( GLuint list , GLenum mode );

where:

list is a unique positive integer which identifies the list

and

mode can be GL_COMPILE or GL_COMPILE_AND_EXECUTE.

use GL_COMPILE if you do not want the commands executed when the display list is created

The end of a display list is marked by a call to:

glEndList(void);

Once created a display list is executed by a call to:

glCallList(GLuint list);

A display list can contain OpenGL state changes, and these should be taken into account by any immediate mode OpenGL graphics calls executed after the execution of the display list.

If you want to retain the current state from before the call to the display list, the current state can be saved and restored using glPushAttrib() and glPopAttrib(). These calls can also be inserted into the display list for optimal processing speed.


Introduction to OpenGL - L26

Buffer Tests

When you draw geometry, text or images, OpenGL performs many calculations to transform the fragment, determines lighting, does perspective transformations, calculates the colour of every pixel which needs to be drawn etc. Once OpenGL has determined that the fragment should be generated and what colour it should be, several more stages of processing are required in order to determine if the fragment should be drawn as a pixel in the framebuffer.

If it's outside the window or farther from than viewpoint than the pixel already in the buffer, it's not drawn. In another stage the fragment's colour is blended with the colour of the pixel already in the framebuffer.

The following tests are performed in this order:

1. Scissor test. 2. Alpha test.
3. Stencil test. 4. Depth test.
5. Blending. 6. Dithering.
7. Logical operations.

1. Scissor Test

This test is just a rectangular version of the stencil test. However, because it is very easy to do this test in hardware, it is a special case. If a fragment lies inside the defined rectangle, it is drawn. The default rectangle matches the size of the window and scissoring is disabled.

The function to define a new scissor test is:

glScissor( GLint x, GLint y, GLsizei width, GLsizei height )

The parameters specify the lower left corner (x/y) and the size of the rectangle.

The scissor test is enabled and disabled in the usual way using:

glEnable( GL_SCISSOR_TEST ) and glDisable( GL_SCISSOR_TEST )

You can also use glIsEnabled( GL_SCISSOR_TEST ) to determine if scissoring is enabled.

glGetIntegerv( GL_SCISSOR_BOX ) is used to return the values which define the scissor rectangle.


Introduction to OpenGL - L27

Buffer Tests - Alpha

If you have requested a frame buffer in RGBA format (as all the examples to date do!), then you can apply the alpha test to accept or reject a fragment based on the value of the alpha channel. You can set a reference value against which the alpha value of the fragment is tested according to a specified comparison function.

As always you can enable/disable this function with:

glEnable( GL_ALPHA_TEST ) and glDisable( GL_ALPHA_TEST )

The comparison function and the alpha reference value are specified with:

glAlphaFunc( GLenum func , GLclampf ref)

ref is clamped to lie between 0.0 and 1.0 and

func can be one of:

GL_NEVER Never accept
GL_ALWAYS Always accept
GL_LESS Accept if fragment alpha < ref
GL_LEQUAL Accept if fragment alpha <= ref
GL_EQUAL Accept if fragment alpha = ref
GL_GEQUAL Accept if fragment alpha >= ref
GL_GREATER Accept if fragment alpha > ref
GL_NOTEQUAL Accept if fragment alpha not equal to ref

e.g.

   glEnable ( GL_ALPHA_TEST );
   glAlphaFunc ( GL_GREATER, 0.5 );

(c) 2002 Dr R P Fletcher, Computing Service


Introduction to OpenGL - L27a

Buffer Tests - Alpha

In order to use this function we must provide colour information using the red, green, blue and alpha channels. We have used the GL function family glColor3x() to provide colour information. In order to use the alpha channel we must use the GL function family glColor4x().

e.g.

   glColor4f(1.0,0.0,0.0,0.5);  // Red with alpha = 0.5

A simple example will show 2 coloured rectangles but allow you to change the alpha values via the keyboard. When the alpha values entered no longer match the accept fragment criterion, the rectangle should disappear.

We add 2 global variables to hold the alpha values:

   GLfloat alphar=0.6;
   GLfloat alphab=0.6;

In the InitGL() function we add the 2 lines:

  glEnable( GL_ALPHA_TEST );
  glAlphaFunc( GL_GREATER, 0.5 );

and in the drawing function we set the colours thus:

  glColor4f(1.0,0.0,0.0,alphar);
  glBegin(GL_QUADS);                /* start drawing the first quad.*/
  glVertex3f( 1.0, 1.0, 1.0);        /* Top Right Of The Quad (Front)*/
  glVertex3f(-1.0, 1.0, 1.0);        /* Top Left Of The Quad (Front)*/
   ... etc
  glColor4f(0.0,0.0,1.0,alphab);
  glBegin(GL_QUADS);                /* start drawing the second quad.*/
  glVertex3f( 1.0, 1.0, 1.0);        /* Top Right Of The Quad (Front)*/
   ... etc

Our keyboard function adds in bindings for the r/R and b/B keys to increment and decrement the alpha values.

     case 'r':
        alphar += 0.05; printf("Red alpha = %f\n",alphar);
        break;
     case 'R':
        alphar -= 0.05; printf("Red alpha = %f\n",alphar);
        break;

(c) 2002 Dr R P Fletcher, Computing Service


Introduction to OpenGL - L28

Buffer Tests - Stencil

This lesson not yet available


Introduction to OpenGL - L29

Buffer Tests - Depth

This lesson not yet available


Introduction to OpenGL - L30

Accumulation Buffer

This lesson not yet available


Introduction to OpenGL - L0a

Running the first program. L0.c

The first programs in this series of lessons will be given to you. As we move on and you learn more OpenGL you will be able to create your own graphics.

Shift-Click here to obtain the source for Lesson 0. Save it in your account as L0.c

Now compile and link this program using the system-wide simple command

% makeglut L0
Note that there is NO extension required. This will compile the program L0.c, link it with the GLUT libraries and OpenGL libraries to make an executable program called L0. You can now run this by giving the command

% L0

We now need to look at the output from the program. First we should see a window in the top left of the screen, with a window title "L0". At first sight it looks like just a frame.
We also see "drawing called" in the shell window where the program was run. Now try moving the window. What we see is that the window has "captured" the pixels on the screen which were present when the window was opened. Try resizing the window. This time we again see the "drawing called" message, indicating our "DrawGLScene" function was called. However, the enlarged window now has no extra graphics drawn in it. Do some playing around to see what you have to do to get the window to capture all the underlying pixels again.

Now close the program by pressing <ctrl>-c in the shell window, or by using the window close menu item. We will design a better way of terminating an OpenGL graphics program.

Now proceed to the next lesson.


Introduction to OpenGL - L1a

Extending the Setup code and adding some graphics

We also need to add in the function callback to process the keyboard presses we make. In this example, we will only process the ESCAPE key. In the main set up part of the program we add the call:

glutKeyboardFunc(NormalKey);         /*Normal key is pressed */

Which means that whenever a "normal" key is pressed (that is not a function key F1 to F12, or Page Up/down etc) the function NormalKey will be called, which we have to supply. The code for this is quite easy:

void NormalKey(GLubyte key, GLint x, GLint y)
{
    switch ( key )    {
     case ESCAPE :
        printf("escape pressed. exit.\n");
    glutDestroyWindow(window);	/* Kill our window */
    exit(0); 			/* Very dirty exit */
        break;				/* Do we need this??? */
     default:
    break;
    }
}

The parameter key contains the key pressed, and usefully, the parameters x and y hold the screen coordinates where the mouse pointer was when the key was pressed.


Introduction to OpenGL - L2a

Discussion of the rendering

In this part of the lesson we will discuss the way the various elements of the scene are rendered, starting with the points.

  glColor3ub(255,0,0);                   /* Set the colour rgb, 0-255 */
  glBegin(GL_POINTS);                    /* Points */
   for (i=0, xy=0.0 ;i<10;i++, xy+=0.1)
     glVertex3f(xy,xy,-6.0);             /* where */
  glEnd();

First we set the colour using glColor3ub(). The form we have used here is to specify red, green and blue (the "3" component) and using unsigned bytes as the arguments (hence the "ub" and therefore in the range 0 to 255). We have set the point colour to be full red.

Next we bracket the rendering of the primitives with a glBegin() and glEnd() pair of function calls. The glBegin() call takes a single argument which is the primitive type you wish to render, which for this part is points (GL_POINTS). We now set up a small loop and specify each vertex using the "3f variant. Notice we set the "Z" value to -6.0, that is, a little into the screen. Since all the points will be at the same screen depth, they will be co-planar.

The next section is the drawing of the line primitives.

  glColor3f(0.0,1.0,0.0);                /* Set colour rgb, 0.0 - 1.0 */
  glBegin(GL_LINES);                     /* Lines this time */
   for (i=0, xy=0.0 ;i<10;i++, xy+=0.1) {
     glVertex3f(xy,xy,-6.0);             /* need start and end points */
     glVertex3f(xy+1.0,xy,-6.0);
   }
  glEnd();

This time we have specified the colour using the 3f variant of the glColor function call. Again we are specifying the red, green and blue components of the colour, but now in the range 0.0 to 1.0, and this time we have set the lines to be full green.

We now bracket the rendering primitive again and this time we set the primitive type to be GL_LINES This time we need to supply 2 vertices, one for the start of the line and another for the end of the line. All the lines start at the same place as the points and are drawn the same length in the positive "X" direction. Once again, we set the "Z" axis component to be -6.0, that is, still co-planar with the points.


Introduction to OpenGL - L3a

More on Colour

To enable the different shading model we need to add in the appropriate call to glShadeModel() in the initialisation function.

GLvoid InitGL(GLfloat Width, GLfloat Height)
{
  glClearColor(0.0, 0.0, 0.0, 0.0);		/* This Will Clear The Background Color To Black */
  glLineWidth(2.0);                             /* Add line width,   ditto */
  glShadeModel(GL_SMOOTH);                      /* Set the color model */
  Transform( Width, Height );                   /* Perform the transformation */
}

In order to see the effect we also need to draw something and specify the colours at the appropriate vertices.

GLvoid DrawGLScene()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	/* Clear The Screen And The Depth Buffer */

  glBegin(GL_LINES);                     /* Lines */
     glColor3f(1.0,0.0,0.0);             /* Set colour Red */
     glVertex3f(0.0,0.0,-6.0);           /* need start and end points */
     glColor3f(1.0,1.0,0.0);             /* Set colour Yellow */
     glVertex3f(2.0,2.0,-6.0);
  glEnd();

  glBegin(GL_QUADS);                     /* Quads */
     glColor3f(1.0,0.0,0.0);             /* Set colour red */
     glVertex3f(-2.0,-2.0,-6.0);
     glColor3f(1.0,1.0,0.0);             /* Set colour yellow */
     glVertex3f(-2.0,2.0,-6.0);
     glColor3f(1.0,0.0,0.0);             /* Set colour red */
     glVertex3f(0.0,2.0,-6.0);
     glColor3f(1.0,1.0,0.0);             /* Set colour yellow  */
     glVertex3f(0.0,-2.0,-6.0);
  glEnd();

  printf("drawing called\n");
}

Notice that we now specify a colour for each vertex for both the line and quadrilateral. OpenGL will perform the colour interpolation for us to give a smooth transition of colours across the graphic objects you have drawn.


Introduction to OpenGL - L4a

Retrieving State information

In this lesson we will query the state of the windows and the window buffering mode. We will use the GLUT library functions to return the information in an easily accessible manner. In the same program we will also return the information about the hardware capabilities for drawing points using the OpenGL state retrieval functions.

We will use the program L2.c as the base code. Firstly two new variables are added into our global section, viz. sizes[2] and step.

/* ASCII code for the escape key. */
#define ESCAPE 27

GLint window;           /* The number of our GLUT window */
GLint Xsize=400;
GLint Ysize=400;

GLfloat sizes[2];       /* Store supported point size range */
GLfloat step;           /* Store supported point size increments */

We need to add some query information into the keyboard handling routine,

     case 'P':
     case 'p':
        glGetFloatv(GL_POINT_SIZE_RANGE,sizes);
        glGetFloatv(GL_POINT_SIZE_GRANULARITY,&step);
        printf("Point size ranges from %f to %f at %f steps.\n",
               sizes[0],sizes[1],step);
        break;
     case 'W':
     case 'w':                          /* Tell us about the window */
        printf("Current window size is %d x %d pixels in %s mode\n",
               glutGet(GLUT_WINDOW_WIDTH),
               glutGet(GLUT_WINDOW_HEIGHT),
               glutGet(GLUT_WINDOW_DOUBLEBUFFER) ? "double buffer" :
                                                   "single buffer");
        break;

When the w key is pressed the state of the OpenGL machine is queried by the 3 calls to glutGet and a message is printed in the shell window. Note that the query about the buffering mode results in the correct text being delivered to the printf() function.

When the p key is pressed we get information returned about the available point sizes and the minimum step increment between the different sizes. Notice this time we are using the OpenGL function calls.

We will use the value of the returned state variables in later lessons in this course.


Introduction to OpenGL - L5a

Translation in x,y and z

The first transformation we will look at is to move the object around in the 3D space. Left/right and up/down are quite obvious, but in the Z dimension a positive value moves the object towards the viewer (and so it appears larger) and a negative value moves the object away from the viewer (and consequently it appears smaller).

To implement this we will use the 3 keys x,y and z to add a small increment to the arguments to the gl translate function and upper case X, Y and Z to subtract the same increment. We need to keep these 3 variables in the global section. The program will draw a simple shaded quad originally in the centre of the screen which can be moved around using these keys.

/* ASCII code for the escape key. */
#define ESCAPE 27

GLint window;           /* The number of our GLUT window */
GLint Xsize=400;
GLint Ysize=400;

GLfloat xt=0.0,yt=0.0,zt=0.0;   /* x,y,z translation */

In the keyboard handling routine we will add in the simple simple code to add or subtract a value from these variables.

     case 'x':                          /* Move to right */
        xt += 0.2;
        glutPostRedisplay();
        break;
     case 'X':
        xt -= 0.2;                      /* Move to left */
        glutPostRedisplay();
        break;

      case 'y':                          /* Move up */
        yt += 0.2;
        glutPostRedisplay();
        break;
     case 'Y':
        yt -= 0.2;                      /* Move down */
        glutPostRedisplay();
        break;

      case 'z':                          /* Move forward */
        zt += 0.2;
        glutPostRedisplay();
        break;
     case 'Z':
        zt -= 0.2;                      /* Move away */
        glutPostRedisplay();
        break;

Notice that we request a redisplay of the scene, using glutPostRedisplay(), in order to see the effect of changing the value of the variables xt,yt and zt.

Of course, we now need to implement the translation in the GL scene drawing function.

GLvoid DrawGLScene()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	/* Clear The Screen And The Depth Buffer */

  glPushMatrix();

  glLoadIdentity();
  glTranslatef(0.0,0.0,-6.0);
  glTranslatef(xt,yt,zt);

  glBegin(GL_QUADS);
    glColor3f(1.0,1.0,0.0);
    glVertex3f( 1.0f, 1.0f, 1.0f);
    glColor3f(1.0,0.0,0.0);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glColor3f(1.0,1.0,0.0);
    glVertex3f(-1.0f,-1.0f, 1.0f);
    glColor3f(1.0,0.0,0.0);
    glVertex3f( 1.0f,-1.0f, 1.0f);
   glEnd();

  glPopMatrix();

  printf("drawing called\n");
}

Notice that we are now drawing a unit quad about the origin. Also, since we are simply either adding or subtracting a constant to the variables used for the translation, we need to apply the transformation afresh. Transformations are also cumulative as the current viewing matrix is multiplied by a vector representation of the transformation requested. So, we need to first move the cube into view, and this is accomplished by the simple gl function call glTranslatef(0.0,0.0,-6.0). We then further transform the scene with our user controlled transformation as specified by glTranslatef(xt,yt,zt).

In order to keep the transformation local (in a real program there may be any number of transformations on global and local objects), we save our transformation by pushing the current transformation matrix onto a stack, do the transformation, and then restore the transformation. Also, in order to have a completely clear transformation to begin, we call the gl function glLoadIdentity(), which loads a unitary transformation in to the transformation matrix (i.e. one which does nothing!).


Introduction to OpenGL - L6a

Scaling

Shift-Click here to obtain the source for Lesson 6. Save it in your account as L6.c

Now compile and link this program using the system-wide simple command

% makeglut L6
Note that there is NO extension required. This will compile the program L6.c, link it with the GLUT libraries and OpenGL libraries to make an executable program called L6. You can now run this by giving the command

% L6

The program should draw a centred shaded quad. Pressing the x, y, and z keys (and the upper case keys) will perform the scaling in the appropriate dimension.

Try commenting out the calls to PushMatrix() and PopMatrix() and observe the behaviour. This will teach another valuable lesson on coordinate transformation.

Now proceed to the next lesson.


Introduction to OpenGL - L7a

Rotation

Shift-Click here to obtain the source for Lesson 7. Save it in your account as L7.c

Now compile and link this program using the system-wide simple command

% makeglut L7
Note that there is NO extension required. This will compile the program L7.c, link it with the GLUT libraries and OpenGL libraries to make an executable program called L7. You can now run this by giving the command

% L7

The program should draw a centred shaded quad. Pressing the x, y, and z keys (and the upper case keys) will perform the rotation, and rotate the object around the vectors specified.

The rotation of the quad about the vector (0.0,0.0,1.0) -> (0.0,0.0,0.0) should be quite obvious ... this is a ray from a point one unit out of the screen to the origin, which is at the centre of the quad. So, you would expect the "Z" key to rotate the object within the Z plane.

Make sure you understand how the 3 different rotation transformation calls affect the object in the x, y and z planes.

Now proceed to the next lesson.


Introduction to OpenGL - L8a

Rotation

Shift-Click here to obtain the source for Lesson 8. Save it in your account as L8.c

Now compile and link this program using the system-wide simple command

% makeglut L8
Note that there is NO extension required. This will compile the program L8.c, link it with the GLUT libraries and OpenGL libraries to make an executable program called L8. You can now run this by giving the command

% L8

This program will draw a wireframe cube. Pressing the x, y, and z keys (and the upper case keys) will perform the rotation, and move the object around.

Notice that the lines of the cube cross each other, but you should also notice that they do not observe their correct 3D depth, but simply the order in which they are drawn. So, the 3D object is actually incorrect.

Now proceed to the next lesson.


Introduction to OpenGL - L9a

Drawing another 3D object

Shift-Click here to obtain the source for Lesson 9. Save it in your account as L9.c

Now compile and link this program using the system-wide simple command

% makeglut L9
Note that there is NO extension required. This will compile the program L9.c, link it with the GLUT libraries and OpenGL libraries to make an executable program called L9. You can now run this by giving the command

% L9

This program will draw a solid cube. Pressing the x, y, and z keys (and the upper case keys) will perform the rotation, and rotate the object around the origin.

Notice that the faces of the cube do not observe their correct 3D depth, but are simply rendered in the order in which they are drawn. So, again, the 3D object is incorrect but visually more erroneous.

You may wish to modify the program to draw the faces with smooth shading to make the object more visually appealing!

Now proceed to the next lesson.