• No results found

Flag Effect (Waving Texture)Flag Effect (Waving Texture)

In document Nehe Opengl Tutorial (Page 49-67)

Flag Effect (Waving Texture)

Well greetings all. For those of you that want to see what we are doing here, you can check it out at the end of my demo/hack Worthless! I am bosco and I will do my best to teach you guys how to do the animated, sine-wave picture. This tutorial is based on NeHe's tutorial #6 and you should have at least that much knowledge. You should download the source package and place the bitmap I've included in a director y called data where your source code is. Or use your own texture if it's an appropriate size to be used as a texture with OpenGL.

First things first. Open Tutorial #6 in Visual C++ and add the following include statement right after the other #include statements.

The #include below allows us to work with complex math such as sine and cosine.

#include <math.h>// For The Sin() Function

We'll use the array points to store the individual x, y & z coordinates of our grid. The grid is 45 points by 45 points, which in turn makes 44 quads x 44 quads. wiggle_count will be used to keep track of how fast the texture waves. Every three frames looks pretty good, and the variable hold will store a floating point value to smooth out the waving of the flag. These lines can be added at the top of the program, somewhere under the last #include line, and before the GLuint texture[1] line.

float points[ 45 ][ 45 ][3];// The Array For The Points On The Grid Of Our "Wave"

int wiggle_count = 0;// Counter Used To Control How Fast Flag Waves GLfloat hold;// Temporarily Holds A Floating Point Value

Move down the the LoadGLTextures() procedure. We want to use the texture called Tim .bmp. Find LoadBMP("Data/NeHe.bmp") and replace it with LoadBMP("Data/Tim.bmp").

if (TextureImage[0]=LoadBMP("Data/Tim.bmp"))// Load The Bitmap

Now add the following code to the bottom of the InitGL() function before return TRUE.

glPolygonMode( GL_BACK, GL_FILL );// Back Face Is Filled In

glPolygonMode( GL_FRONT, GL_LINE );// Front Face Is Drawn With Lines

These simply specify that we want back f acing polygons to be filled completely and that we want front facing polygons to be outlined only. Mostly personal preference at this point. Has to do with the orientation of the polygon or the direction of the vertices.

See the Red Book for more information on this. Incidentally, while I'm at it, let me plug the book by saying it's one of the driving forces behind me learning OpenGL, not to mention NeHe's site! Thanks NeHe. Buy The Programmer's Guide to OpenGL from Addison-Wesley. It's an invaluable resource as far as I 'm concerned. Ok, back to the tutorial . Right below the code above, and above return TRUE, add the following lines.

// Loop Through The X Plane for(int x=0; x<45; x++) {

// Loop Through The Y Plane for(int y=0; y<45; y++) {

// Apply The Wave To Our Mesh points[x][y][0]=float((x/5.0f)-4.5f);

points[x][y][1]=float((y/5.0f)-4.5f);

points[x][y][2]=float(sin((((x/5.0f)*40.0f)/360.0f)*3.141592654*2.0f));

} }

Lesson 11 – Flag Effect (Waving Texture) 46

Thanks to Graham Gibbons for suggesting an integer loop to get rid of the spike in the ripple.

The two loops above initialize the points on our grid. I initialize variables in my loop to localize them in my mind as merely loop variables. Not sure it's kosher. W e use integer loops to prevent odd graphical glitches that appear when floating point calculations are used. We divide the x and y variables by 5 ( i.e. 45 / 9 = 5 ) and subtract 4.5 from each of them to center the "wave". The same effect could be accomplished with a translate, but I prefer this method.

The final value points[x][y][2] statement is our sine value. The sin() function requires radians. We take our degree value, which is our float_x multiplied by 40.0f. Once we have that, to convert to radians we take the degree, divide by 360.0f, multiply by pi, or an approximation and then multiply by 2.0f.

I'm going to re-write the DrawGLScene function from scratch so clean it out and it replace with the following code.

int DrawGLScene(GLvoid)// Draw Our GL Scene {

int x, y;// Loop Variables

float float_x, float_y, float_xb, float_yb;// Used To Break The Flag Into Tiny Quads

Different variables used for controlling the loops. See the code below but most of these serve no "specific" purpose other than controlling loops and storing temporary values.

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Clear The Screen And Depth Buffer glLoadIdentity(); // Reset The Current Matrix

glTranslatef(0.0f,0.0f,-12.0f);// Translate 17 Units Into The Screen glRotatef(xrot,1.0f,0.0f,0.0f);// Rotate On The X Axis

