Welcome to Tutorial 9. By now you should have a very good understanding of OpenGL. You've learned everything
from setting up an OpenGL Window, to texture mapping a spinning object using lighting and blending. This will
be the first semi-advanced tutorial. You'll learn the following: Moving bitmaps around the screen in 3D,
removing the black pixels around the bitmap (using blending), adding color to a black & white texture and
finally you'll learn how to create fancy colors and simple animation by mixing different colored textures together.
We'll be modifying the code from lesson 1 for this tutorial. We'll start off by adding a few new variables
to the beginning of the program. I'll rewrite the entire section of code so it's easier to see where the
changes are being made.
#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 lines are new. twinkle and tp are BOOLean variables meaning they can be TRUE or FALSE.
twinkle will keep track of whether or not the twinkle effect has been enabled. tp is used to check if the
'T' key has been pressed or released. (pressed tp=TRUE, relased tp=FALSE).
BOOL twinkle; // Twinkling Stars
BOOL tp; // 'T' Key Pressed?
num will keep track of how many stars we draw to the screen. It's defined as a CONSTant. This means it
can never change within the code. The reason we define it as a constant is because you can not redefine
an array. So if we've set up an array of only 50 stars and we decided to change num to 51, the array would
not grow to 51, so an error would occur. You can change this value to whatever you want it to be in this
line only. Don't try to change the value of num later on in the code unless you want disaster to occur.
const num=50; // Number Of Stars To Draw
Now we create a structure. The word structure sounds intimidating, but it's not really. A structure is a
group simple data (variables, etc) representing a larger similar group. In english :) We know that we're
keeping track of stars. You'll see that the 7th line below is 'stars;'. We know each star will have 3 values
for color, and all these values will be integer values. The 3rd line int r,g,b sets up 3 integer values.
One for red (r), one for green (g), and one for blue (b). We know each star will be a different distance
from the center of the screen, and can be place at one of 360 different angles from the center. If you look
at the 4th line below, we make a floating point value called dist. This will keep track of the distance.
The 5th line creates a floating point value called angle. This will keep track of the stars angle.
So now we have this group of data that describes the color, distance and angle of a star on the screen.
Unfortunately we have more than one star to keep track of. Instead of creating 50 red values, 50 green
values, 50 blue values, 50 distance values and 50 angle values, we just create an array called 'star'. Each
number in the 'star' array will hold all of the information in our structure called 'stars'. We make the
'star' array in the 8th line below. If we break down the 8th line: stars star[num]. This is what we come
up with. The type of array is going to be 'stars'. stars is a structure. So the array is going to hold
all of the information in the structure. The name of the array is star. The number of arrays is [num].
So because num=50, we now have 50 arrays called star. Each array stores ALL of the information in the
structure. Alot easier than keeping track of each star with seperate variables. Which would be a very
stupid thing to do, and would not allow us to add remove stars by changing the value of num.
typedef struct // Create A Structure For Star
{
int r, g, b; // Stars Color
GLfloat dist; // Stars Distance From Center
GLfloat angle; // Stars Current Angle
}
stars; // Structures Name Is stars
stars star[num]; // Make 'star' Array Of 'num' Using Info From The Structure 'stars'
Next we set up variables to keep track of how far away from the stars the viewer is (zoom), and what angle
we're seeing the stars from (tilt). We make a variable called spin that will spin the twinkling stars on
the z axis, which makes them look like they are spinning at their current location.
loop is a variable we'll use in the program to draw all 50 stars, and texture[1] will be used to store
the one b&w texture that we load in. If you wanted more textures, you'd increase the value from one to
however many textures you add.
GLfloat zoom=-15.0f; // Viewing Distance Away From Stars
GLfloat tilt=90.0f; // Tilt The View
GLfloat spin; // Spin Twinkling Stars
GLuint loop; // General loop Variable
GLuint texture[1]; // Storage For One Texture
Now we load in the texture. I shouldn't have to explain this section of code in great detail. It's the
same code we used to load the textures in lesson 6, 7 and 8. The bitmap we load is called star.bmp. We
generate only one texture using glGenTextures(1, &texture[0]). The texture will use linear filtering.
// Load Bitmap And Convert To A Texture
GLvoid LoadGLTextures()
{
// Load Texture
AUX_RGBImageRec *texture1; // Record To Store RBG Data From The Bitmap
texture1 = auxDIBImageLoad("Data/star.bmp");
if (!texture1) // Check If The Texture Loaded
{
exit(1); // If Not, Exit With An Error Message
}
glGenTextures(1, &texture[0]); // Generate 1 Texture
// Create Linear Filtered Texture
glBindTexture(GL_TEXTURE_2D, texture[0]);
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 we set up OpenGL to render the way we want. We're not going to be using Depth Testing in this project,
so make sure if you're using the code from lesson one that you remove glDepthFunc(GL_LESS); and
glEnable(GL_DEPTH_TEST); otherwise you'll see some very bad results. We're using texture mapping in this
code however so you'll want to make sure you add LoadGLTextures(); and glEnable(GL_TEXTURE_2D);
GLvoid InitGL(GLsizei Width, GLsizei Height) // This Will Be Called Right After The GL Window Is Created
{
LoadGLTextures(); // Load The Texture ( ADD )
glEnable(GL_TEXTURE_2D); // Enable Texture Mapping ( ADD )
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
glShadeModel(GL_SMOOTH); // Enables Smooth Color Shading
glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
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); // Select The Modelview Matrix
glBlendFunc(GL_SRC_ALPHA,GL_ONE); // Set The Blending Function For Translucency
glEnable(GL_BLEND); // Enable Blending
The following code is new. It sets up the starting angle, distance, and color of each star. Notice how
easy it is to change the information in the structure. The loop will go through all 50 stars. To change
the angle of star[1] all we have to do is say star[1].angle={some number} . It's that simple!
for (loop=0; loop<num; loop++) // Create A Loop That Goes Through All The Stars
{
star[loop].angle=0.0f; // Start All The Stars At Angle Zero
I calculate the distance by taking the current star (which is the value of loop) and dividing it by the
maximum amount of stars there can be. Then I multiply the result by 5.0f. Basically what this does is
moves each star a little bit farther than the previous star. When loop is 50 (the last star), loop divided
by num will be 1.0f. The reason I multiply by 5.0f is because 1.0f*5.0f is 5.0f. 5.0f is the very edge
of the screen. I don't want stars going off the screen so 5.0f is perfect. If you set the zoom further
into the screen you could use a higher number than 5.0f, but your stars would be alot smaller (because of
perspective).
You'll notice that the colors for each star are made up of random values from 0 to 255. You might be
wondering how we can use such large values when normally the colors are from 0.0f to 1.0f. When we set
the color we'll use glColor4ub instead of glColor4f. ub means Unsigned Byte. A byte can be any value from
0 to 255. In this program it's easier to use bytes than to come up with a random floating point value.
star[loop].dist=(float(loop)/num)*5.0f; // Calculate Distance From The Center
star[loop].r=rand()%256; // Give star[loop] A Random Red Intensity
star[loop].g=rand()%256; // Give star[loop] A Random Green Intensity
star[loop].b=rand()%256; // Give star[loop] A Random Blue Intensity
}
}
The Resize code is the same, so we'll jump to the drawing code. If you're using the code from lesson one,
delete the DrawGLScene code, and just copy what I have below. There's only 2 lines of code in lesson one
anyways, so there's not a lot to delete.
GLvoid DrawGLScene(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
glBindTexture(GL_TEXTURE_2D, texture[0]); // Select Our Texture
for (loop=0; loop<num; loop++) // Loop Through All The Stars
{
glLoadIdentity(); // Reset The View Before We Draw Each Star
glTranslatef(0.0f,0.0f,zoom); // Zoom Into The Screen (Using The Value In 'zoom')
glRotatef(tilt,1.0f,0.0f,0.0f); // Tilt The View (Using The Value In 'tilt')
Now we move the star. The star starts off in the middle of the screen. The first thing we do is spin the
scene on the y axis. If we spin 90 degrees, the x axis will no longer run left to right, it will run into
and out of the screen. As an example to help clarify. Imagine you were in the center of a room. Now
imagine that the left wall had -x written on it, the front wall had -z written on it, the right wall had +x
written on it, and the wall behind you had +z written on it. If the room spun 90 degrees to the right, but
you did not move, the wall in front of you would no longer say -z it would say -x. All of the walls would
have moved. -z would be in front +z behind, -x would be in front, and +x would be behind you. Make sense?
By rotating the scene, we change the direction of the x and z planes.
The next line of code moves to a positive value on the x plane. Normally a positive value on x would move us
to the right side of the screen (where +x usually is), but because we've rotated on the y plane, the +x
could be anywhere. If we rotated by 180 degrees, it would be on the left side of the screen instead of the
right. So when we move forward on the positive x plane, we could be moving left, right, forward or backward.
glRotatef(star[loop].angle,0.0f,1.0f,0.0f); // Rotate To The Current Stars Angle
glTranslatef(star[loop].dist,0.0f,0.0f); // Move Forward On The X Plane
Now for some tricky code. The star is actually a flat texture. Now if you drew a flat quad in the middle
of the screen and texture mapped it, it would look fine. It would be facing you like it should. But if
you rotated on the y axis by 90 degrees, the texture would be facing the right and left sides of the screen.
All you'd see is a thin line. We don't want that to happen with our stars. We want them to face the
screen all the time, no matter how much we rotate and tilt the screen.
We do this by cancelling any rotations that we've made, just before we draw the star. You cancel the
rotations in reverse order. So above we tilted the screen, then we rotated to the stars current angle.
In reverse order, we'd un-rotate (new word) the stars current angle. To do this we use the negative value
of the angle, and rotate by that. So if we rotated the star by 10 degrees, rotating it back -10 degrees
will make the star face the screen once again on that axis. So the first line below cancels the rotation
on the y axis. Now we need to until the screen on the x axis. So to do that we just tilt the screen
by -tilt. After we've cancelled the x and y rotations, the star will face the screen completely.
glRotatef(-star[loop].angle,0.0f,1.0f,0.0f); // Cancel The Current Stars Angle
glRotatef(-tilt,1.0f,0.0f,0.0f); // Cancel The Screen Tilt
If twinkle is TRUE, we'll draw a non-spinning star on the screen. To get a different color, we take the
maximum number of stars (num) and subtract the current stars number (loop), then subtract 1 because our
loop only goes from 0 to num-1. If the result was 10 we'd use the color from star number 10. That way the
color of the two stars is usually different. Not a good way to do it, but effective. The last value is
the alpha value. The lower the value, the darker the star is.
If twinkle is enabled, each star will be drawn twice. This will slow down the program a little depending
on what type of computer you have. If twinkle is enabled, the colors from the two stars will mix together
creating some really nice colors. Also because this star does not spin, it will appear as if the stars
are animated when twinkle is enabled. (look for yourself if you don't understand what I mean).
Notice how easy it is to add color to the texture. Even though the texture is black and white, it will
become whatever color we select before we draw the texture. Also take note that we're using bytes for
the color values rather than floating point numbers. Even the alpha value is a byte.
if (twinkle) // Twinkling Stars Enabled
{
// Assign A Color Using Bytes
glColor4ub(star[(num-loop)-1].r,star[(num-loop)-1].g,star[(num-loop)-1].b,255);
glBegin(GL_QUADS); // Begin Drawing The Textured Quad
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,-1.0f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);
glEnd(); // Done Drawing The Textured Quad
}
Now we draw the main star. The only difference from the code above is that this star is always drawn, and
this star spins on the z axis.
glRotatef(spin,0.0f,0.0f,1.0f); // Rotate The Star On The Z Axis
// Assign A Color Using Bytes
glColor4ub(star[loop].r,star[loop].g,star[loop].b,255);
glBegin(GL_QUADS); // Begin Drawing The Textured Quad
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,-1.0f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);
glEnd(); // Done Drawing The Textured Quad
Here's where we do all the movement. We spin the normal stars by increasing the value of spin. Then we
change the angle of each star. The angle of each star increased by loop/num. What this does is spins the
stars that are farther from the center faster. The stars closer to the center spin slower. Finally we
decrease the distance each star is from the center of the screen. This makes the stars look as if they
are being sucked into the middle of the screen.
spin+=0.01f; // Used To Spin The Stars
star[loop].angle+=float(loop)/num; // Changes The Angle Of A Star
star[loop].dist-=0.01f; // Changes The Distance Of A Star
The lines below check to see if the stars have hit the center of the screen or not. When a star hits the
middle of the screen it's given a new color, and is moved 5 units from the center, so it can start it's
journey back to the center as a new star.
if (star[loop].dist<0.0f) // Is The Star In The Middle Yet
{
star[loop].dist+=5.0f; // Move The Star 5 Units From The Center
star[loop].r=rand()%256; // Give It A New Red Value
star[loop].g=rand()%256; // Give It A New Green Value
star[loop].b=rand()%256; // Give It A New Blue Value
}
}
}
Find the line if (keys[VK_ESCAPE]) SendMessage(hWnd,WM_CLOSE,0,0). Right under that line, add the following
lines of code.
The lines below check to see if the T key has been pressed. If it has been pressed and it's not being held
down the following will happen. If twinkle is FALSE, it will become TRUE. If it was TRUE, it will become
FALSE. Once T is pressed tp will become TRUE. This prevents the code from running over and over again if
you hold down the T key.
if (keys['T'] && !tp) // Is T Being Pressed And Is tp FALSE
{
tp=TRUE; // If So, Make tp TRUE
twinkle=!twinkle; // Make twinkle Equal The Opposite Of What It Is
}
The code below checks to see if you've let go of the T key. If you have, it makes tp=FALSE. Pressing the
T key will do nothing unless tp is FALSE, so this section of code is very important.
if (!keys['T']) // Has The T Key Been Released
{
tp=FALSE; // If So, make tp FALSE
}
The rest of the code checks to see if the up arrow, down arrow, page up or page down keys are being pressed.
if (keys[VK_UP]) // Is Up Arrow Being Pressed
{
tilt-=0.5f; // Tilt The Screen Up
}
if (keys[VK_DOWN]) // Is Down Arrow Being Pressed
{
tilt+=0.5f; // Tilt The Screen Down
}
if (keys[VK_PRIOR]) // Is Page Up Being Pressed
{
zoom-=0.2f; // Zoom Out
}
if (keys[VK_NEXT]) // Is Page Down Being Pressed
{
zoom+=0.2f; // Zoom In
}
}
}
In this tutorial I have tried to explain in as much detail how to load in a gray scale bitmap image, remove
the black space around the image (using blending), add color to the image, and move the image around the
screen in 3D. I've also shown you how to create beautiful colors and animation by overlapping a second copy
of the bitmap on top of the original bitmap. Once you have a good understanding of everything I've taught
you up till now, you should have no problems making 3D demos ofyour own. All the basics have been covered!
Jeff Molofee (NeHe)
* DOWNLOAD Visual C++ Code For This Lesson.
* DOWNLOAD Visual Fortran Code For This Lesson.
( Conversion by Jean-Philippe Perois )
* DOWNLOAD Delphi Code For This Lesson.
( Conversion by Marc Aarts )
* DOWNLOAD Linux Code For This Lesson.
( Conversion by Richard Campbell )
* DOWNLOAD Irix Code For This Lesson.
( Conversion by Lakmal Gunasekara )
* DOWNLOAD Solaris Code For This Lesson.
( Conversion by Lakmal Gunasekara )
* DOWNLOAD Mac OS Code For This Lesson.
( Conversion by Anthony Parker )
* DOWNLOAD Power Basic Code For This Lesson.
( Conversion by Angus Law )
* DOWNLOAD Java Code For This Lesson.
( Conversion by Darren Hodges )