以文本方式查看主题 - 计算机科学论坛 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 21-lesson 22 (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=54074) |
-- 作者:一分之千 -- 发布时间:10/19/2007 12:53:00 PM -- Lesson 21 Welcome to my 21st OpenGL Tutorial! Coming up with a topic for this tutorial was extremely difficult. I know alot of you are tired of learning the basics. Everyone is dying to learn about 3D objects, Multitexturing and all that other good stuff. For those people, I'm sorry, but I want to keep the learning curve gradual. Once I've gone a step ahead it's not as easy to take a step back without people losing interest. So I'd prefer to keep pushing forward at a steady pace. In case I've lost a few of you :) I'll tell you a bit about this tutorial. Until now all of my tutorials have used polygons, quads and triangles. So I decided it would be nice to write a tutorial on lines. A few hours after starting the line tutorial, I decided to call it quits. The tutorial was coming along fine, but it was BORING! Lines are great, but there's only so much you can do to make lines exciting. I read through my email, browsed through the message board, and wrote down a few of your tutorial requests. Out of all the requests there were a few questions that came up more than others. So... I decided to write a multi-tutorial :) In this tutorial you will learn about: Lines, Anti-Aliasing, Orthographic Projection, Timing, Basic Sound Effects, and Simple Game Logic. Hopefully there's enough in this tutorial to keep everyone happy :) I spent 2 days coding this tutorial, and It's taken almost 2 weeks to write this HTML file. I hope you enjoy my efforts! At the end of this tutorial you will have made a simple 'amidar' type game. Your mission is to fill in the grid without being caught by the bad guys. The game has levels, stages, lives, sound, and a secret item to help you progress through the levels when things get tough. Although this game will run fine on a Pentium 166 with a Voodoo 2, a faster processor is recommended if you want smoother animation. I used the code from lesson 1 as a starting point while writing this tutorial. We start off by adding the required header files. stdio.h is used for file operations, and we include stdarg.h so that we can display variables on the screen, such as the score and current stage. // This Code Was Created By Jeff Molofee 2000 #include <windows.h> // Header File For Windows HDC hDC=NULL; // Private GDI Device Context filled is FALSE while the grid isn't filled and TRUE when it's been filled in. gameover is pretty obvious. If gameover is TRUE, that's it, the game is over, otherwise you're still playing. anti keeps track of antialiasing. If anti is TRUE, object antialiasing is ON. Otherwise it's off. active and fullscreen keep track of whether or not the program has been minimized or not, and whether you're running in fullscreen mode or windowed mode. bool keys[256]; // Array Used For The Keyboard Routine The variable adjust is a very special variable! Even though this program has a timer, the timer only checks to see if your computer is too fast. If it is, a delay is created to slow the computer down. On my GeForce card, the program runs insanely smooth, and very very fast. After testing this program on my PIII/450 with a Voodoo 3500TV, I noticed that the program was running extremely slow. The problem is that my timing code only slows down the gameplay. It wont speed it up. So I made a new variable called adjust. adjust can be any value from 0 to 5. The objects in the game move at different speeds depending on the value of adjust. The lower the value the smoother they move, the higher the value, the faster they move (choppy at values higher than 3). This was the only real easy way to make the game playable on slow systems. One thing to note, no matter how fast the objects are moving the game speed will never run faster than I intended it to run. So setting the adjust value to 3 is safe for fast and slow systems. The variable lives is set to 5 so that you start the game with 5 lives. level is an internal variable. The game uses it to keep track of the level of difficulty. This is not the level that you will see on the screen. The variable level2 starts off with the same value as level but can increase forever depending on your skill. If you manage to get past level 3 the level variable will stop increasing at 3. The level variable is an internal variable used for game difficulty. The stage variable keeps track of the current game stage. int loop1; // Generic Loop1 Then we have x and y. These variables will keep track of what intersection our player is at. There are 11 points left and right and 11 points up and down. So x and y can be any value from 0 to 10. That is why we need the fine values. If we could only move one of 11 spots left and right and one of 11 spots up and down our player would jump around the screen in a quick (non smooth) motion. The last variable spin will be used to spin the objects on their z-axis. struct object // Create A Structure For Our Player The first line below creates a structure for our player. Basically we're giving our player structure fx, fy, x, y and spin values. By adding this line, we can access the player x position by checking player.x. We can change the player spin by adding a number to player.spin. The second line is a bit different. Because we can have up to 15 enemies on the screen at a time, we need to create the above variables for each enemy. We do this by making an array of 15 enemies. the x position of the first enemy will be enemy[0].x. The second enemy will be enemy[1].x, etc. The last line creates a structure for our special item. The special item is an hourglass that will appear on the screen from time to time. We need to keep track of the x and y values for the hourglass, but because the hourglass doesn't move, we don't need to keep track of the fine positions. Instead we will use the fine variables (fx and fy) for other things later in the program. struct object player; // Player Information The first thing we do is create a 64 bit integer called frequency. This variable will hold the frequency of the timer. When I first wrote this program, I forgot to include this variable. I didn't realize that the frequency on one machine may not match the frequency on another. Big mistake on my part! The code ran fine on the 3 systems in my house, but when I tested it on a friends machine the game ran WAY to fast. Frequency is basically how fast the clock is updated. Good thing to keep track of :) The resolution variable keeps track of the steps it takes before we get 1 millisecond of time. mm_timer_start and mm_timer_elapsed hold the value that the timer started at, and the amount of time that has elapsed since the the timer was started. These two variables are only used if the computer doesn't have a performance counter. In that case we end up using the less accurate multimedia timer, which is still not to bad for a non-time critical game like this. The variable performance_timer can be either TRUE of FALSE. If the program detects a performance counter, the variable performance_timer variable is set to TRUE, and all timing is done using the performance counter (alot more accurate than the multimedia timer). If a performance counter is not found, performance_timer is set to FALSE and the multimedia timer is used for timing. The last 2 variables are 64 bit integer variables that hold the start time of the performance counter and the amount of time that has elapsed since the performance counter was started. The name of this structure is "timer" as you can see at the bottom of the structure. If we want to know the timer frequency we can now check timer.frequency. Nice! struct // Create A Structure For The Timer Information Basically steps[ ] is just a look-up table. If adjust was 3, we would look at the number stored at location 3 in steps[ ]. Location 0 holds the value 1, location 1 holds the value 2, location 2 holds the value 4, and location 3 hold the value 5. If adjust was 3, our objects would move 5 pixels at a time. Make sense? int steps[6]={ 1, 2, 4, 5, 10, 20 }; // Stepping Values For Slow Video Adjustment GLuint texture[2]; // Font Texture Storage Space LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc We start off by clearing all the timer variables to zero. This will set all the variables in our timer structure to zero. After that, we check to see if there is NOT a performance counter. The ! means NOT. If there is, the frequency will be stored in timer.frequency. If there was no performance counter, the code in between the { }'s is run. The first line sets the variable timer.performance_timer to FALSE. This tells our program that there is no performance counter. The second line gets our starting multimedia timer value from timeGetTime(). We set the timer.resolution to 0.001f, and the timer.frequency to 1000. Because no time has elapsed yet, we make the elapsed time equal the start time. void TimerInit(void) // Initialize Our Timer (Get It Ready) // Check To See If A Performance Counter Is Available Notice instead of sharing variables for the performance and multimedia timer start and elapsed variables, I've decided to make seperate variables. Either way it will work fine. else The first thing we do is set up a 64 bit variable called time. We will use this variable to grab the current counter value. The next line checks to see if we have a performance counter. If we do, timer.performance_timer will be TRUE and the code right after will run. The first line of code inside the { }'s grabs the counter value and stores it in the variable we created called time. The second line takes the time we just grabbed (time and subtracts the start time that we got when we initialized the timer. This way our timer should start out pretty close to zero. We then multiply the results by the resolution to find out how many seconds have passed. The last thing we do is multiply the result by 1000 to figure out how many milliseconds have passed. After the calculation is done, our results are sent back to the section of code that called this procedure. The results will be in floating point format for greater accuracy. If we are not using the peformance counter, the code after the else statement will be run. It does pretty much the same thing. We grab the current time with timeGetTime() and subtract our starting counter value. We multiply it by our resolution and then multiply the result by 1000 to convert from seconds into milliseconds. float TimerGetTime() // Get Time In Milliseconds if (timer.performance_timer) // Are We Using The Performance Timer? The top left of the screen is 0 on the x-axis and 0 on the y-axis. So by setting the player.x value to 0 we move the player to the far left side of the screen. By setting the player.y value to 0 we move our player to the top of the screen. The fine positions have to be equal to the current player position, otherwise our player would move from whatever value it's at on the fine position to the top left of the screen. We don't want to player to move there, we want it to appear there, so we set the fine positions to 0 as well. void ResetObjects(void) // Reset Player And Enemies To make sure we give all the viewable enemies a new position, we loop through all the visible enemies (stage times level). We set each enemies x position to 5 plus a random value from 0 to 5. (the maximum value rand can be is always the number you specify minus 1). So the enemy can appear on the grid, anywhere from 5 to 10. We then give the enemy a random value on the y axis from 0 to 10. We don't want the enemy to move from it's old position to the new random position so we make sure the fine x (fx) and y (fy) values are equal to the actual x and y values multiplied by width and height of each tile on the screen. Each tile has a width of 60 and a height of 40. for (loop1=0; loop1<(stage*level); loop1++) // Loop Through All The Enemies int LoadGLTextures() // Load Bitmaps And Convert To Textures if ((TextureImage[0]=LoadBMP("Data/Font.bmp")) && // Load The Font glGenTextures(2, &texture[0]); // Create The Texture for (loop1=0; loop1<2; loop1++) // Loop Through 2 Textures for (loop1=0; loop1<2; loop1++) // Loop Through 2 Textures GLvoid BuildFont(GLvoid) // Build Our Font Display List glNewList(base+loop1,GL_COMPILE); // Start Building A List GLvoid KillFont(GLvoid) // Delete The Font From Memory Notice that I enable texture mapping, reset the view and then translate to the proper x / y position. Also notice that if character set 0 is selected, the font is enlarged one and half times width wise, and double it's original size up and down. I did this so that I could write the title of the game in big letters. After the text has been drawn, I disable texture mapping. GLvoid glPrint(GLint x, GLint y, int set, const char *fmt, ...) // Where The Printing Happens if (fmt == NULL) // If There's No Text va_start(ap, fmt); // Parses The String For Variables if (set>1) // Did User Choose An Invalid Character Set? if (set==0) // If Set 0 Is Being Used Enlarge Font glCallLists(strlen(text),GL_UNSIGNED_BYTE, text); // Write The Text To The Screen We start off by setting up the view port. We do this the same way we'd do it if we were setting up a perspective view. We make the viewport equal to the width of our window. Then we select the projection matrix (thing movie projector, it information on how to display our image). and reset it. Immediately after we reset the projection matrix, we set up our ortho view. I'll explain the command in detail: The first parameter (0.0f) is the value that we want for the far left side of the screen. You wanted to know how to use actual pixel values, so instead of using a negative number for far left, I've set the value to 0. The second parameter is the value for the far right side of the screen. If our window is 640x480, the value stored in width will be 640. So the far right side of the screen effectively becomes 640. Therefore our screen runs from 0 to 640 on the x-axis. The third parameter (height) would normally be our negative y-axis value (bottom of the screen). But because we want exact pixels, we wont have a negative value. Instead we will make the bottom of the screen equal the height of our window. If our window is 640x480, height will be equal to 480. So the bottom of our screen will be 480. The fourth parameter would normally be the positive value for the top of our screen. We want the top of the screen to be 0 (good old fashioned screen coordinates) so we just set the fourth parameter to 0. This gives us from 0 to 480 on the y-axis. The last two parameters are for the z-axis. We don't really care about the z-axis so we'll set the range from -1.0f to 1.0f. Just enough that we can see anything drawn at 0.0f on the z-axis. After we've set up the ortho view, we select the modelview matrix (object information... location, etc) and reset it. GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // Resize And Initialize The GL Window glViewport(0,0,width,height); // Reset The Current Viewport glMatrixMode(GL_PROJECTION); // Select The Projection Matrix glOrtho(0.0f,width,height,0.0f,-1.0f,1.0f); // Create Ortho 640x480 View (0,0 At Top Left) glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix After the font has been built, we set things up. We enable smooth shading, set our clear color to black and set depth clearing to 1.0f. After that is a new line of code. glHint() tells OpenGL how to draw something. In this case we are telling OpenGL that we want line smoothing to be the best (nicest) that OpenGL can do. This is the command that enables anti-aliasing. The last thing we do is enable blending and select the blend mode that makes anti-aliased lines possible. Blending is required if you want the lines to blend nicely with the background image. Disable blending if you want to see how crappy things look without it. It's important to point out that antialiasing may not appear to be working. The objects in this game are quite small so you may not notice the antialaising right off the start. Look hard. Notice how the jaggie lines on the enemies smooth out when antialiasing is on. The player and hourglass should look better as well. int InitGL(GLvoid) // All Setup For OpenGL Goes Here BuildFont(); // Build The Font glShadeModel(GL_SMOOTH); // Enable Smooth Shading We clear the screen (to black) along with the depth buffer. Then we select the font texture (texture[0]). We want the words "GRID CRAZY" to be a purple color so we set red and blue to full intensity, and we turn the green up half way. After we've selected the color, we call glPrint(). We position the words "GRID CRAZY" at 207 on the x axis (center on the screen) and 24 on the y-axis (up and down). We use our large font by selecting font set 0. After we've drawn "GRID CRAZY" to the screen, we change the color to yellow (full red, full green). We write "Level:" and the variable level2 to the screen. Remember that level2 can be greater than 3. level2 holds the level value that the player sees on the screen. %2i means that we don't want any more than 2 digits on the screen to represent the level. The i means the number is an integer number. After we have written the level information to the screen, we write the stage information right under it using the same color. int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing Once a random color has been selected, we write the words "GAME OVER" to the right of the game title. Right under "GAME OVER" we write "PRESS SPACE". This gives the player a visual message letting them know that they have died and to press the spacebar to restart the game. if (gameover) // Is The Game Over? Inside the loop, we reset the view. After the view has been reset, we translate to the 490 pixels to the right plus the value of loop1 times 40.0f. This draws each of the animated player lives 40 pixels apart from eachother. The first animated image will be drawn at 490+(0*40) (= 490), the second animated image will be drawn at 490+(1*40) (= 530), etc. After we have moved to the spot we want to draw the animated image, we rotate counterclockwise depending on the value stored in player.spin. This causes the animated life images to spin the opposite way that your active player is spinning. We then select green as our color, and start drawing the image. Drawing lines is alot like drawing a quad or a polygon. You start off with glBegin(GL_LINES), telling OpenGL we want to draw a line. Lines have 2 vertices. We use glVertex2d to set our first point. glVertex2d doesn't require a z value, which is nice considering we don't care about the z value. The first point is drawn 5 pixels to the left of the current x location and 5 pixels up from the current y location. Giving us a top left point. The second point of our first line is drawn 5 pixels to the right of our current x location, and 5 pixels down, giving us a bottom right point. This draws a line from the top left to the bottom right. Our second line is drawn from the top right to the bottom left. This draws a green X on the screen. After we have drawn the green X, we rotate counterclockwise (on the z axis) even more, but this time at half the speed. We then select a darker shade of green (0.75f) and draw another x, but we use 7 instead of 5 this time. This draws a bigger / darker x on top of the first green X. Because the darker X spins slower though, it will look as if the bright X has a spinning set of feelers (grin) on top of it. for (loop1=0; loop1<lives-1; loop1++) // Loop Through Lives Minus Current Life Right after that we set the line width to 2.0f. This makes the lines thicker, making the grid look more defined. Then we disable anti-aliasing. The reason we disable anti-aliasing is because although it's a great feature, it eats CPU's for breakfast. Unless you have a killer graphics card, you'll notice a huge slow down if you leave anti-aliasing on. Go ahead and try if you want :) The view is reset, and we start two loops. loop1 will travel from left to right. loop2 will travel from top to bottom. We set the line color to blue, then we check to see if the horizontal line that we are about to draw has been traced over. If it has we set the color to white. The value of hline[loop1][loop2] will be TRUE if the line has been traced over, and FALSE if it hasn't. After we have set the color to blue or white, we draw the line. The first thing to do is make sure we haven't gone to far to the right. We don't want to draw any lines or check to see if the line has been filled in when loop1 is greater than 9. Once we are sure loop1 is in the valid range we check to see if the horizontal line hasn't been filled in. If it hasn't, filled is set to FALSE, letting our OpenGL program know that there is at least one line that hasn't been filled in. The line is then drawn. We draw our first horizontal (left to right) line starting at 20+(0*60) (= 20). This line is drawn all the way to 80+(0*60) (= 80). Notice the line is drawn to the right. That is why we don't want to draw 11 (0-10) lines. because the last line would start at the far right of the screen and end 80 pixels off the screen. filled=TRUE; // Set Filled To True Before Testing glColor3f(0.0f,0.5f,1.0f); // Set Line Color To Blue The next thing we do is check to see if we are checking a box that exists on the screen. Remember that our loop draws the 11 lines right and left and 11 lines up and down. But we dont have 11 boxes. We have 10 boxes. So we have to make sure we don't check the 11th position. We do this by making sure both loop1 and loop2 is less than 10. That's 10 boxes from 0 - 9. After we have made sure that we are in bounds we can start checking the borders. hline[loop1][loop2] is the top of a box. hline[loop1][loop2+1] is the bottom of a box. vline[loop1][loop2] is the left side of a box and vline[loop1+1][loop2] is the right side of a box. Hopefully I can clear things up with a diagram:
When loop1 is increased, the right side of our old box becomes the left side of the new box. When loop2 is increased, the bottom of the old box becomes the top of the new box. If all 4 borders are TRUE (meaning we've passed over them all) we can texture map the box. We do this the same way we broke the font texture into seperate letters. We divide both loop1 and loop2 by 10 because we want to map the texture across 10 boxes from left to right and 10 boxes up and down. Texture coordinates run from 0.0f to 1.0f and 1/10th of 1.0f is 0.1f. So to get the top right side of our box we divide the loop values by 10 and add 0.1f to the x texture coordinate. To get the top left side of the box we divide our loop values by 10. To get the bottom left side of the box we divide our loop values by 10 and add 0.1f to the y texture coordinate. Finally to get the bottom right texture coordinate we divide the loop values by 10 and add 0.1f to both the x and y texture coordinates. Quick examples: loop1=0 and loop2=0 loop1=1 and loop2=1 Hopefully that all makes sense. If loop1 and loop2 were equal to 9 we would end up with the values 0.9f and 1.0f. So as you can see our texture coordinates mapped across the 10 boxes run from 0.0f at the lowest and 1.0f at the highest. Mapping the entire texture to the screen. After we've mapped a section of the texture to the screen, we disable texture mapping. Once we've drawn all the lines and filled in all the boxes, we set the line width to 1.0f. glEnable(GL_TEXTURE_2D); // Enable Texture Mapping if (anti) // Is Anti TRUE? For the hourglass we use x and y to position the timer, but unlike our player and enemies we don't use fx and fy for fine positioning. Instead we'll use fx to keep track of whether or not the timer is being displayed. fx will equal 0 if the timer is not visible. 1 if it is visible, and 2 if the player has touched the timer. fy will be used as a counter to keep track of how long the timer should be visible or invisible. |
-- 作者:一分之千 -- 发布时间:10/19/2007 12:54:00 PM -- lesson21 (continue) So we start off by checking to see if the timer is visible. If not, we skip over the code without drawing the timer. If the timer is visible, we reset the modelview matrix, and position the timer. Because our first grid point from left to right starts at 20, we will add hourglass.x times 60 to 20. We multiply hourglass.x by 60 because the points on our grid from left to right are spaced 60 pixels apart. We then position the hourglass on the y axis. We add hourglass.y times 40 to 70.0f because we want to start drawing 70 pixels down from the top of the screen. Each point on our grid from top to bottom is spaced 40 pixels apart. After we have positioned the hourglass, we can rotate it on the z-axis. hourglass.spin is used to keep track of the rotation, the same way player.spin keeps track of the player rotation. Before we start to draw the hourglass we select a random color. if (hourglass.fx==1) // If fx=1 Draw The Hourglass glBegin(GL_LINES); // Start Drawing Our Hourglass Using Lines glLoadIdentity(); // Reset The Modelview Matrix After doing the new rotation, we set the color to a darker shade of green. So that it actually looks like the player is made up of different colors / pieces. We then draw a large '+' on top of the first piece of the player. It's larger because we're using -7 and +7 instead of -5 and +5. Also notice that instead of drawing from one corner to another, I'm drawing this piece of the player from left to right and top to bottom. glRotatef(player.spin*0.5f,0.0f,0.0f,1.0f); // Rotate Clockwise Inside the loop we reset the modelview matrix, and position the current enemy (enemy[loop1]). We position the enemy using it's fine x and y values (fx and fy). After positioning the current enemy we set the color to pink and start drawing. The first line will run from 0, -7 (7 pixels up from the starting location) to -7,0 (7 pixels left of the starting location). The second line runs from -7,0 to 0,7 (7 pixels down from the starting location). The third line runs from 0,7 to 7,0 (7 pixels to the right of our starting location), and the last line runs from 7,0 back to the beginning of the first line (7 pixels up from the starting location). This creates a non spinning pink diamond on the screen. for (loop1=0; loop1<(stage*level); loop1++) // Loop To Draw Enemies glRotatef(enemy[loop1].spin,0.0f,0.0f,1.0f); // Rotate The Enemy Blade GLvoid KillGLWindow(GLvoid) // Properly Kill The Window if (hRC) // Do We Have A Rendering Context? if (!wglDeleteContext(hRC)) // Are We Able To Delete The RC? if (hDC && !ReleaseDC(hWnd,hDC)) // Are We Able To Release The DC if (hWnd && !DestroyWindow(hWnd)) // Are We Able To Destroy The Window? if (!UnregisterClass("OpenGL",hInstance)) // Are We Able To Unregister Class KillFont(); // Kill The Font We Built int WINAPI WinMain( HINSTANCE hInstance, // Instance // Ask The User Which Screen Mode They Prefer if (!CreateGLWindow("NeHe's Line Tutorial",640,480,16,fullscreen)) // Create Our OpenGL Window ResetObjects(); // Set Player / Enemy Starting Positions while(!done) // Loop That Runs While done=FALSE Immediately after we swap the buffers we create a delay. We do this by checking to see if the current value of the timer (TimerGetTime( )) is less than our starting value plus the game stepping speed times 2. If the current timer value is less than the value we want, we endlessly loop until the current timer value is equal to or greater than the value we want. This slows down REALLY fast systems. Because we use the stepping speed (set by the value of adjust) the program will always run the same speed. For example, if our stepping speed was 1 we would wait until the timer was greater than or equal to 2 (1*2). But if we increased the stepping speed to 2 (causing the player to move twice as many pixels at a time), the delay is increased to 4 (2*2). So even though we are moving twice as fast, the delay is twice as long, so the game still runs the same speed :) One thing alot of people like to do is take the current time, and subtract the old time to find out how much time has passed. Then they move objects a certain distance based on the amount of time that has passed. Unfortunately I can't do that in this program because the fine movement has to be exact so that the player can line up with the lines on the grid. If the current fine x position was 59 and the computer decided the player needed to move two pixels, the player would never line up with the vertical line at position 60 on the grid. float start=TimerGetTime(); // Grab Timer Value Before We Draw // Draw The Scene. Watch For ESC Key And Quit Messages From DrawGLScene() while(TimerGetTime()<start+float(steps[adjust]*2.0f)) {}// Waste Cycles On Fast Systems if (keys[VK_F1]) // Is F1 Being Pressed? If the 'A' key has been released (is FALSE) then ap is set to FALSE telling the program that the key is no longer being held down. if (keys['A'] && !ap) // If 'A' Key Is Pressed And Not Held We start off by checking to make sure the game isn't over, and that the window (if in windowed mode) is still active. By checking active the enemies wont move if the screen is minimized. This gives you a convenient pause feature when you need to take a break :) After we've made sure the enemies should be moving, we create a loop. The loop will loop through all the visible enemies. Again we calculate how many enemies should be on the screen by multiplying the current stage by the current internal level. if (!gameover && active) // If Game Isn't Over And Programs Active Move Objects If the enemy x position is less than the player x position, and the enemy's fine y position is lined up with a horizontal line, we move the enemy x position one block closer to the current player position. We also do this to move the enemy left, down and up. When moving up and down, we need to make sure the enemy's fine x position lines up with a vertical line. We don't want the enemy cutting through the top or bottom of a box. Note: changing the enemies x and y positions doesn't move the enemy on the screen. Remember that when we drew the enemies we used the fine positions to place the enemies on the screen. Changing the x and y positions just tells our program where we WANT the enemies to move. if ((enemy[loop1].x<player.x) && (enemy[loop1].fy==enemy[loop1].y*40)) if ((enemy[loop1].x>player.x) && (enemy[loop1].fy==enemy[loop1].y*40)) if ((enemy[loop1].y<player.y) && (enemy[loop1].fx==enemy[loop1].x*60)) if ((enemy[loop1].y>player.y) && (enemy[loop1].fx==enemy[loop1].x*60)) If delay is greater than 3-level and the player hasn't touched the hourglass, we move the enemies by adjusting the enemy fine positions (fx and fy). The first thing we do is set delay back to 0 so that we can start the delay counter again. Then we set up a loop that loops through all the visible enemies (stage times level). if (delay>(3-level) && (hourglass.fx!=2)) // If Our Delay Is Done And Player Doesn't Have Hourglass We then check to see if the enemy fx value is greater than the enemy x position times 60 and if so, we move the enemy left and spin the enemy left. We do the same when moving the enemy up and down. If the enemy y position is less than the enemy fy position times 40 (40 pixels between grid points up and down) we increase the enemy fy position, and rotate the enemy to make it look like it's rolling downwards. Lastly if the enemy y position is greater than the enemy fy position times 40 we decrease the value of fy to move the enemy upward. Again, the enemy spins to make it look like it's rolling upward. if (enemy[loop2].fx<enemy[loop2].x*60) // Is Fine Position On X Axis Lower Than Intended Position? If the player is dead, we decrease lives. Then we check to make sure the player isn't out of lives by checking to see if lives equals 0. If lives does equal zero, we set gameover to TRUE. We then reset our objects by calling ResetObjects(), and play the death sound. Sound is new in this tutorial. I've decided to use the most basic sound routine available... PlaySound(). PlaySound() takes three parameters. First we give it the name of the file we want to play. In this case we want it to play the Die .WAV file in the Data directory. The second parameter can be ignored. We'll set it to NULL. The third parameter is the flag for playing the sound. The two most common flags are: SND_SYNC which stops everything else until the sound is done playing, and SND_ASYNC, which plays the sound, but doesn't stop the program from running. We want a little delay after the player dies so we use SND_SYNC. Pretty easy! The one thing I forgot to mention at the beginning of the program: In order for PlaySound() and the timer to work, you have to include the WINMM.LIB file under PROJECT / SETTINGS / LINK in Visual C++. Winmm.lib is the Windows Multimedia Library. If you don't include this library, you will get error messages when you try to compile the program. // Are Any Of The Enemies On Top Of The Player? if (lives==0) // Are We Out Of Lives? ResetObjects(); // Reset Player / Enemy Positions If we didn't make sure the player was at a crossing, and we allowed the player to move anyways, the player would cut right through the middle of boxes, just like the enemies would have done if we didn't make sure they were lined up with a vertical or horizontal line. Checking this also makes sure the player is done moving before we move to a new location. If the player is at a grid crossing (where a vertical and horizontal lines meet) and he's not to far right, we mark the current horizontal line that we are on as being traced over. We then increase the player.x value by one, causing the new player position to be one box to the right. We do the same thing while moving left, down and up. When moving left, we make sure the player wont be going off the left side of the grid. When moving down we make sure the player wont be leaving the bottom of the grid, and when moving up we make sure the player doesn't go off the top of the grid. When moving left and right we make the horizontal line (hline[ ] [ ]) under us TRUE meaning it's been traced. When moving up and down we make the vertical line (vline[ ] [ ]) under us TRUE meaning it has been traced. if (keys[VK_RIGHT] && (player.x<10) && (player.fx==player.x*60) && (player.fy==player.y*40)) If the player fx value is less than the player x value times 60 we increase the player fx position by the step speed our game is running at based on the value of adjust. If the player fx value is greater than the player x value times 60 we decrease the player fx position by the step speed our game is running at based on the value of adjust. If the player fy value is less than the player y value times 40 we increase the player fy position by the step speed our game is running at based on the value of adjust. If the player fy value is greater than the player y value times 40 we decrease the player fy position by the step speed our game is running at based on the value of adjust. if (player.fx<player.x*60) // Is Fine Position On X Axis Lower Than Intended Position? We set the starting level to 1, along with the actual displayed level (level2). We set stage to 0. The reason we do this is because after the computer sees that the grid has been filled in, it will think you finished a stage, and will increase stage by 1. Because we set stage to 0, when the stage increases it will become 1 (exactly what we want). Lastly we set lives back to 5. else // Otherwise If filled is TRUE, the first thing we do is play the cool level complete tune. I've already explained how PlaySound() works. This time we'll be playing the Complete .WAV file in the DATA directory. Again, we use SND_SYNC so that there is a delay before the game starts on the next stage. After the sound has played, we increase stage by one, and check to make sure stage isn't greater than 3. If stage is greater than 3 we set stage to 1, and increase the internal level and visible level by one. If the internal level is greater than 3 we set the internal leve (level) to 3, and increase lives by 1. If you're amazing enough to get past level 3 you deserve a free life :). After increasing lives we check to make sure the player doesn't have more than 5 lives. If lives is greater than 5 we set lives back to 5. if (filled) // Is The Grid Filled In? We create two loops (loop1 and loop2) to loop through the grid. We set all the vertical and horizontal lines to FALSE. If we didn't do this, the next stage would start, and the game would think the grid was still filled in. Notice the routine we use to clear the grid is similar to the routine we use to draw the grid. We have to make sure the lines are not being drawn to far right or down. That's why we check to make sure that loop1 is less than 10 before we reset the horizontal lines, and we check to make sure that loop2 is less than 10 before we reset the vertical lines. ResetObjects(); // Reset Player / Enemy Positions for (loop1=0; loop1<11; loop1++) // Loop Through The Grid X Coordinates The first line of code is PlaySound("Data/freeze.wav",NULL, SND_ASYNC | SND_LOOP). This line plays the freeze .WAV file in the DATA directory. Notice we are using SND_ASYNC this time. We want the freeze sound to play without the game stopping. SND_LOOP keeps the sound playing endlessly until we tell it to stop playing, or until another sound is played. After we have started the sound playing, we set hourglass.fx to 2. When hourglass.fx equals 2 the hourglass will no longer be drawn, the enemies will stop moving, and the sound will loop endlessly. We also set hourglass.fy to 0. hourglass.fy is a counter. When it hits a certain value, the value of hourglass.fx will change. // If The Player Hits The Hourglass While It's Being Displayed On The Screen player.spin+=0.5f*steps[adjust]; // Spin The Player Clockwise hourglass.spin-=0.25f*steps[adjust]; // Spin The Hourglass Counter Clockwise The second line checks to see if hourglass.fx is equal to 0 (non visible) and the hourglass counter (hourglass.fy) is greater than 6000 divided by the current internal level (level). If the fx value is 0 and the counter is greater than 6000 divided by the internal level we play the hourglass .WAV file in the DATA directory. We don't want the action to stop so we use SND_ASYNC. We won't loop the sound this time though, so once the sound has played, it wont play again. After we've played the sound we give the hourglass a random value on the x-axis. We add one to the random value so that the hourglass doesn't appear at the players starting position at the top left of the grid. We also give the hourglass a random value on the y-axis. We set hourglass.fx to 1 this makes the hourglass appear on the screen at it's new location. We also set hourglass.fy back to zero so it can start counting again. This causes the hourglass to appear on the screen after a fixed amount of time. hourglass.fy+=steps[adjust]; // Increase The hourglass fy Variable This causes the hourglass to disappear if you don't get it after a certain amount of time. if ((hourglass.fx==1) && (hourglass.fy>6000/level)) // Is The hourglass fx Variable Equal To 1 And The fy if hourglass.fx equal 2 and hourglass.fy is greater than 500 plus 500 times the current internal level we kill the timer sound that we started playing endlessly. We kill the sound with the command PlaySound(NULL, NULL, 0). We set hourglass.fx back to 0, and set hourglass.fy to 0. Setting fx and fy to 0 starts the hourglass cycle from the beginning. fy will have to hit 6000 divided by the current internal level before the hourglass appears again. if ((hourglass.fx==2) && (hourglass.fy>500+(500*level)))// Is The hourglass fx Variable Equal To 2 And The fy delay++; // Increase The Enemy Delay Counter // Shutdown Please note, this was an extremely large projects. I tried to comment everything as clearly as possible, but putting what things into words isn't as easy as it may seem. I know how everything works off by heart, but trying to explain is a different story :) If you've read through the tutorial and have a better way to word things, or if you feel diagrams might help out, please send me suggestions. I want this tutorial to be easy to follow through. Also note that this is not a beginner tutorial. If you haven't read through the previous tutorials please don't email me with questions until you have. Thanks. Jeff Molofee (NeHe) |
-- 作者:一分之千 -- 发布时间:10/19/2007 12:55:00 PM -- 第二十二课 这是一课高级教程,请确信你对基本知识已经非常了解了。这一课是基于第六课的代码的,它将建立一个非常酷的立体纹理效果。 #include <string.h> // 字符串处理函数 #define MAX_EMBOSS (GLfloat)0.01f // 定义了突起的最大值 现在回到我们的代码,__ARB_ENABLE用来设置是否使用ARB扩展。如果你想看你的OpenGL扩展,只要把#define EXT_INFO前的注释去掉就行了。接着,我们在运行检查我们的扩展,以保证我们的程序可以在不同的系统上运行。所以我们需要一些内存保存扩展名的字符串,他们是下面两行。接着我们用一个变量multitextureSupported来标志当前系统是否能使用multitexture扩展,并用maxTexelUnits记录运行系统的纹理单元,这个值最少是1。 #define __ARB_ENABLE true // 使用它设置是否使用ARB扩展 PFNGLMULTITEXCOORD1FARBPROC glMultiTexCoord1fARB = NULL; GLuint filter=1; // 定义过滤器类型 // 立方体的纹理和坐标 GLfloat data[]= { bool isInString(char *string, const char *search) { bool initMultitexture(void) { #ifdef EXT_INFO if (isInString(extensions,"GL_ARB_multitexture") // 是否支持多重纹理扩展? #ifdef EXT_INFO return true; void initLights(void) { int LoadGLTextures() { // 载入*.bmp图像,并转换为纹理 // 加载基础纹理 // 创建使用临近过滤器过滤得纹理 // 创建使用线形过滤器过滤得纹理 // 创建使用线形Mipmap过滤器过滤得纹理 if (Image) { // 如果图像句柄存在,则释放图像回收资源 // 载入凹凸贴图 // 创建使用临近过滤器过滤得纹理 for (int i=0; i<3*Image->sizeX*Image->sizeY; i++) // 反转凹凸贴图数据 glGenTextures(3, invbump); // 创建三个反转了凹凸贴图 // 创建使用临近过滤器过滤得纹理 if (Image=auxDIBImageLoad("Data/OpenGL_ALPHA.bmp")) { glGenTextures(1, &glLogo); // 创建标志纹理 // 使用线形过滤器 if (Image) { // 如果图像存在,则删除 // 载入扩展标志纹理 if (Image) { // 如果图像存在,则删除 void doCube (void) { multitextureSupported=initMultitexture(); initLights(); 凹凸贴图 凹凸贴图 真实的凹凸贴图是逐像素计算的 光照计算是按每个象素点的法向量计算的 只能对漫射光计算,不能使用反射光 C = (L*N)*Dl*Dm L 顶点到灯之间的单位向量 纹理图代表高度场 [0,1] 之间的高度代表凹凸方程 偏移量m的近似导出 查找(s,t)纹理的高度H0 1) 原始凸起(H0).
2) 原始的凸起(H0)向光源移动一小段距离形成第二个凸起(H1)
3) 用H1凸起减去H0凸起 (H1-H0) 计算灯光亮度 计算片断的颜色Cf Cf = (L*N) x Dl x Dm 我们还没有完成所有的任务,还必须做以下内容: 保存纹理! 把灯光方向向量变换到一个笛卡尔坐标系中 使用法向坐标系中的向量作为偏移量 计算向量,纹理坐标 混合0 alpha设置: 虽然我们做了一些改动,使得这个程序的实现与TNT的实现不一样,但它能工作与各种不同的显卡上。在这里我们将学到两三件事,凹凸映射在大多数显卡上是一个多通道算法(在TNT系列,可以使用一个2纹理通道实现),现在你应该能想到多重纹理的好处了吧。我们将使用一个三通道非多重纹理的算法实现,这个算法可以被改写为使用一个2纹理通道实现的算法。 // 计算向量v=v*M(左乘) 开始,让我们看看它的算法 所有的向量必须在物体空间或则世界空间中 在我们的实现里,它看起来和上面的实现差不多,除了投影部分,我们将使用我们自己的近似。 我们使用模型坐标,这种设定可以使得灯光位置相对于物体不变。 这个示意图显示了我们坐标系统,你可以通过相减相邻的坐标来获得s,t向量,但必须保证他们构成右手系和归一化。 结束理论讲解(凹凸映射) 下面让我们看看如何生成偏移量,首先创建一个函数创建凹凸映射: // 设置纹理偏移,都为单位长度 下面的函数显示两个标志:一个OpenGL的标志,一个多重纹理的标志,如果可以使用多重纹理,则标志使用alpha混合,并看起来半透明。为了让它在屏幕的边沿显示我们使用混合并禁用光照和深度测试。 void doLogo(void) { bool doMesh1TexelUnits(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清空背景颜色和深度缓存 // 创建模型变换矩阵的逆 // 设置灯光的位置 glBindTexture(GL_TEXTURE_2D, bump[filter]); glBindTexture(GL_TEXTURE_2D,invbump[filter]); glBegin(GL_QUADS); if (!emboss) { xrot+=xspeed; //绘制标志 bool doMesh2TexelUnits(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清空背景颜色和深度缓存 // 创建模型变换矩阵的逆 glRotatef(xrot,1.0f,0.0f,0.0f); // 设置灯光的位置 // 纹理单元 #0 // 纹理单元 #1 // 禁用混合和光照 glBegin(GL_QUADS); glActiveTextureARB(GL_TEXTURE1_ARB); xrot+=xspeed; doLogo(); bool doMeshNoBumps(void) { glRotatef(xrot,1.0f,0.0f,0.0f); if (useMultitexture) { glDisable(GL_BLEND); xrot+=xspeed; doLogo(); bool DrawGLScene(GLvoid) GLvoid KillGLWindow(GLvoid) BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag) LRESULT CALLBACK WndProc( HWND hWnd, if (keys['E']) if (keys['M']) if (keys['B']) if (keys['F']) if (keys[VK_PRIOR]) if (keys[VK_NEXT]) if (keys[VK_UP]) if (keys[VK_DOWN]) if (keys[VK_RIGHT]) if (keys[VK_LEFT]) Michael I. Gold ,它写出了凹凸贴图的原理 |
-- 作者:一分之千 -- 发布时间:10/19/2007 12:57:00 PM -- Lesson 22 This lesson was written by Jens Schneider. It is loosely based on Lesson 06, though lots of changes were made. In this lesson you will learn: How to control your graphic-accelerator抯 multitexture-features. How to do a "fake" Emboss Bump Mapping. How to do professional looking logos that "float" above your rendered scene using blending. Basics about multi-pass rendering techniques. How to do matrix-transformations efficiently. Since at least three of the above four points can be considered "advanced rendering techniques", you should already have a general understanding of OpenGL抯 rendering pipeline. You should know most commands already used in these tutorials, and you should be familiar with vector-maths. Every now and then you抣l encounter a block that reads begin theory(...) as header and end theory(...) as an ending. These sections try to teach you theory about the issue(s) mentioned in parenthesis. This is to ensure that, if you already know about the issue, you can easily skip them. If you encounter problems while trying to understand the code, consider going back to the theory sections. Last but not least: This lesson consists out of more than 1,200 lines of code, of which large parts are not only boring but also known among those that read earlier tutorials. Thus I will not comment each line, only the crux. If you encounter something like this >?lt;, it means that lines of code have been omitted. Here we go: #include <windows.h> // Header File For Windows #define MAX_EMBOSS (GLfloat)0.01f // Maximum Emboss-Translate. Increase To Get Higher Immersion Most accelerators have more than just one texture-unit nowadays. To benefit of this feature, you抣l have to check for GL_ARB_multitexture-support, which enables you to map two or more different textures to one OpenGL-primitive in just one pass. Sounds not too powerful, but it is! Nearly all the time if you抮e programming something, putting another texture on that object results in higher visual quality. Since you usually need multiple "passes" consisting out of interleaved texture-selection and drawing geometry, this can quickly become expensive. But don抰 worry, this will become clearer later on. Now back to code: __ARB_ENABLE is used to override multitexturing for a special compile-run entirely. If you want to see your OpenGL-extensions, just un-comment the #define EXT_INFO. Next, we want to check for our extensions during run-time to ensure our code stays portable. So we need space for some strings. These are the following two lines. Now we want to distinguish between being able to do multitexture and using it, so we need another two flags. Last, we need to know how many texture-units are present(we抮e going to use only two of them, though). At least one texture-unit is present on any OpenGL-capable accelerator, so we initialize maxTexelUnits with 1. #define __ARB_ENABLE true // Used To Disable ARB Extensions Entirely The lines ommitted are GDI-context handles etc. PFNGLMULTITEXCOORD1FARBPROC glMultiTexCoord1fARB = NULL; GLuint filter=1; // Which Filter To Use // Data Contains The Faces Of The Cube In Format 2xTexCoord, 3xVertex. GLfloat data[]= { LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc First, we can assume that we have a long string containing all supported extensions as 慭n?seperated sub-strings. So all we need to do is to search for a 慭n?and start comparing string with search until we encounter another 慭n?or until string doesn抰 match search anymore. In the first case, return a true for "found", in the other case, take the next sub-string until you encounter the end of string. You抣l have to watch a little bit at the beginning of string, since it does not begin with a newline-character. By the way: A common rule is to ALWAYS check during runtime for availability of a given extension! bool isInString(char *string, const char *search) { bool initMultitexture(void) { #ifdef EXT_INFO if (isInString(extensions,"GL_ARB_multitexture") // Is Multitexturing Supported? return true; void initLights(void) { Note also, that I use GL_RGB8 instead of just "3" when specifying texture-type. This is to be more conformant to upcoming OpenGL-ICD releases and should always be used instead of just another number. I marked it in orange for you. int LoadGLTextures() { // Load Bitmaps And Convert To Textures // Load The Tile-Bitmap for Base-Texture // Create Nearest Filtered Texture // Create Linear Filtered Texture // Create MipMapped Texture if (Image) { // If Texture Exists Another issue is, that we don抰 want to have our bitmap repeated over and over in the texture. We just want it once, mapping to texture-coordinates (s,t)=(0.0f, 0.0f) thru (s,t)=(1.0f, 1.0f). All other texture-coordinates should be mapped to plain black. This is accomplished by the two glTexParameteri()-calls that are fairly self-explanatory and "clamp" the bitmap in s and t-direction. // Load The Bumpmaps // Create Nearest Filtered Texture // Create Linear Filtered Texture // Create MipMapped Texture for (int i=0; i<3*Image->sizeX*Image->sizeY; i++) // Invert The Bumpmap glGenTextures(3, invbump); // Create Three Textures // Create Nearest Filtered Texture // Create Linear Filtered Texture // Create MipMapped Texture // Load The Logo-Bitmaps glGenTextures(1, &glLogo); // Create One Textures // Create Linear Filtered RGBA8-Texture if (Image) { // If Texture Exists // Load The "Extension Enabled"-Logo if (Image) { // If Texture Exists GLvoid ReSizeGLScene(GLsizei width, GLsizei height) void doCube (void) { int InitGL(GLvoid) // All Setup For OpenGL Goes Here initLights(); // Initialize OpenGL Light Begin Theory ( Emboss Bump Mapping ) If you have a Powerpoint-viewer installed, it is highly recommended that you download the following presentation: "Emboss Bump Mapping" by Michael I. Gold, nVidia Corp. [.ppt, 309K] For those without Powerpoint-viewer, I抳e tried to convert the information contained in the document to .html-format. Here it comes: Emboss Bump Mapping Michael I. Gold NVidia Corporation Bump Mapping Real Bump Mapping Uses Per-Pixel Lighting. Emboss Bump Mapping Is A Hack C=(L*N) x Dl x Dm Texture Map Represents Heightfield Embossing Approximates Derivative
1) Original bump (H0).
2) Original bump (H0) overlaid with second bump (H1) slightly perturbed toward light source.
3) Substract original bump from second (H0-H1). This leads to brightened (B) and darkened (D) areas. Compute The Lighting Evaluate Fragment Color Cf We抮e Not Quite Done Yet. We Still Must: Conserve Textures! Rotate Light Vector Into Normal Space Use Normal-Space Light Vector For Offsets Calculate Vectors, Texcoords On The Host Combiner 0 Alpha-Setup: Though we抮e doing it a little bit different than the TNT-Implementation to enable our program to run on ALL accelerators, we can learn two or three things here. One thing is, that bump mapping is a multi-pass algorithm on most cards (not on TNT-family, where it can be implemented in one 2-texture pass.) You should now be able to imagine how nice multitexturing really is. We抣l now implement a 3-pass non-multitexture algorithm, that can be (and will be) developed into a 2-pass multitexture algorithm. By now you should be aware, that we抣l have to do some matrix-matrix-multiplication (and matrix-vector-multiplication, too). But that抯 nothing to worry about: OpenGL will do the matrix-matrix-multiplication for us (if tweaked right) and the matrix-vector-multiplication is really easy-going: VMatMult(M,v) multiplies matrix M with vector v and stores the result back in v: v:=M*v. All Matrices and vectors passed have to be in homogenous-coordinates resulting in 4x4 matrices and 4-dim vectors. This is to ensure conformity to OpenGL in order to multiply own vectors with OpenGL-matrices right away. // Calculates v=vM, M Is 4x4 In Column-Major, v Is 4dim. Row (i.e. "Transposed") Here we抣l discuss two different algorithms. I found the first one several days ago under: The program is called GL_BUMP and was written by Diego T醨tara in 1999. But his implementation does the diffuse lighting the same way we抣l do it: by using OpenGL抯 built-in lighting. Since we can抰 use the combiners-method Gold suggests (we want our programs to run anywhere, not just on TNT-cards!), we can抰 store the diffuse factor in the alpha channel. Since we already have a 3-pass non-multitexture / 2-pass multitexture problem, why not apply OpenGL-Lighting to the last pass to do all the ambient light and color stuff for us? This is possible (and looks quite well) only because we have no complex geometry, so keep this in mind. If you抎 render several thousands of bump mapped triangles, try to invent something new! Furthermore, he uses multitexturing, which is, as we shall see, not as easy as you might have thought regarding this special case. But now to our Implementation. It looks quite the same to the above Algorithm, except for the projection step, where we use an own approach: This figure shows where our vectors are located. You can get t and s by simply subtracting adjacent vertices, but be sure to have them point in the right direction and to normalize them. The blue spot marks the vertex where texCoord2f(0.0f,0.0f) is mapped to. End Theory ( Emboss Bump Mapping Algorithms ) Let抯 have a look to texture-coordinate offset generation, first. The function is called SetUpBumps(), since this actually is what it does: // Sets Up The Texture-Offsets I always like logos to be displayed while presentational programs are running. We抣l have two of them right now. Since a call to doLogo() resets the GL_MODELVIEW-matrix, this has to be called as final rendering pass. This function displays two logos: An OpenGL-Logo and a multitexture-Logo, if this feature is enabled. The logos are alpha-blended and are sort of semi-transparent. Since they have an alpha-channel, I blend them using GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, as suggested by all OpenGL-documentation. Since they are all co-planar, we do not have to z-sort them before. The numbers that are used for the vertices are "empirical" (a.k.a. try-and-error) to place them neatly into the screen edges. We抣l have to enable blending and disable lighting to avoid nasty effects. To ensure they抮e in front of all, just reset the GL_MODELVIEW-matrix and set depth-function to GL_ALWAYS. void doLogo(void) { By the way: If you don抰 exactly know how the modelview was built, consider using world-space, since matrix-inversion is complicated and costly. But if you抮e doing large amounts of vertices inverting the modelview with a more generalized approach could be faster. bool doMesh1TexelUnits(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer // Build Inverse Modelview Matrix First. This Substitutes One Push/Pop With One glLoadIdentity(); // Transform The Lightposition Into Object Coordinates: glBindTexture(GL_TEXTURE_2D, bump[filter]); You could save computing time by just rotating the lightvector into inverted direction. However, this didn抰 work out correctly, so we do it the plain way: rotate each normal and center-point the same way we rotate our geometry! glBindTexture(GL_TEXTURE_2D,invbump[filter]); glBegin(GL_QUADS); if (!emboss) { xrot+=xspeed; /* LAST PASS: Do The Logos! */ bool doMesh2TexelUnits(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer // Build Inverse Modelview Matrix First. This Substitutes One Push/Pop With One glLoadIdentity(); glRotatef(xrot,1.0f,0.0f,0.0f); // Transform The Lightposition Into Object Coordinates: // TEXTURE-UNIT #0 // TEXTURE-UNIT #1 // General Switches glBegin(GL_QUADS); glActiveTextureARB(GL_TEXTURE1_ARB); xrot+=xspeed; /* LAST PASS: Do The Logos! */ bool doMeshNoBumps(void) { glRotatef(xrot,1.0f,0.0f,0.0f); if (useMultitexture) { glDisable(GL_BLEND); xrot+=xspeed; /* LAST PASS: Do The Logos! */ bool DrawGLScene(GLvoid) // Here's Where We Do All The Drawing GLvoid KillGLWindow(GLvoid) // Properly Kill The Window BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag) LRESULT CALLBACK WndProc( HWND hWnd, // Handle For This Window int WINAPI WinMain( HINSTANCE hInstance, // Instance >?lt; if (keys['E']) if (keys['M']) if (keys['B']) if (keys['F']) if (keys[VK_PRIOR]) if (keys[VK_NEXT]) if (keys[VK_UP]) if (keys[VK_DOWN]) if (keys[VK_RIGHT]) if (keys[VK_LEFT]) If you have questions and / or suggestions regarding this lesson, you can just mail me, since I have not yet a web page. This is my current project and will follow soon. Thanks must go to: Jeff Molofee (NeHe) |
-- 作者:ririyeye -- 发布时间:1/8/2010 10:41:00 AM -- 顶 |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
6,085.938ms |