Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411522 Posts in 69377 Topics- by 58431 Members - Latest Member: Bohdan_Zoshchenko

April 28, 2024, 10:17:36 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityTownhallForum IssuesArchived subforums (read only)Tutorials[3D] Generating terrain by using a heightmap
Pages: [1]
Print
Author Topic: [3D] Generating terrain by using a heightmap  (Read 6777 times)
BlueMoon
Level 0
**


View Profile
« on: March 14, 2011, 01:12:11 PM »

[3D] Generating terrain by using a heightmap

This is a translated version of my Dutch tutorial. If you are Dutch, you might prefer to read that one, because it is probably much better formulated than this one.

While Game Maker is mainly a 2D engine, a lot of cool things can be done in 3D. This tutorial learns you how to generate 3D terrain, based on a heightmap. The scripts are pretty fast, since I use trianglestrips instead of trianglelists.
It also defines the normals correctly, which allows you to use lighting.



An example of 3D terrain


Some specifications:
  • GM6+
  • Requires the Pro edition.
Table of contents

  • Table of contents
  • What is a heightmap(and how does it work?)?
  • Making a heightmap
  • Reading a heightmap
  • Drawing the terrain
  • Finding the z-value of a position on the terrain
  • Drawing a texture over the entire terrain, instead of repeating it every cel.
  • Examples
  • FAQ
  • Final Notes



What is a heightmap(and how does it work?)?
A heightmap is a black and white(usually), topdown print of a landscape. This is a simple example:
The heightmap is being read, and converted to an array containing the z-value of each gridpoint. These z-value's are based on the 'value'(Like in Hue, Saturation, Value) of the gridpoint(pixel).
In pseudocode it works like this:

for each pixel
 {
  Height[ x, y] = Value;
  Height[ x, y] /= 255; // 255 == white, the maximum
  Height[ x, y] *= max height; // The maximal height you want
 }


The maximum and minimum are now 0 and 'max height'.
A black pixel represents 0: 0/255*max height = 0.
A white pixel represents 'max height': 255/255*max height = max height.


Making a heightmap
The more creative part. You can make a heightmap by making a black sprite and enthusiasticly start drawing grey pixels on it, but that makes it really hard to get a nice result.
Instead, use a Terrain Editor, such as EarthSculptor(free version is good enough). You can export a heightmap by clicking Export>Something-with-Heightmap.


Reading a heightmap
So how are we going to read the heightmap?

First of all, we have to define two variables: gridsize and gridparts.

gridsize will be the size of a cel in our heightmap. The height/width of a triangle. Just like the gridsize in GM's room editor.

gridparts is the number of cels, horizontal and vertical. Remember: your heightmap image should always be 1 pixel bigger then the number of cels you want, since a pixel represents the height of a corner of a triangle, not a cel itself.
So if you want a 50x50 terrain, use a heightmap of 51x51 pixels.

Here is an image for those of you who didn't understand:

The total size of the terrain is equal to gridsize*gridparts, but the size of the heightmap is gridparts+1.
In my script, the value of gridparts is based on the size of the sprite(instead of manually defining it), and gridsize is based on room_width and gridparts.

So, this is the first part of our script:
Code:
// terrain_create(heightmap)
//
// heightmap: the sprite to be used as heightmap

globalvar gridparts, gridsize;
gridparts = sprite_get_width(argument0)-1;
gridsize = room_width/gridparts;

Now we should read the height value's. First we draw the sprite using draw_sprite(which means you'll have to execute this script before initialising D3D(by calling d3d_start()). Then we'll read the value's and calculate the height.
Code:
draw_sprite(argument0,-1,0,0);
var i, j;
globalvar height;
for ( i=0; i<=gridparts; i+=1) {
 for ( j=0; j<=gridparts; j+=1) {
  height[ i, j] = color_get_value( draw_getpixel( i, j) )/255 * argument1;
 }
}
As you can see, I added argument1. This is the maximum height('white'). Our script is now:
Code:
// terrain_create(heightmap,maxheight)
//
// heightmap: the sprite to be used as heightmap
// maxheight: the maximal height of the terrain

globalvar gridparts, gridsize;
gridparts = sprite_get_width(argument0)-1;
gridsize = room_width/gridparts;

