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 )