In this tutorial I'll teach you how to use three different texture filters. I'll teach you how to move an object using keys on the keyboard, and I'll also teach you how to apply simple lighting to your OpenGL scene. Lots covered in this tutorial, so if the previous tutorials are giving you problems, go back and review. It's important to have a good understanding of the basics before you jump into the following code.

We're going to be modifying the code from lesson one again. As usual, if there are any major changes, I will write out the entire section of code that has been modified. We'll start off by adding a few new variables to the program.

#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

static	HGLRC hRC;		// Permanent Rendering Context
static	HDC hDC;		// Private GDI Device Context

BOOL	keys[256];		// Array Used For The Keyboard Routine

The following three lines are new. We're going to make three boolean variables. BOOL means the variable can only be TRUE or FALSE. We create a variable called light to keep track of whether or not the lighting is on or off. the variables lp and fp are used to store whether or not the 'L' or 'F' key has been pressed. I'll explain why we need these variables later on in the code. For now, just know that they are important.

BOOL	light;			// Lighting ON/OFF
BOOL	lp;			// L Pressed?
BOOL	fp;			// F Pressed?

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 crate is.

GLfloat	xrot;			// X Rotation
GLfloat	yrot;			// Y Rotation
GLfloat xspeed;			// X Rotation Speed
GLfloat yspeed;			// Y Rotation Speed

GLfloat	z=-5.0f;		// Depth Into The Screen

Now we create the light. We'll use two different types of light. 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.

Light is created the same way color is created. If the first number is 1.0f, and the next two are 0.0f, we will end up with a bright red light. If the third number is 1.0f, and the first two are 0.0f, we will have a bright blue light. The last number is an alpha value. We'll leave it at 1.0f for now.

So in the line below, we are storing the values for a white ambient light at half intensity (0.5f). Because all the numbers are 0.5f, we will end up with a light that's halfway between off (black) and full brightness (white). Red, blue and green mixed at the same value will create a shade from black(0.0f) to white(1.0f). Without an ambient light, spots where there is no diffuse light will appear very dark.

GLfloat LightAmbient[]=		{ 0.5f, 0.5f, 0.5f, 1.0f };

In the next line we're storing the values for a super bright, full intensity diffuse light. All the values are 1.0f. This means the light is as bright white as we can get it. A diffuse light this bright lights up the front of the crate quite nicely whenever the crate is facing the light.

GLfloat LightDiffuse[]=		{ 1.0f, 1.0f, 1.0f, 1.0f };

Finally we store the position of the light. The first three numbers are the same as glTranslate's three numbers. The first number is for moving left and right on the x plane, the second number is for moving up and down on the y plane, and the third number is for moving into and out of the screen on the z plane. Because we want our light hitting directly on the front of the crate, we don't move left or right so the first value is 0.0f (no movement on x), we don't want to move up and down, so the second value is 0.0f as well. For the third value we want to make sure the light is always in front of the crate. So we'll position the light off the screen, towards the viewer. Lets say the glass on your monitor is at 0.0f on the z plane. We'll position the light at 2.0f on the z plane. If you could actually see the light, it would be floating in front of the glass on your monitor. By doing this, the only way the light would be behind the crate is if the crate was also in front of the glass on your monitor. Of course if the crate was no longer behind the glass on your monitor, you would no longer see the crate, so it doesn't matter where the light is. Does that make sense?

There's no real easy way to explain the third parameter. You should know that -2.0f is going to be closer to you than -5.0f. and -100.0f would be WAY into the screen. Once you get to 0.0f, the image is so big, it fills the entire monitor. Once you start going into positive values, the image no longer appears on the screen cause it has "gone past the screen". That's what I mean when I say out of the screen. The object is still there, you just can't see it anymore.

Leave the last number at 1.0f. This tells OpenGL the designated coordinates are the position of the light source. More about this in a later tutorial.

GLfloat LightPosition[]=	{ 0.0f, 0.0f, 2.0f, 1.0f };

The 'filter' variable below is to keep 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 (0).

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

GLuint	filter;			// Which Filter To Use
GLuint	texture[3];		// Storage for 3 textures

Now we load in a bitmap, and create three different textures from it. This tutorial uses the glaux library to load in the bitmap, so make sure you have the glaux library included before you try compiling the code. I know Delphi, and Visual C++ both have glaux libraries. I'm not sure about other languages. I'm only going to explain what the new lines of code do, if you see a line I haven't commented on, and you're wondering what it does, check tutorial six. It explains loading, and building texture maps from bitmap images in great detail.

