7. Terrain Level Of Detail

Jump To:

heightfield.h Source
heightfield.cpp Source

main.cpp Source
Download

Heightfield.H File:

This is a really simple change I am going to implement, which will give us a crisper looking terrain, while also giving us a constant 60 frames per second 😀 I am going to add a variable here which will be called hLOD:

// int hLOD;

This is going to be a public variable which we are going to set before we create our terrain. It is going to be a number which is a power of 2. Eg: 2, 4, 8, 16. I am going to use it to draw every nth vertex, where n is the hLOD variable. So we are going to draw every hLOD vertex if that makes sense. The reason this speeds up the terrain rendering should be obvious, the less we have to draw, the faster it will go. But it makes it crisper as well, because if you run the previous tutorials and get up close to the terrain, you will find that it is really jagged. This also smooths out the terrain surface for us.

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.

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

int hLOD;

BYTE hHeightField[1024][1024];

};

Heightfield.CPP File:

In our heightfield.cpp file, all our changes are going to take place in the Create function. First, we need to change the vertex count that we will be using throughout to tell us how many vertices we need, we do this by changing the following line:

// vhVertexCount = (int)(hmHeight * hHeight * 6) / (1 * 1);

to:

// vhVertexCount = (int)(hmHeight * hHeight * 6) / (hLOD * hLOD);

The next change we need to make is to the increments in our loops. Instead of incrementing by 1 for every vertex, we need to increment by every hLOD to get every hLOD’th vertex like so:

for (int hMapX = 0; hMapX < hmWidth; hMapX+=hLOD){

for (int hMapZ = 0; hMapZ < hmHeight; hMapZ+=hLOD){

After we have our loop setup, we need to make sure that our polygons span over every hLOD vertex, so instead of incrementing our vertices X and Z positions by 1, we need to increment them by our hLOD value as follows:

flX = (float)hMapX + ((nTri == 1 || nTri == 2 || nTri == 5) ? hLOD : 0);
flZ = (float)hMapZ + ((nTri == 2 || nTri == 4 || nTri == 5) ? hLOD : 0);

Next we are going to move onto our main.cpp file and set a value for our hLOD variable.

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.

    #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 * 
* sizeof(float), vhVertices, GL_STATIC_DRAW_ARB);

glGenBuffersARB(1, &vhVBOTexCoords);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vhVBOTexCoords);

glBufferDataARB(GL_ARRAY_BUFFER_ARB, vhVertexCount * 
* 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 * hHeight * 6) / (hLOD * hLOD
);

vhVertices = new Vert[vhVertexCount];
vhTexCoords = new TexCoord[vhVertexCount];

int nIndex = 0;
float flX;
float flZ;

for (int hMapX = 0; hMapX < hmWidth; hMapX+=hLOD){

for (int hMapZ = 0; hMapZ < hmHeight; hMapZ+=hLOD){
for (int nTri = 0; nTri < 6; nTri++){

flX = (float)hMapX + ((nTri == 1 || nTri == 2 || nTri == 5) ? hLOD : 0);

flZ = (float)hMapZ + ((nTri == 2 || nTri == 4 || nTri == 5) ? hLOD : 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:

We are only going to make 1 change to our main.cpp file in this tutorial. This is the addition of the line:

// hField.hLOD = 8;

In our Init function, right before we call the creation of our heightfield. I chose the number 8 because after testing different numbers, I found 8 to give good frame rates, and still look really nice.

And that is it for now. Check out the next tutorial on adding a bump map texture to the heightfield for dynamic lighting on the terrain here *coming soon*. 🙂

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.
146.
147.

    #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.hLOD = 8;

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

#include <iostream.h>

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