glRotatef(yrot,0.0f,1.0f,0.0f);// Rotate On The Y Axis glRotatef(zrot,0.0f,0.0f,1.0f);// Rotate On The Z Axis glBindTexture(GL_TEXTURE_2D, texture[0 // Select Our Texture

You've seen all of this before as well. Same as in tutorial #6 except I merely push my scene back away fr om the camera a bit more.

glBegin(GL_QUADS);// Start Drawing Our Quads

for( x = 0; x < 44; x++ )// Loop Through The X Plane 0-44 (45 Points) {

for( y = 0; y < 44; y++ )// Loop Through The Y Plane 0-44 (45 Points) {

Merely starts the loop to draw our polygons. I use integers here to keep from having to use the int() function as I did earlier to get the array reference returned as an integer.

float_x = float(x)/44.0f;// Create A Floating Point X Value float_y = float(y)/44.0f;// Create A Floating Point Y Value

float_xb = float(x+1)/44.0f;// Create A Floating Point Y Value+0.0227f float_yb = float(y+1)/44.0f;// Create A Floating Point Y Value+0.0227f

We use the four variables above for the texture coordinates. Each of our polygons (square i n the grid), has a 1/44 x 1/44 section of the texture mapped on it. The loops will specify the lower left vertex and then we just add to it accordingly to get the other three ( i.e. x+1 or y+1 ).

glTexCoord2f( float_x, float_y);// First Texture Coordinate (Bottom Left) glVertex3f( points[x][y][0], points[x][y][1], points[x][y][2] );

glTexCoord2f( float_x, float_yb );// Second Texture Coordinate (Top Left) glVertex3f( points[x][y+1][0], points[x][y+1][1], points[x][y+1][2] );

glTexCoord2f( float_xb, float_yb );// Third Texture Coordinate (Top Right) glVertex3f( points[x+1][y+1][0], points[x+1][y+1][1], points[x+1][y+1][2] );

glTexCoord2f( float_xb, float_y );// Fourth Texture Coordinate (Bottom Right) glVertex3f( points[x+1][y][0], points[x+1][y][1], points[x+1][y][2] );

} }

glEnd();// Done Drawing Our Quads

The lines above merely make the OpenGL calls to pass all the data we talked about. Four separate calls to each glTexCoord2f() and glVertex3f(). Continue with the following. Notice the quads are drawn clockwise. This means the face you see initially will be the back. The back is filled in. The front is made up of lines.

If you drew in a counter clockwise order the face you'd initially see would be the front face, meaning you would see the grid type texture instead of the filled in face.

if( wiggle_count == 2 )// Used To Slow Down The Wave (Every 2nd Frame Only) {

If we've drawn two scenes, then we want to cycle our sine values giving us "motion".

for( y = 0; y < 45; y++ )// Loop Through The Y Plane {

Lesson 11 – Flag Effect (Waving Texture) 47

hold=points[0][y][2];// Store Current Value One Left Side Of Wave for( x = 0; x < 44; x++)// Loop Through The X Plane

{

// Current Wave Value Equals Value To The Right points[x][y][2] = points[x+1][y][2];

}

points[44][y][2]=hold;// Last Value Becomes The Far Left Stored Value }

wiggle_count = 0;// Set Counter Back To Zero }

wiggle_count++;// Increase The Counter

What we do here is store the first value of each line, we then move the wave to the left one, causing the image to wave. The value we stored is then added to the end to create a never ending wave across the face of the texture. Then we reset the counter wiggle_count to keep our animation going.

The above code was modified by NeHe (Feb 2000), to fix a flaw in the ripple going across the surface of the texture. The ripple is now smooth.

xrot+=0.3f;// Increase The X Rotation Variable yrot+=0.2f;// Increase The Y Rotation Variable zrot+=0.4f;// Increase The Z Rotation Variable return TRUE;// Jump Back

}

Standard NeHe rotation values. :) And that's it folks. Compile and you should have a nice rotating bitmapped "wave". I'm not sure what else to say except, whew.. This was LONG! But I hope you guys can follow it/get something out of it. If you have any questions, want me to clear something up or tell me how god awful, lol, I code, then send me a note.

This was a blast, but very energy/tim e consuming. It makes me appreciate the likes of NeHe ALOT more now. Thanks all.

Bosco

Bosco ([email protected]@home.com) Jeff Molofee

Jeff Molofee(NeHeNeHe)

Lesson 12 – Display Lists 48

Lesson 12 Lesson 12 Display Lists Display Lists

In this tutorial I'll teach you how to use Display Lists. Not only do display lists speed up your code, they also cut down on the number of lines of code you need to write when creating a simple GL scene.