// Load Bitmaps And Convert To Textures
GLvoid LoadGLTextures()
{
	// Load Texture
	AUX_RGBImageRec *texture1;

	texture1 = auxDIBImageLoad("Data/crate.bmp");
	if (!texture1)
	{
		exit(1);
	}

In tutorial six, we used linear filtered texture maps. They require a hefty amount of processing power, but they look real nice. The first type of texture were 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 real bad. If you've ever played a game where the textures look all blocky, it's probably using this type of texture. The only benefit of this type of texture is that projects made using this type of texture will usually run pretty good on slow computers.

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 nice, but we're intested in speed, so we'll use low quality for both. I'm not sure if I explained this or not, but 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
	glGenTextures(3, &texture[0]);
	glBindTexture(GL_TEXTURE_2D, texture[0]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); ( NEW )
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); ( NEW )
	glTexImage2D(GL_TEXTURE_2D, 0, 3, texture1->sizeX, texture1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, texture1->data);

This is the same type of texture we used in tutorial six. Linear filtered. The only thing that has changed is that we are storing this texture in texture[1] instead of texture[0] because it's the second texture we've made. If we stored it in texture[0] like above, it would overwrite the GL_NEAREST 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, texture1->sizeX, texture1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, texture1->data);

Now for a new way to make textures. Mipmapping! You may have noticed that when you make an image very tiny on the screen, alot of the fine details disappear. Patterns that used to look nice start looking real bad. 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).

I had said in tutorial six there was a way around the 64,128,256,etc limit that OpenGL puts on texture width and height. gluBuild2DMipmaps is it. From what I've found, you can use any bitmap image you want (any width and height) when building mipmapped textures. OpenGL will automatically size it to the proper width and height.

Because this is texture number three, we're going to store this texture in texture[2]. So now we have texture[0] which has no filtering, texture[1] which uses linear filtering, and texture[2] which uses mipmapped textures. We're done building the textures for this tutorial.

	// 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,GL_LINEAR_MIPMAP_NEAREST); ( NEW )

The following line builds the mipmapped texture. We're creating a 2D texture using three colors (red, green, blue). texture1->sizeX returns the bitmaps width, texture1->sizeY returns the bitmaps height, GL_RGB means we're using Red, Green, Blue colors in that order. GL_UNSIGNED_BYTE means the data that makes the texture is made up of bytes, and texture1->data points to the bitmap data that we're building the texture from.

	gluBuild2DMipmaps(GL_TEXTURE_2D, 3, texture1->sizeX, texture1->sizeY, GL_RGB, GL_UNSIGNED_BYTE, texture1->data); ( NEW )
};

Now we load the textures, and initialize the OpenGL settings. The first line of InitGL loads the textures using the code above. After the textures have been created, we enable 2D texture mapping with glEnable(GL_TEXTURE_2D). The background color is set to black, we enable depth testing, the shade mode is set to smooth shading, then we set up our projection matrix with the proper perspective settings.

GLvoid InitGL(GLsizei Width, GLsizei Height)					// This Will Be Called Right After The GL Window Is Created
{
	LoadGLTextures();							// Load The Texture(s)
	glEnable(GL_TEXTURE_2D);						// Enable Texture Mapping

	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);					// This Will Clear The Background Color To Black
	glClearDepth(1.0);							// Enables Clearing Of The Depth Buffer
	glDepthFunc(GL_LESS);							// The Type Of Depth Test To Do
	glEnable(GL_DEPTH_TEST);						// Enables Depth Testing
	glShadeModel(GL_SMOOTH);						// Enables Smooth Color Shading

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();							// Reset The Projection Matrix

	gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);	// Calculate The Aspect Ratio Of The Window

	glMatrixMode(GL_MODELVIEW);

Now we set up the lighting. The line below will set the amount of ambient light that light one will give off. At the beginning of this tutorial we stored the amount of ambient light in LightAmbient. The values we stored in the array will be used (half intensity ambient light).

	glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);

Next we set up the amount of diffuse light that light number one will give off. We stored the amount of diffuse light in LightDiffuse. The values we stored in this array will be used (full intensity white light).

	glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);

