PDA

View Full Version : www.GameTutorials.com Height map tutorial



Ultra
04-10-2003, 12:13 AM
Hi all!

I've been trying to convert http://www.gametutorials.com Height map tutorial number 1 to Delphi (it's almost the same as the one on NeHe). However when I compile it I get a framerate of ~2 fps. :(

Needless to say I don't know why and I need some help. Here are the part of the code that seem to mess it up.


//////////////////////////////////////////////////////
// This returns the Height into the Height Map
//////////////////////////////////////////////////////

function Height(pHeightMap: array of Byte; x, y: Integer): Integer;
var
x2,y2: Integer;
begin
x2 := x mod MAP_SIZE;
y2 := y mod MAP_SIZE;

Result := pHeightMap[x2 + (y2 * MAP_SIZE)];
end;

////////////////////////////////////////////////////////////////////////////////////
// This sets the color value for a particular index, depending on the height index
////////////////////////////////////////////////////////////////////////////////////

procedure SetVertexColor(pHeightMap: array of Byte; x, y: Integer);
var
fColor: Single;
begin
fColor := -0.15 + (Height(pHeightMap, x, y) / 256.0);
glColor3f(0, fColor, 0);
end;

////////////////////////////////////////////
// This renders the height map as QUADS
////////////////////////////////////////////

procedure RenderHeightMap(pHeightMap: array of Byte);
var
x1, y1: Integer;
x2, y2, z2: Integer;
// fColor: Single; // never used
begin
x1 := 0; y1 := 0;
if Length(pHeightMap) = 0 then Exit;

glBegin(GL_QUADS);
//x1 := 0;
while x1 < MAP_SIZE do
begin
//y1 := 0; // commented out or testing purposes (it get alot faster from 1/10 fps -> 2 fps)
while y1 < MAP_SIZE do
begin
// bottom left vertex
x2 := x1;
y2 := Height(pHeightMap, x1, y1);
z2 := y1;

SetVertexColor(pHeightMap, x2, z2);

glVertex3i(x2, y2, z2);

// commented out for testing purposes (it gets a bit faster, from 2 fps -> 5 fps)
{ // top left vertex
x2 := x1;
y2 := Height(pHeightMap, x1, y1 + STEP_SIZE);
z2 := y1 + STEP_SIZE;

SetVertexColor(pHeightMap, x2, z2);

glVertex3i(x2, y2, z2);

// top right vertex
x2 := x1 + STEP_SIZE;
y2 := Height(pHeightMap, x1 + STEP_SIZE, y1 + STEP_SIZE);
z2 := y1 + STEP_SIZE;

SetVertexColor(pHeightMap, x2, z2);

glVertex3i(x2, y2, z2);

// bottom right vertex
x2 := x1 + STEP_SIZE;
y2 := Height(pHeightMap, x1 + STEP_SIZE, y1);
z2 := y1;

SetVertexColor(pHeightMap, x2, z2);

glVertex3i(x2, y2, z2); }

y1 := y1 + STEP_SIZE;
end;
x1 := x1 + STEP_SIZE
end;
glEnd;

// reset the color
glColor4f(1.0, 1.0, 1.0, 1.0);
end;

///////////////////////////////////////////////////////////////////////////////////
// This loads a .raw file into an array of bytes. Each value is a height value
///////////////////////////////////////////////////////////////////////////////////

procedure LoadRawFile(strName: String; nSize: Integer; var pHeightMap: array of Byte);
var
f: File;
result: Integer;
begin
if not FileExists(strName) then
begin
MessageBox(0, 'Cannot find the height map!', 'Error', MB_OK);
Exit;
end;

AssignFile(f, strName);
try
{$I-}
Reset(f, 1);
{$I+}

result := IOResult;
if result <> 0 then
begin
MessageBox(0, 'Cannot get data!', 'Error', MB_OK);
end;

BlockRead(f, pHeightMap[0], SizeOf(pHeightMap[0]) * nSize);
finally
CloseFile(f);
end;
end;


Another thing that's a bit strange is that when MAP_SIZE = 1024, I get a stack overflow error. I don't know if these two problems are related.

If this code isn't the problem I can email the whole thing (~120 kB) if someone is kind enough to want to help.

Avatar
04-10-2003, 12:44 PM
Hi

I don't know much about OpenGL but I was wondering if you called the procedure RenderHeightmap each refresh ?

Because, obviously, you recompute the heightmap each frame with that procedure aren't you ? (Sorry I don't know anything on OpenGL ^^)

