Heightfield.CPP File: Who’s ready for a major code overhaul?! I know I am So without further ado: Starting off, we are going to add a couple of new header files, these are the glew.h and glext.h files, used for the incorporation of OpenGL extensions within our program. They just let us call the extensions without us having to link them to OpenGL manually. So we have the new lines: // #include <GL/glew.h> // #include <GL/glext.h> Simple enough so far? Good, now lets look at our new Init function. The following code is the outline of our function. Pretty bland isn’t it? // bool SwiftHeightField::Init(void){ // return true; // } Now lets look at generating a Vertex Buffer Object for our vertices First off, we need to call glGenBuffersARB to tell OpenGL that we want to generate a buffer for our Vertex Buffer Object: // glGenBuffersARB(1, &vhVBOVertices); Then we need to bind that buffer as an array for OpenGL to fill with data: // glBindBufferARB(GL_ARRAY_BUFFER_ARB, vhVBOVertices); And finally we need to fill the Vertex Buffer Object with our vertex information that is loaded in our Create function: // glBufferDataARB(GL_ARRAY_BUFFER_ARB, vhVertexCount * 3 * sizeof(float), vhVertices, GL_STATIC_DRAW_ARB); The last line here is telling OpenGL to fill our current open buffer vhVBOVertices, with our vertices vhVertices, the size of our vertex count vhVertexCount, multiplied by 3 (for our x, y and z coordinates), then multiplied by the size of a float variable. We set our Vertex Buffer Object to GL_STATIC_DRAW_ARB, because we don’t plan on updating/changing the vertices inside our Vertex Buffer Object as a terrain is generally static (unless you want to get into destructible terrain). After we have generated, bound and filled our buffer, we are going to want to just clean up after ourselves and delete all the information in vhVertices, and set it to nothing to save system resources: // delete [] vhVertices; // vhVertices = NULL; Did you get all that? If not, you can email me, but for now lets continue the tutorial After you have done this for your vertices, you are going to want to do the same for our texture coordinates. This is done the exact same way, just changing which variables we are using for what: // glGenBuffersARB(1, &vhVBOTexCoords); // glBindBufferARB(GL_ARRAY_BUFFER_ARB, vhVBOTexCoords); // glBufferDataARB(GL_ARRAY_BUFFER_ARB, vhVertexCount * 2 * sizeof(float), vhTexCoords, GL_STATIC_DRAW_ARB); Just make sure here, that you change the 3 from the previous code, to a 2. This is because last time with our vertices, we were working with x, y and z floats for our coordinates, now we are only working with u and v floats for our texture coordinates. Vertices have an extra third dimension to work with, while textures only work in two dimensions. And also remember to clean up after with this as well: // delete [] vhTexCoords; // vhTexCoords = NULL; Now let us move onto our Create function. This is where the next major change occurs. Not in the loading of the terrain, but the loading of the terrain data into our vhVertices and vhTexCoords variables for use in our Init function. The first one we are going to look at is the preperation of our vhVertices and vhTexCoords variables. First off we are going to declare just how many vertices our Vertex Buffer Object will be using. This is done by multiplying the height by the width by 6 of the terrain. We need to multiply by 6 because we are using triangles, are two triangles make up a ‘patch’ or polygon and there are 3 vertices in each triangle. The extra code division of (1 * 1) will be used later on when I add some REALLY basic Level Of Detail optimization to just lower how many polygons are drawn. At this stage, we are drawing every polygon at once, so we divide it by (1 * 1) = 1 to get our initial number of vertices. Next up we set our vhVertices variable to an array the size of the number of vertices that we have. And we do the same for our texture coordinates. // vhVertexCount = (int)(hmHeight * hHeight * 6) / (1 * 1); // vhVertices = new Vert[vhVertexCount]; // vhTexCoords = new TexCoord[vhVertexCount]; After this, we have a few temporary variables, these are nIndex, flX and flZ. These are going to hold which vertex we are up to and which vertex we are working on. // int nIndex = 0; // float flX; // float flZ; Now we are going to enter a couple of loops that will go through our terrain from left to right, and front to back (entirely open to interpretation). // for (int hMapX = 0; hMapX < hmWidth; hMapX++){ // for (int hMapZ = 0; hMapZ < hmHeight; hMapZ++){ Inside our loops we are going to add another third loop, which will go through each vertex of the current triangle we are up to. // for (int nTri = 0; nTri < 6; nTri++){ Now while we are looping through everything, here is where we calculate everything we need. First off we are going to temporarily work out which X and Z coordinate we are up to for our vertex. The following two lines of code read (in pseudocode): if nTri = 1 or 2 or 5{ hMapX = hMapX + 1; } else { hMapX = hMapX; } And the same goes for the Z value we are currently up to, only now using triangles 2, 4 and 5. // flX = (float)hMapX + ((nTri == 1 || nTri == 2 || nTri == 5) ? 1 : 0); // flZ = (float)hMapZ + ((nTri == 2 || nTri == 4 || nTri == 5) ? 1 : 0); These next three lines, are simply setting the x, y and z values of our current vertex in vhVertices to what it would be if we were just plain out using our render code from the previous tutorials, only difference is I have condensed it with the above lines. // vhVertices[nIndex].x = flX; // vhVertices[nIndex].y = hHeightField[(int)flX][(int)flZ]; // vhVertices[nIndex].z = flZ; The next two lines are devoted to setting the texture coordines for our current VhTexCoords just as we did on the fly in our previous tutorials. // vhTexCoords[nIndex].u = flX / 1024; // vhTexCoords[nIndex].v = flZ / 1024; And the final line of nIndex++ just increments which vertex we are actually up to. // nIndex++; // } // } // } Now while we are still in our Create function, we need to call our Init function now that we have everything read in and ready to transfer to our Vertex Buffer Objects. To do this, I just call the line: // Init(); At some stage after all of the above code Now let us move onwards to our Render function! Now to start off, we are going to just delete EVERYTHING from inside our Render function and start from scratch Hehe. Sounds like alot of fun now don’t it? Of course it is! We LOVE rewriting functions don’t we?! Anyways…. Now that you are prepared, it’s actually not that tricky. Because we are working with Vertex Buffer Objects, we also start working with Client States. These work in exactly the same way (syntax wise) as glEnable and glDisable. Only now we have glEnableClientState and glDisableClientState. So first off we are going to enable our texture coordinate array client state, enable texturing and then bind our texture like so: // glEnableClientState(GL_TEXTURE_COORD_ARRAY); // glEnable(GL_TEXTURE_2D); // glBindTexture(GL_TEXTURE_2D, tID[0]); Next off, we need to bind which vertex buffer object goes with this texture coordinate client state. This is our vhVBOTexCoords vertex buffer object that we created in our Init function. After we have bound which buffer object we are going to use, we need to call glTexCoordPointer to tell OpenGL that this is a texture coordinate pointer. // glBindBufferARB(GL_ARRAY_BUFFER_ARB, vhVBOTexCoords); // glTexCoordPointer(2, GL_FLOAT, 0, (char *) NULL); The next three lines are similar, only this time we are working with our vertex buffer, and not our texture coordinate buffer. So we first need to enable the client state we are going to be using: // glEnableClientState(GL_VERTEX_ARRAY); Then we have to bind our buffer vhVBOVertices and call glVertexPointer. Now that we have everything set up, it is time to draw our Vertex Buffer Object. // glBindBufferARB(GL_ARRAY_BUFFER_ARB, vhVBOVertices); // glVertexPointer(3, GL_FLOAT, 0, (char *) NULL); We do this with the following line. Yes, it is only one line of code to actually draw it, everything else is setting up to draw it We are drawing our vertex arrays as a set of triangles, starting at position 0 in our Vertex Buffer Object and ending at the size of vhVertexCount. // glDrawArrays(GL_TRIANGLES, 0, vhVertexCount); Now that we have setup and drawn our Vertex Buffer Object, we need to disable our client states and texturing with the following lines: // glDisableClientState(GL_VERTEX_ARRAY); // glDisable(GL_TEXTURE_2D); // glDisableClientState(GL_TEXTURE_COORD_ARRAY); Phew, now we can finally move away from this file  1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. | | | #include <stdio.h> #include <GL/glew.h> #include <GL/gl.h> #include <GL/glext.h> #include “jpeg.h” #include “heightfield.h” bool SwiftHeightField::Init(void){ glGenBuffersARB(1, &vhVBOVertices); glBindBufferARB(GL_ARRAY_BUFFER_ARB, vhVBOVertices); glBufferDataARB(GL_ARRAY_BUFFER_ARB, vhVertexCount * 3 * sizeof(float), vhVertices, GL_STATIC_DRAW_ARB); glGenBuffersARB(1, &vhVBOTexCoords); glBindBufferARB(GL_ARRAY_BUFFER_ARB, vhVBOTexCoords); glBufferDataARB(GL_ARRAY_BUFFER_ARB, vhVertexCount * 2 * sizeof(float), vhTexCoords, GL_STATIC_DRAW_ARB); delete [] vhVertices; vhVertices = NULL; delete [] vhTexCoords; vhTexCoords = NULL; return true; } bool SwiftHeightField::Create(char *hFileName, const int hWidth, const int hHeight){ hmHeight = hHeight; hmWidth = hWidth; FILE *fp; fp = fopen(hFileName, “rb”); fread(hHeightField, 1, hWidth * hHeight, fp); fclose(fp); vhVertexCount = (int)(hmHeight * hmWidth * 6) / (1 * 1); vhVertices = new Vert[vhVertexCount]; vhTexCoords = new TexCoord[vhVertexCount]; int nIndex = 0; float flX; float flZ; for (int hMapX = 0; hMapX < hmWidth; hMapX++){ for (int hMapZ = 0; hMapZ < hmHeight; hMapZ++){ for (int nTri = 0; nTri < 6; nTri++){ flX = (float)hMapX + ((nTri == 1 || nTri == 2 || nTri == 5) ? 1 : 0); flZ = (float)hMapZ + ((nTri == 2 || nTri == 4 || nTri == 5) ? 1 : 0); vhVertices[nIndex].x = flX; vhVertices[nIndex].y = hHeightField[(int)flX][(int)flZ]; vhVertices[nIndex].z = flZ; vhTexCoords[nIndex].u = flX / 1024; vhTexCoords[nIndex].v = flZ / 1024; nIndex++; } } } SwiftTextureJpeg(tID, “texture.jpg”, 0); Init(); return true; } void SwiftHeightField::Render(void){ glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, tID[0]); glBindBufferARB(GL_ARRAY_BUFFER_ARB, vhVBOTexCoords); glTexCoordPointer(2, GL_FLOAT, 0, (char *) NULL); glEnableClientState(GL_VERTEX_ARRAY); glBindBufferARB(GL_ARRAY_BUFFER_ARB, vhVBOVertices); glVertexPointer(3, GL_FLOAT, 0, (char *) NULL); glDrawArrays(GL_TRIANGLES, 0, vhVertexCount); glDisableClientState(GL_VERTEX_ARRAY); glDisable(GL_TEXTURE_2D); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } |
|