Now we set the position of the light. We stored the position in LightPosition. The values we stored in this array will be used (right in the center of the front face, 0.0f on x, 0.0f on y, and 2 unit towards the viewer {coming out of the screen} on the z plane).

	glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);

Finally, we enable light number one. Because we haven't enabled GL_LIGHTING, you wont see any lighting just yet. The light is set up, and positioned, it's even enabled, but until we enable GL_LIGHTING, the light will not work.

	glEnable(GL_LIGHT1);
}

In the next section of code, we're going to draw the texture mapped cube. I will comment a few of the line only because they are new. If you're not sure what the uncommented lines do, check tutorial number six.

GLvoid DrawGLScene(GLvoid)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear The Screen And The Depth Buffer
	glLoadIdentity();						// Reset The View

The next three lines of code position and rotate the texture mapped cube. glTranslatef(0.0f,0.0f,z) moves the cube to unit z on the z plane (away from and towards the viewer). glRotatef(xrot,1.0f,0.0f,0.0f) uses xrot to rotate the cube on the x axis. glRotatef(yrot,1.0f,0.0f,0.0f) uses yrot to rotate the cube on the y axis.

	glTranslatef(0.0f,0.0f,z);

	glRotatef(xrot,1.0f,0.0f,0.0f);
	glRotatef(yrot,0.0f,1.0f,0.0f);

The next line is similar to the line we used in tutorial six, but instead of binding texture[0], we are binding texture[filter]. Any time we press the 'F' key, the value in filter will increase. If this value is higher than two, the variable filter is set back to zero. When the program starts the filter will be set to zero. This is the same as saying glBindTexture(GL_TEXTURE_2D, texture[0]). If we press 'F' once more, the variable filter will equal one, which is the same as saying glBindTexture(GL_TEXTURE_2D, texture[1]). By using the variable filter we can select any of the three textures we've made.

	glBindTexture(GL_TEXTURE_2D, texture[filter]);

	glBegin(GL_QUADS);

glNormal3f is new to my tutorials. A normal is a line pointing straight out of the middle of a polygon at a 90 degree angle. When you use lighting, you need to specify a normal. The normal tells OpenGL which direction the polygon is facing... which way is up. If you don't specify normals, all kinds of weird things happen. Faces that shouldn't light up will light up, the wrong side of a polygon will light up, etc. The normal should point outwards from the polygon.