Can't you stock all your vertices in an array in order to render them ? Instead of calculate them each frame ?

I'm surely making a mistake but I wanted to be sure :)

Bye
Avatar

Ultra
04-10-2003, 05:40 PM
Actually I don't know much about OpenGL either so your probably not much further off than I am.

RenderHeigtMap is called each frame and storing it in an array (or maybe display list) might speed it up. However, it is called each frame in the original c++ code and right now I only want to convert the tutorial, optimization comes later. :wink:

Avatar
05-10-2003, 06:53 AM
Can you post your rendering loop please ?

Ultra
05-10-2003, 04:04 PM
There's not much in it...


////////////////////////////////////////
// This function handles the MainLoop
////////////////////////////////////////

function MainLoop: WPARAM;
var
msg: TMSG;
begin
while True do
begin
if PeekMessage(msg, 0, 0, 0, PM_REMOVE) then
begin
if msg.message = WM_QUIT then
Break;
TranslateMessage(msg);
DisPatchMessage(msg);
end
else
begin
g_Camera.Update;
RenderScene;
end;
end;

DeInit;
Result := msg.wParam;
end;

/////////////////////////////////////////////////
// This function renders the entire screen
/////////////////////////////////////////////////

procedure RenderScene;
begin
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glLoadIdentity;

g_Camera.Look;

RenderHeightMap(g_HeightMap);

SwapBuffers(g_hDC);
end;


Original c++ code:




///////////////////////////////// MAIN GAME LOOP \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This function Handles the main game loop
/////
///////////////////////////////// MAIN GAME LOOP \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*

WPARAM MainLoop&#40;&#41;
&#123;
MSG msg;

while&#40;1&#41; // Do our infinate loop
&#123; // Check if there was a message
if &#40;PeekMessage&#40;&msg, NULL, 0, 0, PM_REMOVE&#41;&#41;
&#123;
if&#40;msg.message == WM_QUIT&#41; // If the message wasnt to quit
break;
TranslateMessage&#40;&msg&#41;; // Find out what the message does
DispatchMessage&#40;&msg&#41;; // Execute the message
&#125;
else // if there wasn't a message
&#123;
g_Camera.Update&#40;&#41;; // Update the camera data
RenderScene&#40;&#41;; // Render the scene every frame
&#125;
&#125;

DeInit&#40;&#41;; // Clean up and free all allocated memory

return&#40;msg.wParam&#41;; // Return from the program
&#125;

///////////////////////////////// RENDER SCENE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This function renders the entire scene.
/////
///////////////////////////////// RENDER SCENE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*

void RenderScene&#40;&#41;
&#123;
glClear&#40;GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT&#41;; // Clear The Screen And The Depth Buffer
glLoadIdentity&#40;&#41;; // Reset The matrix


/////// * /////////// * /////////// * NEW * /////// * /////////// * /////////// *

// Give OpenGL our camera position
g_Camera.Look&#40;&#41;;

// If we pass the g_HeightMap data into our RenderHeightMap&#40;&#41; function it will
// render the terrain in QUADS. If you are going to make any use of this function,
// it might be a good idea to put in an &#40;X, Y&#41; parameter to draw it at, or just use
// OpenGL's matrix operations &#40;glTranslatef&#40;&#41; glRotate&#40;&#41;, etc...&#41;

RenderHeightMap&#40;g_HeightMap&#41;; // Render the height map

/////// * /////////// * /////////// * NEW * /////// * /////////// * /////////// *


SwapBuffers&#40;g_hDC&#41;; // Swap the backbuffers to the foreground
&#125;


Here is the C++ code for the stuff in my first post:



///////////////////////////////// HEIGHT \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This returns the height into the height map
/////
///////////////////////////////// HEIGHT \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*

int Height&#40;BYTE *pHeightMap, int X, int Y&#41;
&#123;
// This is used to index into our height map array.
// When ever we are dealing with arrays, we want to make sure
// that we don't go outside of them, so we make sure that doesn't
// happen with a %. This way x and y will cap out at &#40;MAX_SIZE - 1&#41;

int x = X % MAP_SIZE; // Error check our x value
int y = Y % MAP_SIZE; // Error check our y value

if&#40;!pHeightMap&#41; return 0; // Make sure our data is valid

