A Tutorial for Learning Texture Binding, Filtering, Lighting, and Fogging Effects in OpenGL

Environment: VC6

Well, here is good news for all game lovers and beginning game programmers. This article will show you how to create a simple Balloon Show game using OpenGL libraries. The OpenGL libraries can be used with other language platforms, but it is best to use them with the Visual C++ environment because most of the game programmers use VC++ to make their games. This article shows the step by step handling from the beginning till the end.

The first thing you will have to do is build a project in Visual C++. After you have created a new Win32 Application (not a console application) in Visual C++, you will need to link the OpenGL libraries. In Visual C++, go to Project, Settings, and then click on the LINK tab. Under “Object/Library Modules” at the beginning of the line (before kernel32.lib), add OpenGL32.lib GLu32.lib and GLaux.lib. Once you’ve done this, click OK. You’re now ready to write an OpenGL Windows program. Your program should include all these header files:

#include "windows.h"     // Header File For Windows
#include "gl\gl.h"       // Header File For The OpenGL32 Library
#include "gl\glu.h"      // Header File For The GLu32 Library
#include "gl\glaux.h"    // Header File For The GLaux Library

The few variables that we do set up are very important, and will be used in just about every OpenGL program you write using this code. The data types for OpenGL are also defined. For example:

GLvoid
GLuint
GLfloat

The first line sets up a Rendering Context. Every OpenGL program is linked to a Rendering Context. A Rendering Context is what links OpenGL calls to the Device Context. The OpenGL Rendering Context is defined as hRC. In order for your program to draw to a Window, you need to create a Device Context; this is done in the second line. The Windows Device Context is defined as hDC. The DC connects the Window to the GDI (Graphics Device Interface). The RC connects OpenGL to the DC.

HDC       hDC=NULL;     // Private GDI Device Context
HGLRC     hRC=NULL;     // Permanent Rendering Context
HWND      hWnd=NULL;    // Holds Our Window Handle
HINSTANCE hInstance;    // Holds The Instance Of The Application

The first line below sets up an array, keys[256], that we will use to monitor key presses on the keyboard. There are many ways to watch for key presses on the keyboard, but this is the way I do it. It’s reliable, and it can handle more than one key being pressed at a time.

The active variable will be used to tell our program whether or not our Window has been minimized to the taskbar. The variable fullscreen is fairly obvious. If our program is running in fullscreen mode, fullscreen will be TRUE; if our program is running in Windowed mode, fullscreen will be FALSE.

bool keys[256];        // Array Used For The Keyboard Routine
bool active=TRUE;      // Window Active Flag Set To TRUE By Default/
bool fullscreen=TRUE;  // Fullscreen Flag Set To Fullscreen Mode
                       // By Default

In the following line, we define WndProc() so that CreateGLWindow() can make reference to WndProc().

LRESULT  CALLBACK  WndProc (HWND, UINT, WPARAM, LPARAM);

The OpenGL scene will be resized based on the width and height of the window it’s being displayed in.

GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
  // Resize And Initialize The GL Window
{
  if (height==0)    // Prevent A Divide By Zero By
  {                 // Making Height Equal One
    height=1;
  }
  glViewport(0, 0, width, height);  // Reset The Current Viewport

The following lines set the screen up for a perspective view. This means that things in the distance get smaller. This creates a realistic looking scene. The perspective is calculated with a 45 degree viewing angle based on the window’s width and height. The 0.1f, 100.0f are the starting point and ending point for how deep we can draw into the screen.

glMatrixMode(GL_PROJECTION);   // Select The Projection Matrix
glLoadIdentity();              // Reset The Projection Matrix
// Calculate The Aspect Ratio Of The Window
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
glMatrixMode(GL_MODELVIEW);    // Select The Modelview Matrix
glLoadIdentity();              // Reset The Modelview Matrix
}

In the next section of code, we do all of the setup for OpenGL. We set what color to clear the screen to, and we turn on the depth buffer, enable smooth shading, and so forth. This routine will not be called until the OpenGL Window has been created.