For example. Lets say you're making the game asteroids. Each level starts off wit h at least 2 asteroids. So you sit down with your graph paper (grin), and figure out how to make a 3D asteroid. Once you have everything figured out, you build the asteroid in OpenGL using Polygons or Quads. Lets say the asteroid i s octagonal (8 sides). If you're smart you'll create a loop, and draw the asteroid once inside the loop. You'll end up with roughly 18 lines or more of code to make the asteroid. Creating the asteroid each time it's drawn to the screen is hard on your system. Once you get into more complex objects you'll see what I mean.

So what's the solution? Display Lists!!! By using a display list, you create the object just once. You can texture map it, color it, whatever you want to do. You give t he display list a name. Because it's an asteroid we'll call the display list 'asteroid'. Now any time I want to draw the textured / colored asteroid on the screen, all I have to do is call glCallList(asteroid). the premade asteroid will instantly appear on the screen. Because the asteroid has already built in the display list, OpenGL doesn't have to figure out how to build it. It's prebuilt in memory. This takes alot of strain off your processor and allows your programs to run alot faster!

So are you ready to learn? :) We'll call this the Q-Bert Display List demo. What you'll end up with is a Q-Bert type screen made up of 15 cubes. Each cube is made up of a TOP, and a BOX. The top will be a seperate display list so that we can color it a darker shade. The box is a cube without the top :)

This code is based around lesson 6. I'll rewrite most of the program so it's easier to see where I've made changes. The follow lines of code are standard code used in just about all the lessons.

#include <windows.h>// Header File For Windows

#include <stdio.h>// Header File For Standard Input/Output

#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

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 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

Now we set up our variables. First we set up storage for one texture. Then we create two new variables for our 2 display lists.

These variable will act as pointers to where the display list is stored in ram. They're called box and top.

After that we have 2 variables called xloop and yloop which are used t o position the cubes on the screen and 2 variables called xrot and yrot that ar e used to rotate the cubes on the x axis and y axis.

GLuint texture[1];// Storage For One Texture GLuint box;// Storage For The Display List GLuint top;// Storage For The Second Display List GLuint xloop;// Loop For X Axis

GLuint yloop;// Loop For Y Axis

GLfloat xrot;// Rotates Cube On The X Axis GLfloat yrot;// Rotates Cube On The Y Axis

Next we create two color arrays. The first one boxcol stores the color values for Bright Red, Orange, Yellow, Green and Blue.

Lesson 12 – Display Lists 49

Each value inside the {}'s represent a red, green and blue value. Each group of {}'s is a specific color.

The second color array we create is for Dark Red, Dark Orange, Dark Yellow, Dark Green and Dark Blue. The dark colors will be used to draw the top of the boxes. We want the lid to be darker than the rest of the box.

static GLfloat boxcol[5][3]=// Array For Box Colors {

// Bright: Red, Orange, Yellow, Green, Blue

{1.0f,0.0f,0.0f},{1.0f,0.5f,0.0f},{1.0f,1.0f,0.0f},{0.0f,1.0f,0.0f},{0.0f,1.0f,1.0f}

};

static GLfloat topcol[5][3]=// Array For Top Colors {

// Dark: Red, Orange, Yellow, Green, Blue

{.5f,0.0f,0.0f},{0.5f,0.25f,0.0f},{0.5f,0.5f,0.0f},{0.0f,0.5f,0.0f},{0.0f,0.5f,0.5f}

};

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);// Declaration For WndProc

Now we build the actual Display List. If you notice, all the code to build the box is in the first list, and all the code to build the top is in the other list. I'll try to explain this section in alot of detail.

GLvoid BuildLists()// Build Box Display List {

We start off by telling OpenGL we want to build 2 lists. glGenLists(2) creates room for the two lists, and returns a pointer to the first list. 'box' will hold the location of the first list. Whenever we call box the first list will be drawn.

box=glGenLists(2);// Building Two Lists

Now we're going to build the first list. We've already freed up room for two lists, and we know that box points to the area we're going to store the first list. So now all we have to do is tell OpenGL where the list should go, and what type of list to make.

We use the command glNewList() to do the job. You'll notice box is the first parameter. This tells OpenGL to store the list in the memory location that box points to. The second parameter GL_COMPILE tells OpenGL we want to prebuild the list in memory so that OpenGL doesn't have to figure out how to create the object ever time we draw it.

GL_COMPILE is similar to programming. If you write a program, and load it into your compiler, you have to compile it every time you want to run it. If it's already compiled into an .EXE file, all you have to do is click on the .exe to run it. No compiling needed.

Once GL has compiled the display list, it's ready to go, no more compiling required. This is where we get the speed boost from using display lists.

glNewList(box,GL_COMPILE);// New Compiled box Display List