// Below, we need to treat the single array like a 2D array.
// We can use the equation&#58; index = &#40;x + &#40;y * arrayWidth&#41; &#41;.
// This is assuming we are using this assumption array&#91;x&#93;&#91;y&#93;
// otherwise it's the opposite. Now that we have the correct index,
// we will return the height in that index.

return pHeightMap&#91;x + &#40;y * MAP_SIZE&#41;&#93;; // Index into our height array and return the height
&#125;


///////////////////////////////// SET VERTEX COLOR \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This sets the color value for a particular index, depending on the height index
/////
///////////////////////////////// SET VERTEX COLOR \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*

void SetVertexColor&#40;BYTE *pHeightMap, int x, int y&#41;
&#123;
if&#40;!pHeightMap&#41; return; // Make sure our height data is valid

// Here we set the color for a vertex based on the height index.
// To make it darker, I start with -0.15f. We also get a ratio
// of the color from 0 to 1.0 by dividing the height by 256.0f;
float fColor = -0.15f + &#40;Height&#40;pHeightMap, x, y &#41; / 256.0f&#41;;

// Assign this green shade to the current vertex
glColor3f&#40;0, fColor, 0 &#41;;
&#125;


///////////////////////////////// RENDER HEIGHT MAP \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This renders the height map as QUADS
/////
///////////////////////////////// RENDER HEIGHT MAP \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*

void RenderHeightMap&#40;BYTE pHeightMap&#91;&#93;&#41;
&#123;
int X = 0, Y = 0; // Create some variables to walk the array with.
int x, y, z; // Create some variables for readability
float fColor = 0.0f; // Create a variable to hold our color of the polygon

if&#40;!pHeightMap&#41; return; // Make sure our height data is valid

glBegin&#40; GL_QUADS &#41;; // Render Quads

// Next we actually need to draw the terrain from the height map.
// To do that, we just walk the array of height data and pluck out
// some heights to plot our points. If we could see this happening,
// it would draw the columns first &#40;Y&#41;, then draw the rows.
// Notice that we have a STEP_SIZE. This determines how defined our
// height map is. The higher the STEP_SIZE, the more blocky the terrain
// looks, while the lower it gets, the more rounded it becomes.
// If we set STEP_SIZE = 1 it would create a vertex for every pixel in the height map.
// I chose 16 as a decent size. Anything too much less gets to be insane and slow.
// Of course, you can increase the number when you get lighting in.
// Then vertex lighting would cover up the blocky shape. Instead of lighting,
// we just put a color value associated with every poly to simplify the tutorial.
// The higher the polygon, the brighter the color is.

for &#40; X = 0; X < MAP_SIZE; X += STEP_SIZE &#41;
for &#40; Y = 0; Y < MAP_SIZE; Y += STEP_SIZE &#41;
&#123;
// Get the &#40;X, Y, Z&#41; value for the bottom left vertex
x = X;
y = Height&#40;pHeightMap, X, Y &#41;;
z = Y;

// Set the color value of the current vertice
SetVertexColor&#40;pHeightMap, x, z&#41;;

glVertex3i&#40;x, y, z&#41;; // Send this vertex to OpenGL to be rendered &#40;integer points are faster&#41;

// Get the &#40;X, Y, Z&#41; value for the top left vertex
x = X;
y = Height&#40;pHeightMap, X, Y + STEP_SIZE &#41;;
z = Y + STEP_SIZE ;

// Set the color value of the current vertex
SetVertexColor&#40;pHeightMap, x, z&#41;;

glVertex3i&#40;x, y, z&#41;; // Send this vertex to OpenGL to be rendered

// Get the &#40;X, Y, Z&#41; value for the top right vertex
x = X + STEP_SIZE;
y = Height&#40;pHeightMap, X + STEP_SIZE, Y + STEP_SIZE &#41;;
z = Y + STEP_SIZE ;

// Set the color value of the current vertex
SetVertexColor&#40;pHeightMap, x, z&#41;;

glVertex3i&#40;x, y, z&#41;; // Send this vertex to OpenGL to be rendered

// Get the &#40;X, Y, Z&#41; value for the bottom right vertex
x = X + STEP_SIZE;
y = Height&#40;pHeightMap, X + STEP_SIZE, Y &#41;;
z = Y;

// Set the color value of the current vertice
SetVertexColor&#40;pHeightMap, x, z&#41;;

glVertex3i&#40;x, y, z&#41;; // Send this vertex to OpenGL to be rendered
&#125;
glEnd&#40;&#41;;

// Reset the color
glColor4f&#40;1.0f, 1.0f, 1.0f, 1.0f&#41;;
&#125;


///////////////////////////////// LOAD RAW FILE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This loads a .raw file into an array of bytes. Each value is a height value.
/////
///////////////////////////////// LOAD RAW FILE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*

void LoadRawFile&#40;LPSTR strName, int nSize, BYTE *pHeightMap&#41;
&#123;
FILE *pFile = NULL;

// Let's open the file in Read/Binary mode.
pFile = fopen&#40; strName, "rb" &#41;;

// Check to see if we found the file and could open it
if &#40; pFile == NULL &#41;
&#123;
// Display our error message and stop the function
MessageBox&#40;NULL, "Can't find the height map!", "Error", MB_OK&#41;;
return;
&#125;

// Here we load the .raw file into our pHeightMap data array.
// We are only reading in '1', and the size is the &#40;width * height&#41;
fread&#40; pHeightMap, 1, nSize, pFile &#41;;

// After we read the data, it's a good idea to check if everything read fine.
int result = ferror&#40; pFile &#41;;

// Check if we received an error.
if &#40;result&#41;
&#123;
MessageBox&#40;NULL, "Can't get data!", "Error", MB_OK&#41;;
&#125;

// Close the file.
fclose&#40;pFile&#41;;
&#125;

Alimonster
10-10-2003, 09:58 PM
Wait, am I correct in saying that you're rendering a 1024 x 1024 heightmap without any level-of-detail at all (i.e., brute force?). That's an _awful lot of stuff to draw!_, especially if you're using glVertex instead of vertex arrays (or display lists).

EDIT: Ah, you do have a step size. Ignore the later suggestion about a step size then. Sorry for not paying attention!

Suggestions:

try putting the entire terrain into a display list, then just calling the display list instead of doing that main rendering code. This may give some benefit.

Consider using a step_size of some sort so that you don't draw every vertex -- e.g. skip every fourth vertex, or whatever. Basically reduce the amount of vertices to draw. This will, of course, be a trade-off - as the number of vertices is lowered, the terrain will get coarser.

Try using vertex arrays instead of glVertex/et cetera. This will reduce the sheer amount of function calls (think about it for a 1024 x 1024 terrain - you will be doing 4 * 1024 * 1024 sheer function calls just of glVertex, before anything else is considered! That's 4194304 function calls in your main loop, though you'll have others there as well (e.g. to set the colour).

You can reduce this amount by a lot if you use GL_TRIANGLE_STRIP instead of GL_QUADS. Try doing a triangle strip (either one per row, which is simple, or one for the entire terrain. Preferably the first - I've heard nVidia cards don't like degenerate trianges these days). With a triange strip, you'll only need 1 vertex to create a new triangle after the first one, which can cut out the vertex calls by a massive amount there. If you do this with vertex arrays then you're all set.

Also, if you want to be fancy, you could look into the VBO (vertex buffer objects) extension. This will let you avoid the transfer from system memory to video memory, since you'll be able to put the vertices into video memory directly and use them there. The transfer will be a bottleneck, especially on larger terrains. I've not got around to try out the VBO extension myself, mind you, so I couldn't help you with it (at least, not immediately - I'd gen up if required). You also need a good graphics card and recent drivers to use it.

Another suggestion (much more complex) would be to use some sort of level-of-detail reduction system. At the very least, think about implementing frustum culling if you've not already (probably, since I think the camera class from gametutorials includes that). Once that's done, there are dozens of algos to choose from to reduce the terrain. Google a few and see what pops up. However, lod is probably best implemented later on.

I'm tired. The above might be rubbish - caveat emptor until I get sleep and proof-read it.

Alimonster
10-10-2003, 10:03 PM
Email your code to me (akeys at icscotland dot net) and I'll look at it after the weekend, hopefully.

Ultra
16-10-2003, 12:20 AM
I finally found out the problem! :D

I had to change procedure Whatever(pHeightmap: array of Byte) to procedure Whatever(var pHeightmap: array of Byte) in a couple of places. I couldn't imagine that would make so much of a difference, but after thinking a bit it seems logical.

Now I've converted both tutorial 1 and 2 to delphi (multitexturing in tutorial 3 doesn't seem to like me) and I'm thinking about maybe uploading them somewhere somehow when they're finished.

Ultra
16-10-2003, 12:21 AM
Also sorry for the long time it took me to reply. I've been busy the last couple of days.