int InitGL(GLvoid)                      // All Setup For OpenGL
                                        // Goes Here
{
glShadeModel(GL_SMOOTH);                // Enables Smooth Shading
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);   // Black Background
glClearDepth(1.0f);                     // Depth Buffer Setup
glEnable(GL_DEPTH_TEST);                // Enables Depth Testing
glDepthFunc(GL_LEQUAL);                 // The Type Of Depth Test
                                        // To Do
glHint(GL_PERSPECTIVE_CORRECTION_HINT,  // Really Nice Perspective
       GL_NICEST);                      // Calculations
return TRUE;                            // Initialization Went OK
}

Anything you plan to display on the screen will go in this section of code.

int DrawGLScene(GLvoid)    // Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Clear The Screen And The Depth Buffer

The next three lines of code position and rotate the texture mapped cube. glTranslatef(0.0f,0.0f,z) moves the cube to the value of z on the z plane (away from and towards the viewer). glRotatef (xrot,1.0f,0.0f,0.0f) uses the variable xrot to rotate the cube on the x axis. glRotatef (yrot,1.0f,0.0f, 0.0f) uses the variable yrot to rotate the cube on the y axis. These things will be covered in detail later.

glTranslatef(0.0f,0.0f,z);         // Translate Into/Out Of The
                                   // Screen By z
glRotatef(xrot,1.0f,0.0f,0.0f);    // Rotate On The X Axis By xrot
glRotatef(yrot,0.0f,1.0f,0.0f);    // Rotate On The Y Axis By yrot

By using the variable filter, we can select any of the three textures we’ve made.

glBindTexture(GL_TEXTURE_2D, texture[filter]);
// Select A Texture Based On filter

The following lines create a new Quadratic object that maps a given bitmap texture to a sphere. Here we map the texture for making the balloon a sphere shape.

q = gluNewQuadric();                    // Create A New Quadratic
gluQuadricNormals(q, GL_SMOOTH);        // Generate Smooth Normals
                                        // For The Quad
gluQuadricTexture(q, GL_TRUE);          // Enable Texture Coords
                                        // For The Quad
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE,    // Set Up Sphere Mapping
          GL_SPHERE_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE,    // Set Up Sphere Mapping
          GL_SPHERE_MAP);

The following code is to make a cube with given bitmap textures on all sides of the cube—front, back, top, bottom, right, and left. The code for the front side is shown here and the rest is in the source code provided with this article.

glBegin(GL_QUADS);                  // Front Face
glNormal3f( 0.0f, 0.0f, 1.0f);      // Normal Pointing Towards
                                    // Viewer
glTexCoord2f(0.0f, 0.0f);           // Point 1 (Front)
  glVertex3f(-1.0f, -1.0f,  1.0f);
glTexCoord2f(1.0f, 0.0f);           // Point 2 (Front)
  glVertex3f( 1.0f, -1.0f,  1.0f);
glTexCoord2f(1.0f, 1.0f);           // Point 3 (Front)
  glVertex3f( 1.0f,  1.0f,  1.0f);
glTexCoord2f(0.0f, 1.0f);
  glVertex3f(-1.0f,  1.0f,  1.0f);  // Point 4 (Front)
glEnd();

glLoadIdentity();    // Reset The Current Modelview Matrix
return TRUE;         // Everything Went OK
}

The following function code destroys the OpenGL window. The first thing we do in KillGLWindow() is check to see whether we are in fullscreen mode. If we are, we’ll switch back to the desktop.

GLvoid KillGLWindow(GLvoid)

The following function creates a OpenGL window and here you can set the size, style, and other attributes of the window.

BOOL CreateGLWindow(char* title, int width, int height,
                    int bits, bool fullscreenflag)

Texture Binding

GLuint texture[3] creates storage space for the three different textures. The textures will be stored at texture[0], texture[1], and texture[2].

GLuint texture[3];    // Storage for 3 textures

AUX_RGBImageRec *LoadBMP(char *Filename)

