6. Terrain Vertex Buffer Objects

Jump To:

heightfield.h Source
heightfield.cpp Source

main.cpp Source
Download

Heightfield.H File:

VBO’s. Well well, this means a total change to our Render function, a new Init function and a few new classes and a few new variables. This is going to be a major change to our code, but in this case, with change comes progress, and wouldn’t you love to go from the current 5 frames per second, to say, 30 or 60? I know I would 🙂 So lets get to it.

First off, we are going to create a couple of extra classes. The first we are going to call Vert and will hold all our vertices for our terrain, and the second is going to be called TexCoord and guess what?! It will hold all of our texture coordinates for our terrain 🙂 Lets take a look at the code we need to add:

First our Vert class, which needs to hold the x, y and z coordinates for our terrain vertices:
// class Vert{
// public:
// float x;
// float y;

// float z;
// };

And second we have our TexCoord class which holds the u and v texture coordinates for our terrain:
// class TexCoord{
// public:
// float u;

// float v;
// };

Now I don’t know about you, but basic C++ knowledge explains the above simply enough. I’m going to assume you have at least some idea of what classes are. So lets go take a look at the changes to our SwiftHeightField class, starting off with our new variables. All our variables are going to be private to only our class functions, and will all be used for the creation and use of our Vertex Buffer Objects. So first off we have vhVertexCount. This will hold how many vertices our terrain has and is calculated by the width * height of our heightfield.raw file. Next up is vhVertices, which attached to our new Vert class will hold all our vertices, and finally we have vhTexCoords which used with our TexCoord class will hold all of the texture coordinates we are going to need:

// int vhVertexCount;
// Vert *vhVertices;
// TexCoord *vhTexCoords;

Now lets look at the last two variables we are going to need to add to our heightfield.h file:

// unsigned int vhVBOVertices;
// unsigned int vhVBOTexCoords;

These are going to be the buffers used for the creation of our Vertex Buffer Object. Please see the ‘Vertex Buffer Object’ tutorial (coming soon) for a possibly more indepth tutorial on how these work. As for now, lets look at the new function we are going to add. This is going to be called Init and guess what?! It is going to Initialize our Vertex Buffer Object for us 🙂

// bool Init(void);

This will be in the private section of our class and will return true if our program is able to create our Vertex Buffer Object 🙂

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.

    #include <windows.h>

class Vert{
    

public:
    
float x;
    
float y;
    
float z;
};

class TexCoord{
    
public:
    
float u;
    
float v;

};

class SwiftHeightField {
    
private:
    
int hmHeight;
    
int hmWidth;
    

    
int vhVertexCount;
    
Vert *vhVertices;
    
TexCoord *vhTexCoords;
    

    
unsigned int vhVBOVertices;
    

unsigned int vhVBOTexCoords;
    

    
unsigned int tID[2];
    

    
bool Init(void);
    

    
public:
    
bool Create(char *hFileName, const int hWidth, const int hHeight);
    

    
void Render(void);
    

    
BYTE hHeightField[1024][1024];

};

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);
}

Main.CPP File:

Our major changes to this file are going to take place in the form of initializing our extensions that we are going to use. To do this, I am first going to create a function called initExtensions which is where we are going to initialize all our extensions:

// void initExtensions(void){
// }

Now that we have a nice neat place to initialize our extensions, lets initialize them:

// glGenBuffersARB = (PFNGLGENBUFFERSARBPROC) wglGetProcAddress("glGenBuffersARB");
// glBindBufferARB = (PFNGLBINDBUFFERARBPROC) wglGetProcAddress("glBindBufferARB");
// glBufferDataARB = (PFNGLBUFFERDATAARBPROC) wglGetProcAddress("glBufferDataARB");

// glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC) wglGetProcAddress("glDeleteBuffersARB");

The above extensions are everything we need to create and use our Vertex Buffer Objects. As we now have our extensions initialized and ready to use, we need to call the initExtensions function. I am doing this inside our Init function *before* we create our heightfield. This is because the extensions are needed for the creation of our Vertex Buffer Objects, not just in drawing them. So our Init function now looks like:

// void Init (void) {
// glEnable(GL_DEPTH_TEST);
// glDepthFunc(GL_LEQUAL);

// initExtensions();

// hField.Create("heightField.raw", 1024, 1024);
// }

And that is it for now. Check out the next tutorial on optimizing the heightfield using for a good solid 60 frames per second here 🙂

If you have any questions, just email me at swiftless@gmail.com

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.
99.
100.
101.
102.
103.
104.

105.
106.
107.
108.
109.
110.
111.
112.
113.

114.
115.
116.
117.
118.
119.
120.
121.
122.

123.
124.
125.
126.
127.
128.
129.
130.
131.

132.
133.
134.
135.
136.
137.
138.
139.
140.

141.
142.
143.
144.
145.

    #include <GL/glew.h>

