This lab is an introduction to Display Lists and Animation
The limited commands that are acceptable between a glBegin() / glEnd() pair only describe an OpenGL primitive. Therefore if you are going to repeatedly execute the same sequence of OpenGL commands, you can create and store a display list and then have this cached sequence of calls repeated with minimal overhead, since all of the vertices, lighting calculations, textures, and matrix operations stored in that list are calculated only when the list is created, not when it is replayed.
Creating a display list is very simple. Let's suppose that we have some code that creates a static model. The important part of the code is within the glBegin() / glEnd() pair. The function for the stock scene looks like this:
// start rendering the stock scene glBegin (GL_POLYGON); ... ... details // finish rendering the stock scene glEnd ();
A display list records the results of a glBegin() / glEnd() sequence. When you record a display list, the current state is used to create the display list; when the list ends, the current state is whatever the original state was, plus any changes made during the recording of the displaying list. Replaying the display list uses the current state, not the state that was in effect when the list was recorded. When the replay ends, the current state is the state when the replay started plus any any changes to the state that were replayed as part of the display list.
To use a display list you will need to use the following commands:
When creating a display list, you use the glNewList() and glEndList() commands as separators. The first function – glNewList() – takes two arguments: the first is a unique integer argument used to identify the display list; the second is a flag indicating whether to simply compile the display list or to compile and immediately execute the display list. The glEndList() function takes no argument and simply serves as a delineator.
Here is how to create a display list:
// get a unique identifier for the display list GLuint myList; myList = glGenLists(1); // start recording the display list glNewList(myList, GL_COMPILE); // just record for now // start rendering the display list glBegin (GL_POLYGON); ... ... details // finish rendering the stock scene glEnd (); glEndList(); //finish creating the display list
The commands executed between the pair use the parameter values in effect when the list was created, not values when the list is replayed. Thus if you generate a display list that's based on a vector pointer, only the values of the pointer are used, not the pointer itself. In other words, you can not set up the array of vertices, create the display list using the array, change the vertices in the array, and then execute the display list and expect the new array values to be effective.
The following commands are not complied into the display list but are executed immediately, regardless of the display list.
glIsList( ) glGenLists( ) glDeleteLists( ) glFeedbackBuffer( ) glSelectBuffer( ) glRenderMode( ) glReadPixels( ) glPixelstore( ) glFlush( ) glFinish( ) glIsEnabled( ) glGet*( )
You should note that calling glNewList() while inside another glNewList() will generate an error.
Executing a display list is done with one of two functions. One is a specialized form that is used for sequences of display lists. The other is the basic method of calling a single display list. The glCallList( ) command takes a single argument, the unique integer ID that you gave a previously defined display list. When a display list is executed, it is as if the display list commands were inserted into the place where you made the glCallList( ) command, except that any state variables that have changed between the display list creation and execution times are ignored. However, if any state variables are changed from within the display list, they remain changed after the display list completes execution. glPushAttrib() and glPopAttrib() were created for such cases. These commands save and restore state variables. Refer to the manual for a full list of attributes you can save.
To call the display list, the following command is used:
glCallList(myList);
Suppose the list is created as follows:
glNewList(myList, GL_COMPILE); glBegin(GL_QUAD); glVertex3f(1, 0, -1); glVertex3f(1, 0, 1); glVertex3f(-1, 0, 1); glVertex3f(-1, 0, -1); glEnd( ); glTranslatef(1, 0, 1); glEndList( );
Note there is a translation after the vertices have been defined. This causes the vertices following the execution of this display list to be translated. To draw five connected squares, the following commands is used:
glCallList(myList); glCallList(myList); glCallList(myList); glCallList(myList); glCallList(myList);
This sequence also leave the ModelView matrix translated five units in the +z and +x directions.
When glNewList( ) encounter a call to glCallList( ), the command that is inserted is a call to that display list, not the commands represented by that display list. For example, consider the following code:
GLuint lists; // create three display lists lists = glGenLists(3); glNewList(lists+0, GL_COMPILE); ... ... glEndList( ); glNewList(lists+1, GL_COMPILE); ... ... glEndList( ); glNewList(lists+2, GL_COMPILE); ... ... glEndList( );
This creates three unique display lists. To generate a hierarchical display list:
GLuint nested_list; // create a hierarchical display list nested_list = glGenLists(1); glNewList(nested_list, GL_COMPILE); glCallList(lists); // +0 not really needed glCallList(lists+1); glCallList(lists+2); glEndList( );
The following line will execute the nested display list and thus call the three display lists nested inside:
glCallList(nested_list);
The advantage of hierarchical display list is that you can modify the commands associated with a display list ID and have the new commands executed in other display lists that use this ID.
// modify the second list glNewList(lists+1, GL_COMPILE); ... ... // some different code glEndList( ); // now execute the modified hierarchical list glCallList(nested_list);
You can see the power of associated with the feature. If you are writing an application that uses many static objects in nonstatic ways, such as an architectural program that creates buildings out of standard parts, the ability to cache objects and to replay them whenever you need, plus the ability to nest them, provides the basis for a very powerful modeling feature.
OpenGL has several buffers that store pixel data for various purposes. There's multiple Color Buffers for drawing, the Depth Buffer for depth tests, the Stencil Buffer for masking off parts of the rendering surface, and the Accumulation Buffer for combining many images for special effects. You have made use of various buffers throughout these lessons.
The two most common special buffer uses are Double Buffering which allows the illusion of speed and smooth animation, and the Depth Test which allows us to draw 3D scenes without sorting all the polygons from back to front first. Using these techniques requires a few extra steps that you should be familiar with.
Double buffering is important in two cases. The first is when you don't want the user to see the rendering being performed but would rather see the entire display updated at one time. The other time is when you need to perform animation or some other rapid updating of the OpenGL display.
If you select a pixel format that supports double buffering, glutInitDisplayMode(GLUT_DOUBLE), then OpenGL commands will be directed to the "back" buffer. You can think of this buffer as a memory bitmap that sits off screen. Double-buffered pixel formats are rendered to the back buffer. What's displayed on the screen is called the "front" buffer. The glut call to swap the front and back buffers is called glutSwapBuffers(). This call exchanges the front and back buffers of the specified DC. What this means to the user is that nothing is displayed on the screen until the buffers are swapped. Then the display appears to be "instantly" updated. If the particular video hardware has enough video memory to support both buffers, this can be as fast as simply resetting the beginning offset of the currently displayed memory, which means that your display change could be as fast as the updated frequency of your monitor, usually better than 75 Hz. Remember that if you can get a rate of 15 static frames per second or better your motion can be considered "flicker free".
Note that double buffering isn't really a method of speeding up your program. Rather it is a method of making it appear to run more quickly, simply by letting the user see the individual frames "after" they have been drawn. In some cases, double buffering will make your program run a bit faster, although the effect depends on the rendering time (or the complexity of the scene). For example, complex scenes will take longer to draw and slow down your frame rate.
Remember before you draw to a color buffer you should clear the buffer:glClear(GL_COLOR_BUFFER_BIT);When you are in double buffering mode you can add somthing to the front buffer without redrawing the whole frame by switching the draw buffer like this:
glDrawBuffer(GL_FRONT);after that everything you draw will be drawn to the front buffer and will be immediately visible, no need to swap buffers. You can go back to regular double buffered rendering by calling:
glDrawBuffer(GL_BACK);
Depth testing allows us to draw the primitives that make up a scene in any order. Without it we would need to break the objects in the scene into their individual triangles and sort them from back to front before rendering. This sorting can be computationally expensive and adds complexity to the renderign process. Depth testing simplfies rendering. You can send whole objects to be rendered all at once and no sorting is needed. It requires more memory however. Every pixel, or fragment, needs to store a depth value.
To get depth testing you need a pixel format with a depth buffer. Many modern graphics cards have depth buffering in all available pixel formats. You should still ask for the depth buffer just in case – glutInitDisplayMode(GLUT_DEPTH).
To turn on depth testing you enable it with glEnable(GL_DEPTH_TEST). Before drawing each frame you should clear the depth buffer – glClear(GL_DEPTH_BUFFER_BIT).
You can change the depth test to achieve various effects by using glDepthFunc. It takes the following paramenters: GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER, or GL_NOTEQUAL.