draw_sprite(argument0,-1,0,0);
var i, j;
globalvar height;
for ( i=0; i<=gridparts; i+=1) {
 for ( j=0; j<=gridparts; j+=1) {
  height[ i, j] = color_get_value( draw_getpixel( i, j) )/255 * argument1;
 }
}
Now we have the height value's. But we still don't have a model to draw. So now we are going to construct a model using trianglelists:
Code:
globalvar terrain;
terrain = d3d_model_create();

for ( j=0; j<gridparts; j+=1)
 {
  d3d_model_primitive_begin(terrain,pr_trianglestrip)
  for ( i=0; i<=gridparts; i+=1)
   {
    terrain_get_normal(i*gridsize,j*gridsize+gridsize);
    d3d_model_vertex_normal_texture(terrain,i*gridsize,j*gridsize+gridsize,height[i,j+1],global.xx,global.yy,global.zz,i,j+1);
    terrain_get_normal(i*gridsize,j*gridsize);
    d3d_model_vertex_normal_texture(terrain,i*gridsize,j*gridsize,height[i,j],global.xx,global.yy,global.zz,i,j);
   }
  d3d_model_primitive_end(terrain);
 }
I used another script, terrain_get_normal. This scripts calculates the normals, so your models get shaded correctly when Lighting is enabled. Here it is:
Code:
// terrain_get_normal(x,y)
// the result is saved in global.xx, global.yy and global.zz
globalvar gridsize;
var d;
global.xx = terrain_get_z(argument0-gridsize,argument1)-terrain_get_z(argument0+gridsize,argument1);
global.yy = terrain_get_z(argument0,argument1-gridsize)-terrain_get_z(argument0,argument1+gridsize);
global.zz = gridsize*2;
d = sqrt(sqr(global.xx)+sqr(global.yy)+sqr(global.zz));
global.xx /= d;
global.yy /= d;
global.zz /= d;
This script also uses another scipt, terrain_get_z. This will be explained later in this tutorial.

The final script:
Code:
// terrain_create(heightmap,maxheight)
//
// heightmap: the sprite to be used as heightmap
// maxheight: the maximal height of the terrain

globalvar gridparts, gridsize;
gridparts = sprite_get_width(argument0)-1;
gridsize = room_width/gridparts;

draw_sprite(argument0,-1,0,0);
var i, j;
globalvar height;
for ( i=0; i<=gridparts; i+=1) {
 for ( j=0; j<=gridparts; j+=1) {
  height[ i, j] = color_get_value( draw_getpixel( i, j) )/255 * argument1;
 }
}

globalvar terrain;
terrain = d3d_model_create();

for ( j=0; j<gridparts; j+=1)
 {
  d3d_model_primitive_begin(terrain,pr_trianglestrip)
  for ( i=0; i<=gridparts; i+=1)
   {
    terrain_get_normal(i*gridsize,j*gridsize+gridsize);
    d3d_model_vertex_normal_texture(terrain,i*gridsize,j*gridsize+gridsize,height[i,j+1],global.xx,global.yy,global.zz,i,j+1);
    terrain_get_normal(i*gridsize,j*gridsize);
    d3d_model_vertex_normal_texture(terrain,i*gridsize,j*gridsize,height[i,j],global.xx,global.yy,global.zz,i,j);
   }
  d3d_model_primitive_end(terrain);
 }


Drawing the terrain
All what's left is drawing the terrain:
Code:
// terrain_draw(tex)
//
// tex: the texture to be applied to the model
//
// Note: use background_get_texture(tex) on the texture first.

texture_set_repeat(true);
d3d_model_draw(terrain,0,0,0,argument0);
(this is probably the most complicated script in the entire tutorial)


Finding the z-value of a position on the terrain
But we're not finished yet. It would be nice if you were able to make your character(or whatever) stand 'on' the heightmap/

We will need another script for that. Maybe you remember we made a global array height. I did not var(var height;) it on purpose, because we'll be using it now.

First we determine on which cel we are:

var gridx, gridy;
gridx=floor(argument0/gridsize)
gridy=floor(argument1/gridsize)


Then we look where on the cel the position is:

var offsetx, offsety;
offsetx= argument0-gridsize*gridx
offsety= argument1-gridsize*gridy


Then we request the z-positions of the corners of the cel:

var z1, z2, z3, z4;
z1=height[gridx,gridy]
z2=height[gridx+1,gridy]
z3=height[gridx+1,gridy+1]
z4=height[gridx,gridy+1]


And using these variables we calculate the z:

var zz;
if offsetx>offsety
 zz=z1 - offsetx*(z1-z2)/gridsize - offsety*(z2-z3)/gridsize
else
 zz=z1 - offsetx*(z4-z3)/gridsize - offsety*(z1-z4)/gridsize


The final script:
Code:
// terrain_get_z(x,y)
//
// x: the x-position
// y: err..

globalvar gridparts, gridsize, height;
var gridx, gridy, offsetx, offsety, z1, z2, z3, z4, zz;

gridx = max(0,min(gridparts-1,floor(argument0/gridsize))); //min/max stuff so you won't get errors if the position is outside the terrain
gridy = max(0,min(gridparts-1,floor(argument1/gridsize)));

offsetx = argument0-gridsize*gridx;
offsety = argument1-gridsize*gridy;

z1=height[gridx,gridy];
z2=height[gridx+1,gridy];
z3=height[gridx+1,gridy+1];
z4=height[gridx,gridy+1];

if offsetx>offsety
 zz=z1 - offsetx*(z1-z2)/gridsize - offsety*(z2-z3)/gridsize;
else
 zz=z1 - offsetx*(z4-z3)/gridsize - offsety*(z1-z4)/gridsize;

return zz;


Drawing a texture over the entire terrain, instead of repeating it every cel.
To do this, replace the lines with the d3d_model_vertex_normal_texture stuff by:
Code:
for ( j=0; j<gridparts; j+=1)
 {
  d3d_model_primitive_begin(terrain,pr_trianglestrip)
  for ( i=0; i<=gridparts; i+=1)
   {
    terrain_get_normal(i*gridsize,j*gridsize+gridsize);
    d3d_model_vertex_normal_texture(terrain,i*gridsize,j*gridsize+gridsize,height[i,j+1],global.xx,global.yy,global.zz,i/gridparts,(j+1)/gridparts);
    terrain_get_normal(i*gridsize,j*gridsize);
    d3d_model_vertex_normal_texture(terrain,i*gridsize,j*gridsize,height[i,j],global.xx,global.yy,global.zz,i/gridparts,j/gridparts);
   }
  d3d_model_primitive_end(terrain);
 }
}


Examples
Here are two examples how to use this ingame:

Example 1
Download
This example shows the basics.

Example 2
Download
This example shows how to make the player walk on the terrain, and how to deal with gravity.


FAQ
A short FAQ.
(got a question? Feel free to ask)

Q: My game crashes when loading the heightmap.
A: Your heightmap is probably to big. The bigger the better of course, but loading time increases exponentially. Use a size of maximal 51*51, 31*31 preferred.

Q: My game lags when drawing the terrain.
A: Your texture is probably to big. Use maximal 1024*1024.

Q: My terrain isn't drawn.
A: Well, the scripts work. Download one of the example's, and look for differences between your and my code.


Final Notes
Heightmap-based terrain can be darn nice, but also darn slow.
Enjoy, but use with care.


Sincerly,
BlueMoon
« Last Edit: March 16, 2011, 05:39:48 AM by BlueMoon » Logged

YYG
Twitter
Please note: My native language is Dutch, therefore my English might sound a little awkward.
krasimir
Level 0
**



View Profile WWW
« Reply #1 on: March 16, 2011, 03:28:21 AM »

Thanks for the translation. Height maps are a love-hate thing for me as I always mess them up.
Logged

BlueMoon
Level 0
**


View Profile
« Reply #2 on: March 16, 2011, 05:43:30 AM »

No problem Smiley
Had to translate them anyway.

Fixed some grammatical mistakes and Dutch comments, by the way.
Logged

YYG
Twitter
Please note: My native language is Dutch, therefore my English might sound a little awkward.
nikki
Level 10
*****


View Profile
« Reply #3 on: March 16, 2011, 12:37:52 PM »

Goed bezig !

Nice one Beer!
Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic