by Chris Halsall
Rendering in OpenGL
The function registered as the Display (and usually Idle) callback is where control is passed whenever it's time to draw the next frame of the display. For our program this is the cbRenderScene() function, and it's also where our motion calculations are done.
Figure 1. With different drawing mode settings, a wide range of visual effects can be created.
If you take a look at the function, you'll see it first simply enables or sets different modes depending on several global variables. This is where lighting, textures, and blending are enabled and disabled as appropriate. Take a close look at the settings requests for the filtering; it only affects textures.
Next we're asked to work with the GL_MODELVIEW matrix, which lets us make transformations that work on the model, or objects, of our scene. Into this matrix we load the Identity matrix, which is simply a way of resetting things to the origin, so there's no rotation, translation or scaling. Lastly, we translate the model out Z_Off distance from the screen so it can be seen and rotate the model as appropriate.
Let the drawing begin!
Now we're ready to start the actual drawing. (Yippee!) The first thing we do is clear both the color (frame) buffer and the depth buffer. It should be pretty obvious why we do this, but it can be both amusing and enlightening to try not clearing either or both buffers. Some applications can even guarantee that they'll be painting the whole screen themselves, so you can save a few cycles by not clearing the color buffer; the depth buffer must still be cleared, however.
Figure 2. By turning double-buffering off, you can watch OpenGL draw each frame. Note that OpenGL draws from the bottom up, and each Quad is made up of two triangles.
After setting the normal, we next call glColor4f() with the RGBA settings for a red face with a 75% level of opaque. Lastly, to complete our first face, we make four calls to glTexCoord2f() interleaved with four calls to glVertex3f() to define the position of the texture on the polygon and the position of the polygon in our 3D world: the floor.
We next do the same type of thing for the top-most grey face, but note that the texture coordinates are different. The third face rendered, painted green, also has unusual texture coordinates, but this time they're non-regular. Compare the code to the visual results, and tweak to see what kinds of effects can be achieved. Both the grey and green faces are set to be 50% opaque.
The fourth face, blue, is the first face with the full texture being requested across its face, and it is adjusted to be only 25% opaque. The second-to-last face to be drawn demonstrates how OpenGL can smoothly blend colors across the face of a polygon, ranging from 90% red, green or blue in three corners to black in the fourth.
The last face, painted yellow, shows that the same kind of blending applies for the alpha-channel of a polygon. The yellow face ranges from full transparent at one corner to fully opaque at another. After we're finished drawing the yellow face, we call glEnd() to tell OpenGL we're finished drawing Quads (at least, for now).
After drawing the cube, we next want to render some text. However, we want it to stay on the screen at the same location, and not spin around with the cube (although that might be neat). To do this, we first reload the Identity matrix, so we're back to the origin again. Then we call glMatrixMode() with GL_PROJECTION, which tells OpenGL we want to work with our projection matrix.
Now, we've already got a nice projection matrix loaded, which we need to render our rotated cube. However, we also need to reset this with an orthogonal projection matrix to be able to render in the true coordinate system of the display. For just this type of situation, OpenGL lets you save matrices by using the glPushMatrix() call. After doing this, we can change the projection matrix as needed, and we can glPopMatrix() the saved matrix back later.
Next, we disable texturing and lighting in case they were activated, since lit or textured text isn't what we want. The color for the text is then set, including a 75% level of transparency, just for fun. Then our code simply renders a text string using sprintf(), which is then painted on the screen with a call to glRasterPos2i() and then another to ourPrintString(). The latter is a simple support function which loops through a string, calling the GLUT library to render a bitmap for each character as needed. Note that in the OpenGL system, (0,0) is the bottom-left of the screen.
The next section of code renders the frames-per-second value and the current frame count in a similar fashion as above. One extra bit of code first renders a small, mostly opaque dark rectangle for the FPS value to be rendered onto. This technique is often used to ensure that some important bit of text can always be seen, regardless of what might be rendered behind it.
After all the text rendering is complete, we call glPopMatrix() to recover our saved projection matrix. Then, since we've completed all the drawing we're going to, we call glutSwapBuffers() to display our just-drawn image to the user. It's at this point that we do our motion calculations, and then we make a call to ourDoFPS() in order to collect the FPS stats. Then we exit the function, returning control back to OpenGL.
Do that about 50 times a second or so, and you've got a pretty good idea what's involved in an OpenGL application loop. Obviously, for an application to be useful (or at least amusing), a great deal more should be going on in the scene. But what we've covered here is the core basis for most OpenGL applications.
Pages: 1, 2