Now we load in a bitmap. The above function codes the loading, and building texture maps from bitmap images. The section of code that loads the bitmap (calling the code above) and converts it into three textures. A variable Status is used to keep track of whether or not the texture was loaded and created. The variable for storing the image is AUX_RGBImageRec. This is created in the function int LoadGLTextures().

Creating Motion Effects

Now we’re going to set up five variables that will control the angle on the x axis (xrot), the angle on the y axis (yrot), the speed the crate is spinning at on the x axis (xspeed), and the speed the crate is spinning at on the y axis (yspeed). We’ll also create a variable called z that will control how deep into the screen (on the z axis) the balloon is or its zoom is.

GLfloat xrot;           // X Rotation
GLfloat yrot;           // Y Rotation
GLfloat xspeed;         // X Rotation Speed
GLfloat yspeed;         // Y Rotation Speed
GLfloat z=-5.0f;        // Depth (zoom) into the Screen
GLfloat height=2.0f;    // Height Of Balloon From Floor

Creating Lighting Effects

We create a variable called light to keep track of whether the lighting is on or off.

BOOL light;    // Lighting ON / OFF

The first type of light is called ambient light. Ambient light is light that doesn’t come from any particular direction. All the objects in your scene will be lit up by the ambient light. The second type of light is called diffuse light. Diffuse light is created by your light source and is reflected off the surface of an object in your scene. Any surface of an object that the light hits directly will be very bright, and areas the light barely gets to will be darker. This creates a nice shading effect on the sides of our crate and the balloon.

GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };
  // Ambient Light Values ( NEW )
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };
  / Diffuse Light Values ( NEW )/

This tells OpenGL the designated coordinates are the position of the light source.

GLfloat LightPosition[]= { 0.0f, 0.0f,    // Light Position ( NEW )
                           2.0f, 1.0f };

The variables lp and fp are used to store whether or not the ‘L’ or ‘F’ key has been pressed.

BOOL lp;    // L Pressed?
BOOL fp;    // F Pressed?

Creating Filter Effects

GLuint filter;    // Which Filter To Use

The filter variable above keeps track of which texture to display. The first texture (texture 0) is made using gl_nearest (no smoothing). The second texture (texture 1) uses gl_linear filtering, which smooths the image out quite a bit. The third texture (texture 2) uses mipmapped textures, creating a very nice looking texture. The variable filter will equal 0, 1, or 2, depending on the texture we want to use. We start off with the first texture.

You’ll notice we’re using GL_NEAREST for both the MIN and MAG. You can mix GL_NEAREST with GL_LINEAR, and the texture will look a bit better, but we’re interested in speed, so we’ll use low quality for both. The MIN_FILTER is the filter used when an image is drawn smaller than the original texture size. The MAG_FILTER is used when the image is bigger than the original texture size.

// Create Nearest Filtered Texture
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,    //( NEW )
                GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,    //( NEW )
                GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX,
             TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
             TextureImage[0]->data);

In using linear filtered texture maps, they require a hefty amount of processing power, but they look really nice. The first type of texture we’re going to create in this tutorial uses GL_NEAREST. Basically, this type of texture has no filtering at all. It takes very little processing power, and it looks really bad. If you’ve ever played a game where the textures look all blocky, it’s probably using this type of texture.

// Create Linear Filtered Texture
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX,
             TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
             TextureImage[0]->data);

When you tell OpenGL to build a mipmapped texture, OpenGL tries to build different-sized, high quality textures. When you draw a mipmapped texture to the screen, OpenGL will select the BEST looking texture from the ones it built (texture with the most detail) and draw it to the screen instead of resizing the original image (which causes detail loss).

// Create MipMapped Texture
glBindTexture(GL_TEXTURE_2D, texture[2]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,
                GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,    //( NEW )
                GL_LINEAR_MIPMAP_NEAREST);

The following line builds the mipmapped texture. We’re creating a 2D texture using three colors (red, maroon, amd blue). TextureImage[0]->sizeX is the bitmap’s width, TextureImage[0]->sizeY is the bitmap’s height, GL_RGB means we’re using Red, Maroon, amd Blue colors in that order. GL_UNSIGNED_BYTE means the data that makes the texture is made up of bytes, and TextureImage[0]->data points to the bitmap data that we’re building the texture from.