The next section of code draws the box without the top. It wont appear on the screen. It will be stored in the display list.

You can put just about any command you want between glNewList() and glEndList(). You can set colors, you can change textures, etc. The only type of code you CAN'T add is code that would change the display list on the f ly. Once the display list is built, you CAN'T change it.

If you added the line glColor3ub(rand()%255,rand()%255,rand()%255) into the code below, you might think that each time you draw the object to the screen it will be a different color. But because the list is only CREATED once, the color will not change each time you draw it to the screen. Whatever color the object was when it was first made is the color it will remain.

If you want to change the color of the display list, you have to change it BEFORE you draw the display list to the screen. I'll explain more on this later.

glBegin(GL_QUADS);// Start Drawing Quads // Bottom Face

glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);// Top Right Of The Texture and Quad glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);// Top Left Of The Texture and Quad glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);// Bottom Left Of The Texture and Quad glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);// Bottom Right Of The Texture and Quad

// Front Face

glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);// Bottom Left Of The Texture and Quad glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);// Bottom Right Of The Texture and Quad

glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);// Top Right Of The Texture and Quad glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);// Top Left Of The Texture and Quad // Back Face

glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);// Bottom Right Of The Texture and Quad

glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);// Top Right Of The Texture and Quad glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);// Top Left Of The Texture and Quad glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);// Bottom Left Of The Texture and Quad

// Right face

glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);// Bottom Right Of The Texture and Quad

glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);// Top Right Of The Texture and Quad

Lesson 12 – Display Lists 50

glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);// Top Left Of The Texture and Quad glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);// Bottom Left Of The Texture and Quad // Left Face

glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);// Bottom Left Of The Texture and Quad

glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);// Bottom Right Of The Texture and Quad

glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);// Top Right Of The Texture and Quad glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);// Top Left Of The Texture and Quad glEnd();// Done Drawing Quads

We tell OpenGL we're done making out list with the command glEndList(). Anything between glNewList() and glEndList is part of the Display List, anything before glNewList() or after glEndList() is not part of the current display list.

glEndList(); // Done Building The box List

Now we'll make our second display list. To find out where the second display list is stored in memory, we t ake the value of the old display list (box) and add one to it. The code below will make 'top' equal the location of the second display list.

top=box+1;// top List Value Is box List Value +1

Now that we know where to store the second display list, we can build it. We do this the same way we built the first display list, but this time we tell OpenGL to store the list at 'top' instead of 'box'.

glNewList(top,GL_COMPILE);// New Compiled top Display List

The following section of code just draws the top of the box. It's a simple quad drawn on the Z plane.

glBegin(GL_QUADS);// Start Drawing Quad // Top Face

glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);// Top Left Of The Texture and Quad glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);// Bottom Left Of The Texture and Quad glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);// Bottom Right Of The Texture and Quad glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);// Top Right Of The Texture and Quad glEnd();// Done Drawing Quad

Again we tell OpenGL we're done buil ding our second list with the command glEndList(). That's it. We've successfully created 2 display lists.

glEndList(); // Done Building The top Display List }

The bitmap/texture building code is the same code we used in previous tutorials to load and build a texture. W e want a texture that we can map onto all 6 sides of each cube. I've decided to use mi pmapping to make the texture look r eal smooth. I hate seeing pixels :) The name of the texture to load is called 'cube.bmp'. It's stored in a dir ectory called data. Find LoadBMP and change that line to look like the line below.

if (TextureImage[0]=LoadBMP("Data/Cube.bmp"))// Load The Bitmap Resizing code is exactly the same as the code i n Lesson 6.

The init code only has a few changes. I've added the line BuildList(). This will jump to the section of code that builds the display lists. Notice that BuildList() is after LoadGLTextures(). It's important to know the order things should go in. First we build the textures, so when we create our display lists, there's a texture already created that we can map onto the cube.

int InitGL(GLvoid)// All Setup For OpenGL Goes Here {

if (!LoadGLTextures())// Jump To Texture Loading Routine {

return FALSE;// If Texture Didn't Load Return FALSE }

BuildLists();// Jump To The Code That Creates Our Display Lists glEnable(GL_TEXTURE_2D);// Enable Texture Mapping

glShadeModel(GL_SMOOTH);// Enable Smooth Shading glClearColor(0.0f, 0.0f, 0.0f, 0.5f);// Black Background

glShadeModel(GL_SMOOTH);// Enable Smooth Shading glClearColor(0.0f, 0.0f, 0.0f, 0.5f);// Black Background

In document Nehe Opengl Tutorial (Page 49-67)