#include <GL/gl.h>
#include <GL/glext.h>
#include <GL/GLUT.h>

#include <math.h>

#include <windows.h>
#include <stdio.h>

#include <string.h>
#include <fstream>
#include <assert.h>

#include “heightfield.h”

#pragma comment(lib,“glew32.lib”)

float xpos = 851.078, ypos = 351.594, zpos = 281.033, xrot = 758, yrot = 238,
 angle=0.0;

float lastx, lasty;

float bounce;
float cScale = 1.0;

SwiftHeightField hField;

void camera (void) {
    
int posX = (int)xpos;
    

int posZ = (int)zpos;
    

    
glRotatef(xrot,1.0,0.0,0.0);
    
glRotatef(yrot,0.0,1.0,0.0);
    

glTranslated(xpos,ypos,zpos);
}

void display (void) {
    

glClearColor (0.0,0.0,0.0,1.0);
    
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    

glLoadIdentity();
    
camera();
    

    
glPushMatrix();
    
hField.Render();
    

glPopMatrix();
    

    
glutSwapBuffers();
}

void initExtensions(void){
    

glGenBuffersARB = (PFNGLGENBUFFERSARBPROC) wglGetProcAddress(“glGenBuffersARB”);
    
glBindBufferARB = (PFNGLBINDBUFFERARBPROC) wglGetProcAddress(“glBindBufferARB”);

    
glBufferDataARB = (PFNGLBUFFERDATAARBPROC) wglGetProcAddress(“glBufferDataARB”);
    
glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC) wglGetProcAddress(“glDeleteBuffersARB”);

}

void Init (void) {
    
glEnable(GL_DEPTH_TEST);
    

glDepthFunc(GL_LEQUAL);
    

    
initExtensions();
    

    
hField.Create(“heightField.raw”, 1024, 1024);

}

void mouseMovement(int x, int y) {
    
int diffx=xlastx;
    

int diffy=ylasty;
    
lastx=x;
    
lasty=y;
    
xrot += (float) diffy;
    

yrot += (float) diffx;
}

void keyboard (unsigned char key, int x, int y) {
    

    
if (key == ‘w’)
    
{
        
float xrotrad, yrotrad;
        

yrotrad = (yrot / 180 * 3.141592654f);
        
xrotrad = (xrot / 180 * 3.141592654f);
        

xpos += float(sin(yrotrad)) * cScale;
        
zpos = float(cos(yrotrad)) * cScale;
        

ypos = float(sin(xrotrad)) ;
        
bounce += 0.04;
    

}
    

    
if (key == ‘s’)
    
{
        
float xrotrad, yrotrad;
        

yrotrad = (yrot / 180 * 3.141592654f);
        
xrotrad = (xrot / 180 * 3.141592654f);
        

xpos = float(sin(yrotrad)) * cScale;
        
zpos += float(cos(yrotrad)) * cScale;
        

ypos += float(sin(xrotrad));
        
bounce += 0.04;
    

}
    

    
if (key == ‘d’)
    
{
        
float yrotrad;
        

yrotrad = (yrot / 180 * 3.141592654f);
        
xpos += float(cos(yrotrad)) * cScale;
        

zpos += float(sin(yrotrad)) * cScale;
    
}
    

    
if (key == ‘a’)
    
{
        
float yrotrad;
        

yrotrad = (yrot / 180 * 3.141592654f);
        
xpos = float(cos(yrotrad)) * cScale;
        

zpos = float(sin(yrotrad)) * cScale;
    
}
    

}

void reshape (int w, int h) {
    
glViewport (0, 0, (GLsizei)w, (GLsizei)h);
    

glMatrixMode (GL_PROJECTION);
    
glLoadIdentity ();
    
gluPerspective (60, (GLfloat)w / (GLfloat)h, 1.0, 1000.0);
    

glMatrixMode (GL_MODELVIEW);
}

int main (int argc, char **argv) {
    

glutInit(&argc, argv);
    
glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA);
    

glutInitWindowSize(500, 500);
    
glutInitWindowPosition(100, 100);
    
glutCreateWindow(“A basic OpenGL Window);
    

Init();
    
glutDisplayFunc(display);
    
glutIdleFunc(display);
    

glutReshapeFunc(reshape);
    
glutKeyboardFunc(keyboard);
    
glutPassiveMotionFunc(mouseMovement);
    

glutMainLoop ();
    
return 0;
}

Download:

Download heightfield.h Source Code for this Tutorial

Download heightfield.cpp Source Code for this Tutorial

Download main.cpp Source Code for this Tutorial

Download heightfield.raw for this Tutorial

Download texture.jpg for this Tutorial

Download jpeg.h Source Code for this Tutorial

Download jpeg.cpp Source Code for this Tutorial

Download jpeglib.zip for this Tutorial

  • March 25, 2010
  • 16