| |
|
|
||||
![]() |
||||||
| |
|
|||||
|
A First Try at Texturing Our first example program, REFLECT.EXE, shows a simple animation. A cube is suspended above a reflective marble surface. We’'ve employed a simple reflection technique that draws the original cube first, then draws the reflected cube below, then blends the floor over the reflected cube. In this example, the window is never validated, so Windows continually repaints the window over and over again. Each time, the rotation of the cube is updated slightly, so we have the simple reflected spinning cube shown in Figure 1. By keeping time with a simple clock function, we can divide the number of frames by the elapsed time and get a crude but effective running frame rate. This demonstration requires thirteen texture loads — six to draw the reflected cube (one for each side of the cube), one for the marble floor, and six more for the cube floating above the floor. This example program reads the texture from disk and uploads it to OpenGL with glTexImage2D() each time a GL_QUAD is drawn. You can use the arrow keys to move the cube's rotation axes around and see that each of the six sides indeed has a different texture. We can realize some performance benefit if we sort the textures to avoid redundant texture loads. For example, we can load the sand texture and draw the face of the reflected cube and the face of the source cube together. In this scenario, we would only have seven texture loads per frame. This technique, called texture sorting, can result in considerable performance gains on some hardware in many situations. However, it is still limited by the time it takes to load each texture. While this technique has the potential to speed up our rendering substantially, it still won't deliver the bang that we'll get from other optimization techniques. We'll get back to this topic of texture management and demonstrate this later. The code from REFLECT.CPP is pretty straightforward. After creating our rendering context, the code calls a SetupRC() function, which performs all the needed initialization. We've set the texture parameters to do bilinear texture filtering and to repeat the texture coordinates (for the marble floor). An excerpt from SetupRC() is shown here: void SetupRC(void) { // Set Texture mapping parameters glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR); glEnable(GL_TEXTURE_2D); ... The scene is rendered in a function called RenderScene(), which draws the original cube with a call to DrawCube(). It then draws the mirrored (and scaled) cube below the original by creating a reflection matrix and translating the origin to a position below the floor. The function then simply calls DrawCube() a second time and blends the marble floor over the reflected cube. The DrawCube() function actually loads the texture for each face of the cube and then draws that face with a single GL_QUAD. The following code excerpt shows an example of drawing a single face of the cube. // Front face of Cube glBegin(GL_QUADS); The LoadBMP() function (included in the source) simply loads a 24-bit Windows .BMP file and calls glTexImage2D() for us. You’'ll find that initially, the first frame is very slow to render. Subsequent frames render much more quickly (comparatively) because the texture images are actually read from the disk cache for each frame. Relying on the disk cache to speed up your application is a poor excuse for software engineering. |
|
|