Intro to OpenGL


OpenGL is a 2D/3D graphics library. It is based on IRIS GL, developed at Silicon Graphics for their high-end workstations. You can find more information about the OpenGL standard at www.opengl.org. This document is not a tutorial, but introduces some of the basic concepts of OpenGL that will be used in our projects, and provides pointers into the documentation.

The best book for learning OpenGL is the OpenGL Programming Guide, Second Edition, from the OpenGL Architecture Review Board. You can use the GLTemplate application to experiment with the features of OpenGL.

OpenGL Conventions

All the functions in the OpenGL library have names beginning with "gl". Defined constants have names beginning with "GL_".

It common for there to be a family of functions for performing the same operation, differing only in the number and/or types of arguments accepted by each. The names of these functions have tags at the end to indicate the type of argument. The whole family will be referred to with a star syntax. For instance, glColor*() refers to any of the 32 functions available within OpenGL for setting the current color, including:

glColor3f( GLfloat, GLfloat, GLfloat )
glColor4d( GLdouble, GLdouble, GLdouble, GLdouble )
glColor3ubv( GLubyte* )

There is also a library of utility functions whose names all begin with "glu". These are simply convenience functions for doing some common tasks that take a few OpenGL commands to perform.

OpenGL as a State Machine

OpenGL keeps a large amount of state. It has many modes which can be switched on and off with the glEnable and glDisable commands. You should think of each function call as manipulating this state, rather than performing a specific drawing operation. To enable high performance, implementations of OpenGL are allowed to buffer commands almost arbitrarily. When you have sent some commands to OpenGL and want to be sure that the results actually appear on screen, call glFlush to force OpenGL to complete.

The OpenGL state is kept in a data structure called a rendering context. We will typically create one rendering context for each OpenGL window. This keeps state changes for drawing in one window from interfering with drawing in the other windows. Switching to another context is done by calling wglCurrentContex.

Drawing Primitives

Drawing in OpenGL consists of calling glBegin to tell OpenGL what you want to draw, then a series of calls to glVertex* to specify the geometry, then a call the glEnd to finish up. The single argument to glBegin tells it how to interpret the series of vertices you are going to throw at it. Here are some of the valid things to send to glBegin:

GL_POINTS draws each vertex as a point
GL_LINES draws a segment connecting the vertices 1 and 2, a segment connecting vertices 3 and 4, 5 and 6, and so on
GL_TRIANGLES draws the first three vertices as a triangle, the second group of three as another triangle, and so on
GL_POLYGON takes the vertices as a single, simple, convex polygon

There are several other options, see the manual for more explanation. Note that whenever you tell OpenGL to draw a polygon, it must be flat and convex. Triangles are easy, because they are always flat and convex, but with higher-order polygons you must be careful or your results will look wrong. Each polygon has a front side and a back side, which can be rendered differently if you choose. The front side is defined (by default) as the side from which the vertices appear in counterclockwise order.

The vertices you specify are coordinates in 3D space. They are transformed by the current modelview matrix, then by the current projection matrix to determine where they map to on the screen.

NOTE: Many OpenGL operations are not allowed between a glBegin and a glEnd, including all those listed under Matrix Stacks below. Used inappropriately an OpenGL function will likely fail silently. For basic 2D programs such as Impressionist glVertex* and glColor* will probably be the only two gl* functions used between a glBegin and a glEnd. You can use as much non-OpenGL code as you like, though.

Matrix Stacks

Contained in the OpenGL state are two matrices which affect how geometry appears on the screen. The projection matrix determines the position and orientation of the camera. The modelview matrix is used to transform geometry before rendering it. In our projects, the projection matrix will typically be set up by the skeleton code. You will change the modelview matrix to affect how primitives are drawn on the screen.

The state maintains a stack for each matrix, with the top matrix being the one that is in force. You can thus make temporary changes by pushing a copy, altering the matrix, using it, then throwing it away with a pop. Since a full stack is maintained, you can nest these temporary changes to (almost) arbitrary depth. The useful commands for manipulating these stacks are:

glMatrixMode Selects which matrix stack is being manipulated, GL_PROJECTION or GL_MODELVIEW. (Actually, there is a third one for controlling texture mapping, but you can read about that in the manual.) The remaining commands all affect the currently selected matrix stack.
glLoadIdentity Replaces the top element of the stack with an identity matrix.
glPushMatrix Duplicates the top element of the matrix stack.
glPopMatrix Pops and discards the top element of the stack.
glTranslate*
glRotate*
glScale*
Composes the top element of the stack with a transformation.

Manipulating the Frame Buffer

Frequently you will want to copy around blocks of pixels, rather than drawing individual primitives like lines and polygons. You could do this by drawing individual GL_POINT objects, but that would be horrendously slow and inefficient. Instead, there is a set of OpenGL functions for copying blocks of pixels to and from the framebuffer.

glReadPixels, glDrawPixels read/write a block of pixels from/to the frame buffer
glReadBuffer, glDrawBuffer select which buffer is read/written
glPixelStore* specify how pixels read/written are arranged
glRasterPos* set the starting point for pixel write operations

Keep in mind that we will frequently be using a double-buffered framebuffer to provide smooth animation. With double-buffering, objects are typically drawn into the "back" buffer, which is then swapped with the front buffer (with the SwapBuffers function) to provide the illusion of instantaneous redrawing. It is important to understand which buffer you are reading or writing when doing block pixel operations.