gluBuild2DMipmaps(GL_TEXTURE_2D, 3,      //( NEW )
                  TextureImage[0]->sizeX,
                  TextureImage[0]->sizeY, GL_RGB,
                  GL_UNSIGNED_BYTE, TextureImage[0]->data);
  }

Creating Fogging Effects

We’ll start by setting up all our variables needed to hold the information for fog. The variable fogMode will be used to hold three types of fog: GL_EXP, GL_EXP2, and GL_LINEAR. I will explain the differences among these three later on. The variables will start at the beginning of the code, after the line GLuint texture[3]. The variable fogfilter will be used to keep track of which fog type we will be using. The variable fogColor will hold the color we want the fog to be. I have also added the boolean variable gp at the top of the code so we can tell whether the ‘g’ key is being pressed.

bool   gp;                           // G Pressed? ( New )
GLuint fogMode[]= { GL_EXP, GL_EXP2,
                    GL_LINEAR };     // Storage For Three Types
                                     // Of Fog
GLuint fogfilter= 0;                 // Which Fog To Use
GLfloat fogColor[4]= {0.5f, 0.5f,    // Fog Color
                      0.5f, 1.0f};

The DrawGLScene() function has the following code to enable fog effects:

glClearColor(0.5f,0.5f,0.5f,1.0f);          // We'll Clear To The
                                            // Color Of The Fog
                                            // ( Modified )

glFogi(GL_FOG_MODE, fogMode[fogfilter]);    // Fog Mode
glFogfv(GL_FOG_COLOR, fogColor);            // Set Fog Color
glFogf(GL_FOG_DENSITY, 0.35f);              // How Dense Will The
                                            // Fog Be
glHint(GL_FOG_HINT, GL_DONT_CARE);          // Fog Hint Value
glFogf(GL_FOG_START, 1.0f);                 // Fog Start Depth
glFogf(GL_FOG_END, 5.0f);                   // Fog End Depth

glEnable(GL_FOG);                           // Enables GL_FOG

The above line, glEnable(GL_FOG);, is pretty much self explanatory. It basically initializes the fog. The line glFogi(GL_FOG_MODE, fogMode [fogfilter] ); establishes the fog filter mode. Now, earlier we declared the array fogMode. It held GL_EXP, GL_EXP2, and GL_LINEAR. Here is when these variables come into play. Let me explain each one:

  • GL_EXP: Basic rendered fog that fogs out all of the screen. It doesn’t give much of a fog effect, but gets the job done on older PCs.
  • GL_EXP2: Is the next step up from GL_EXP. This will fog out all of the screen; however, it will give more depth to the scene.
  • GL_LINEAR: This is the best fog rendering mode. Objects fade in and out of the fog much better.
  • glFogfv(GL_FOG_COLOR, fogcolor): Sets the color of the fog. The line glFogf(GL_FOG_DENSITY, 0.35f); establishes how dense the fog will be. Increase the number and the fog becomes more dense; decrease it and it becomes less dense.
  • glHint (GL_FOG_HINT, GL_DONT_CARE): Establishes the hint. I used GL_DONT_CARE, because I didn’t care about the hint value. Hint value can be one of the following types:
    • gl_dont_care: Lets OpenGL choose the kind of fog (per vertex of per pixel) and an unknown formula.
    • gl_nicest: Makes the fog per pixel (looks good)
    • glfastest: Makes the fog per vertex (faster, but not as nice)

About the Author

Fatima Ahmed is in B.S. (Computer Science) Karachi University, a well-known university in Pakistan. Her interests are in writing technical articles for magazines and e-zines, surfing the net, making projects, and doing programming in different languages. Her articles can be found on this and other Web sites. You can contact her at: fatima_saffano2001@yahoo.com or fatima_bsku@yahoo.com.

References

NeHe’s game programming Tutorials.

Downloads

Download source – 87 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read