Looking at the front face you'll notice that the normal is positive on the z axis. This means the normal is pointing at the viewer. Exactly the direction we want it pointing. On the back face, the normal is pointing away from the viewer, into the screen. Again exactly what we want. If the cube is spun 180 degrees on either the x or y axis, the front will be facing into the screen and the back will be facing towards the viewer. No matter what face is facing the viewer, the normal of that face will also be pointing towards the viewer. Because the light is close to the viewer, any time the normal is pointing towards the viewer it's also pointing towards the light. When it does, the face will light up. The more a normal points towards the light, the brighter that face is. If you move into the center of the cube you'll notice it's dark. The normals are point out, not in, so there's no light inside the box, exactly as it should be.

		// Front Face
		glNormal3f( 0.0f, 0.0f, 1.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
		// Back Face
		glNormal3f( 0.0f, 0.0f,-1.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
		// Top Face
		glNormal3f( 0.0f, 1.0f, 0.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
		// Bottom Face
		glNormal3f( 0.0f,-1.0f, 0.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
		// Right face
		glNormal3f( 1.0f, 0.0f, 0.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
		// Left Face
		glNormal3f(-1.0f, 0.0f, 0.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
	glEnd();

The next two lines increase xrot and yrot by the amount stored in xspeed, and yspeed. If the value in xspeed or yspeed is high, xrot and yrot will increase quickly. The faster xrot, and yrot increase, the faster the cube spins on that axis.

	xrot+=xspeed;
	yrot+=yspeed;
}

Now we move down to the actual loop that tells OpenGL to continously update the screen until ESC is pressed. I've made this program fairly interactive. Only sections of code that are new to this tutorial will be commented.

	while (1)
	{
		// Process All Messages
		while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) == TRUE)
		{
			if (GetMessage(&msg, NULL, 0, 0))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			else
			{
				return TRUE;
			}
		}

		DrawGLScene();
		SwapBuffers(hDC);
		if (keys[VK_ESCAPE]) SendMessage(hWnd,WM_CLOSE,0,0);

The following code checks to see if the letter 'L' has been pressed on the keyboard. The first line checks to see if 'L' is being pressed. If 'L' is being pressed, but lp isn't false, meaning 'L' has already been pressed once or it's being held down, nothing will happen.

		if (keys['L'] && !lp)
		{

If lp was false, meaning the 'L' key hasn't been pressed yet, or it's been released, lp becomes true. This forces the person to let go of the 'L' key before this code will run again. If we didn't check to see if the key was being held down, the lighting would flicker off and on over and over, because the program would think you were pressing the 'L' key over and over again each time it came to this section of code.

Once lp has been set to true, telling the computer that 'L' is being held down, we toggle lighting off and on. The variable 'light' can only be true of false. So if we say light=!light, what we are actually saying is light equals NOT light. Which in english translates to if light equals true make light not true, and if light equals false, make light not false. So if light was true, it becomes false, and if light was false it becomes true.

			lp=TRUE;
			light=!light;

Now we check to see what 'light' ended up being. The first line translated to english means: If 'light' equals false. So if you put it all together, the lines do the following: If 'light' equals false, disable lighting. This turns all lighting off. The command 'else' means if it wasn't false. So if 'light' wasn't false, it must have been true, so we turn lighting on.

			if (!light)
			{
				glDisable(GL_LIGHTING);
			}
			else
			{
				glEnable(GL_LIGHTING);
			}
		}

The following line checks to see if we stopped pressing the 'L' key. If we did, it makes the variable lp equal false, meaning the 'L' key isn't pressed. If we didn't check to see if the key was released, we'd be able to turn lighting on once, but because the computer would always think 'L' was being held down, it wouldn't let us turn it back off.

		if (!keys['L'])
		{
			lp=FALSE;
		}

Now we do something similar with the 'F' key. if the key is being pressed, and it's not being held down or it's never been pressed before, it will make the variable fp equal true meaning the key is now being held down. It will then increase the variable called filter. If filter is greater than 2 (which would be texture[3], and that texture doesn't exist), we reset the variable fiter back to zero.

		if (keys['F'] && !fp)
		{
			fp=TRUE;
			filter+=1;
			if (filter>2)
			{
				filter=0;
			}
		}
		if (!keys['F'])
		{
			fp=FALSE;
		}

The next four lines check to see if we are pressing the 'Page Up' key. If we are it decreases the variable 'z'. If this variable decreases, the cube will move into the distance because of the glTranslatef(0.0f,0.0f,z) command used in the DrawGLScene procedure.

		if (keys[VK_PRIOR])
		{
			z-=0.02f;
		}

These four lines check to see if we are pressing the 'Page Down' key. If we are it increases the variable 'z' and moves the cube towards the viewer because of the glTranslatef(0.0f,0.0f,z) command used in the DrawGLScene procedure.

		if (keys[VK_NEXT])
		{
			z+=0.02f;
		}
		if (keys[VK_UP])
		{
			xspeed-=0.01f;
		}

Now all we have to check for is the arrow keys. By pressing left or right, xspeed is increased or decreased. By pressing up or down, yspeed is increased or decreased. Remember further up in the tutorial I said that if the value in xspeed or yspeed was high, the cube would spin faster. The longer you hold down an arrow key, the faster the cube will spin in that direction.

		if (keys[VK_DOWN])
		{
			xspeed+=0.01f;
		}
		if (keys[VK_RIGHT])
		{
			yspeed+=0.01f;
		}
		if (keys[VK_LEFT])
		{
			yspeed-=0.01f;
		}

	}
}

By the end of this tutorial you should be able to create and interact with high quality, realistic looking, textured mapped objects made up of quads. You should understand the benefits of each of the three filters used in this tutorial. By pressing specific keys on the keyboard you should be able to interact with the object(s) on the screen, and finally, you should know how to apply simple lighting to a scene making the scene appear more realistic.

Jeff Molofee (NeHe)

* DOWNLOAD Visual C++ Code For This Lesson.
* DOWNLOAD Delphi Code For This Lesson. ( Conversion by Brad Choate )
* DOWNLOAD Linux Code For This Lesson. ( Conversion by Richard Campbell )
* DOWNLOAD BeOS Code For This Lesson. ( Conversion by Chris Herborth )