The terrain renderer can be used to create giant terrains from height maps. It uses the most efficient techniques for rendering big detailed terrains available. These are called geo-mip-mapping and texture splatting. Since it's difficult to understand how to use the terrain renderer correctly without knowing how these techniques work, I'm going to explain them now.
So what is geo-mip-mapping? It is a technique used to create different levels of detail for terrains efficiently. The terrain gets split up into a couple of quadratic tiles. Each of these tiles has five different LoDs with a triangle count in the range from 512 to 2. Due to the efficient structure of the LoDs all of them can use the same vertex buffer. The following graphic illustrates how the different LoDs look.
Depending on the height difference of the tile and the distance to the camera, Ultimate 3D will decide which level of detail is to be used for rendering the tile. This extreme variety of the LoDs makes the terrain renderer incredibly efficient. One problem you are confronted with frequently when rendering terrain, is how to get good textures for the terrain. Every part of the terrain should have a different color, but a texture that covers the whole terrain would be either extremely big or extremely undetailed. Neither is desirable. One technique to avoid this is the use of a detail map, but that does not look very good.
For this reason there is a technique called texture splatting. When using texture splatting, you can use as many terrain textures as you want, which will be wrapped over the terrain (wrapping is the same as tiling but for textures). Then you can use alpha maps to define the transparencies of textures at different points. The following graphic illustrates how this works:
Texture splatting gives you the possibility to make your terrain look really detailed and lively. You can use a stone texture for slopes and grass or dirt textures for flat areas. Also, you can put snow on the peaks of your mountains and last but not least you get the possibility to use a separate texture for paths the character can walk on. Note that texture splatting requires support for multi texturing with at least to textures. This means that the return value of GetSimultaneousTextureCount() has to be >=2.
Using the terrain renderer is really easy. The difficult thing is making the diffuse, height and alpha maps (there are programs to do this). But that's not the topic right now. Here you will learn how to create terrain, and how to set up the different textures for it. As with any other object in Ultimate 3D, a terrain requires its own Game Maker object. And again you have to set up a couple of variables before creating the terrain. Here's a list of them:
x, y, z
The origin of the terrain. Every point on the terrain will have an x-, y- and z-coordinate that's higher than the value of the corresponding variable.
height_map
The file that contains the height map you want to use for creating the terrain. This could be, for example, "gfx/TerrainHeightMap.png". The height map can be grayscale, but if you need more precision you can also use all channels to specify the height. The formula, which is used to get from an R, G, B, A color with each value in the range from 0 to 255 to the height of the vertex is z+height*(R*2^24+G*2^16+B*2^8+A)/2^32.
tile_count_x, tile_count_y
The number of tiles along the x-axis and the y-axis. These are two of the three important sizes that give the size of the terrain. They also define how detailed the terrain will be and how much computing time and memory will be required for rendering and creating it. Since a terrain is made up of really complex and detailed geometry you should not use too high values here. 100*100 would already be very big. Its geometry would take about 57 MB RAM.
tile_size
This is the third value that's important for the size of the terrain. It gives the size along the x- and y-axis of each tile in world space. Since tiles are always quadratic this is only one value.
height
The height of the terrain. A black pixel in the height map would result in a terrain z-position that's equal to the value of the variable z
, while a white pixel in the height map would lead to a z-position that's equal to z
+height
.
base_texture
A string giving the file that contains the first texture that is to be applied to the terrain. This texture does not need an alpha map since it does not cover any other texture.
wrap_count_x, wrap_count_y
The wrap count along the x- and y-axis for the first texture. The wrap count is a value that defines how often the texture is to be repeated. Usually the values for these variables are quite similar to those you've set up for the variables tile_count_x
and tile_count_y
.
terrain_lod (optional)
The level of detail factor for the terrain which influences the way Ultimate 3D chooses the LoDs when rendering the tiles. This value is optional so you do not need to set it up. In this case Ultimate 3D will use 1 as LoD factor. If you set up a value lower than 1, higher LoDs will be used, if you enter a higher value, lower LoDs will be used.
light_map_resolution (optional)
The resolution that is to be used for the light map (see below for more information about the light map). By default this is 256. One light map is used for the complete terrain. The value for this variable has to be a power of two.
alpha_map_resolution (optional)
The resolution that is to be used for the alpha map of each tile. By default this is 32. Every terrain tile has its own set of alpha maps, so big values for this variable will result in very strong memory usage. A good value for this usually lies somewhere around 2*light_map_resolution/(tile_count_x+tile_count_y). The value for this variable has to be a power of two.
After setting up these variables you can call CreateTerrain()
once to create the terrain and associate it with the object the function has been called by. Next you have to add the other textures to the terrain. If there are no other textures that need to be applied, you just have to call ApplyTerrainTextures()
. Since terrains are very static objects you do not have to put Step() into the step event this time. However, you've still got to put Destroy() into the destroy event. If you want to use more than one texture using texture splatting, you've got to put a couple of other function calls between the calls to CreateTerrain() and ApplyTerrainTextures(). Since each texture covers the textures that have been applied before it, the order of the function calls matters. Here's the function:
This function adds a texture to the terrain it is called by, using texture splatting:
AddTerrainTexture(
DiffuseMap,
AlphaMap,
WrapCountX, WrapCountY,
AlphaMapChannel
)
|
---|
DiffuseMap
A string giving the file that contains the diffuse map you want to add to the terrain.
AlphaMap
A string giving the file that contains the alpha map you want to use.
WrapCountX, WrapCountY
The wrap count along the x- and y-axis for this diffuse map. The wrap count is a value that defines how often the texture is to be repeated. To make it a bit less obvious that the textures get repeated, you should use different wrap counts for different textures.
AlphaMapChannel
The channel of the alpha map file, from which the information is to be taken. This can be 0 to use the brightness (for grayscale alpha maps), 1 to use the red channel, 2 to use the green channel, 3 to use the blue channel or 4 to use the alpha channel. Thanks to this parameter one alpha map file can contain alpha maps for multiple diffuse maps.
That is all you need to know about creating terrains. With these few functions you can create giant landscapes that look quite detailed. This would not be possible with normal terrain models. So the terrain renderer is a blessing for people who want to make games with many big levels that play in the outside world.
There are also two useful functions to retrieve information about a terrain object at a given position. One can be used to get the height of the terrain at a particular position, the other one can be used to find out which texture is used how strong at a particular position. Here is their description:
This function returns the height of a given terrain object at a given position. It takes very few computing time.
GetTerrainHeightAtPos(
TerrainID,
X, Y
)
|
---|
TerrainID
The ID of the terrain object from which you want to retrieve a height value.
X, Y
The position at which you want the height to be determined.
This function returns the intensity of a given texture at a given position on the given terrain. The return value is a real value in the range from 0 to 1, where 0 means that the texture can not be seen at this position and 1 means that the texture is the only one that can be seen at this position.
GetTextureStrengthAtPosition(
TerrainID,
TextureIndex,
X, Y
)
|
---|
TerrainID
The ID of the terrain object from which you want to retrieve a texture strength.
TextureIndex
The index of the texture you want to get information about. 0 refers to the base diffuse map, 1 refers to the first added texture and so on.
X, Y
The position at which you want the texture strength to be determined.
These two functions are very useful. The first one can be used for simple and very efficient collision detection on terrains, the second one can be used to determine the material, which is present at a particular position. If you want to spread vegetation over a terrain you can use GetTextureStrengthAtPosition(...) to reach that the vegetation is not created on massive rock.
If you have already created some terrain you may have noticed that it does not get lit by the light sources. You should be glad about this; it would look awful if vertex lighting were used because the vertex count of the tiles changes whenever the LoD changes. And since the human eye is very sensitive to sudden changes, that would not look good at all. For this reason terrains use a light map instead. A light map is a texture that gets mapped over the whole terrain with a multiplicative texture operation. Note that this requires support for multi texturing with three textures, so the return value of GetSimultaneousTextureCount() has to be >=3.
Ultimate 3D calculates the light map automatically. You just have to tell it which light sources should be used for creating the light map. Often it's not good to use light sources that move a lot, because recalculating the light map in every single frame takes quite a lot of computing time. Instead you should use only the light sources that do not change rapidly. I recommend using a piece of code that makes Ultimate 3D recalculate the light map frequently (e.g. once a second). Here's the function you need to make Ultimate 3D calculate the light map.
This function makes Ultimate 3D recalculate the light map for the terrain object the function has been called by.
CalculateTerrainLightMap(
LightSourceID1, LightSourceID2, ..., LightSourceID10
)
|
---|
LightSourceID1, LightSourceID2, ..., LightSourceID10
The Game Maker IDs of the light source objects you want to use for calculating the light map. If you put 0 for all of them, Ultimate 3D will use all light sources that are part of the scene.
If the built-in light map calculation function does not satisfy your requirements that's no problem. Ultimate 3D allows you to use a custom light map instead. You create it in any graphics editing software or programs like Earth Sculptor. This way you can use light maps with precomputed shadows or another computation method. If there's a forest somewhere on your terrain you can darken the light map there, to get a shadowed ground. Things like this couldn't be done efficiently without the possibility of setting up custom light maps. So as you can easily see, this function is quite powerful.
This function will set up a texture as custom light map.
SetCustomTerrainLightMap(
TextureIndex
)
|
---|
TextureIndex
The index of the texture, which is to be set up as new terrain light map. If you want to switch back to the automatically calculated light map, you can pass an unused index for this parameter.
In games with heavy weapons you will not get around deforming the terrain sometimes. If a bomb has exploded a crate has to be created. This can actually contribute to the game play, since players may hide in this crate then. Ultimate 3D offers one very simple function for terrain deformation, which leads to the desired results in most cases.
This function raises or lowers the terrain in the surrounding of a given position.
DeformTerrain(
DeformationCenterX, DeformationCenterY,
DeformationRadius,
RaiseLevel
)
|
---|
DeformationCenterX, DeformationCenterY
The origin of the deformation in world space coordinates. This is the position at which the deformation will be maximal.
DeformationRadius
The radius around the deformation center in which the terrain will be deformed.
RaiseLevel
The amount by which the terrain will be raised (positive value) or lowered (negative value).
At the deformation center the terrain is raised or lowered by RaiseLevel
, at a distance bigger than or equal to DeformationRadius
the terrain does not change. Inbetween a cosine curve is used to get a smooth shape. The following graphic illustrates this.
This method leads to very nice spheric crates. It is efficient enough to be called during game play, but it should not be called every step or similarly frequent. Note that it can not lower terrain below z
and can not raise terrain above z+height
. If you are using it you should make sure that there is some space, before these limits are reached.
Often it is necessary to cover the terrain with other textures in some places. For example at places where an explosion has happened a texture with burned dirt should appear. The texturing of the terrain itself can not be changed, but it is possible to create overlays on the terrain, which cover it perfectly and use the desired texture. These overlays are called terrain decals. Ultimate 3D features automatic creation of terrain decals. Terrain decals are model objects and therefore they require their own Game Maker object. In this object you have to set up four variables before the creation of the decal:
terrain_object
The ID of the terrain object on which you want to create a decal.
center_x, center_y
The center position of the decal in world space.
size
The size of the decal. Terrain decals are always quadratic unless they need to be cropped, because they are at the border of the terrain.
After setting up these variables you can call CreateTerrainDecal()
once to create the decal. As usual you need to place Destroy() in the destroy event. Step() can be placed in the step event, but usually this is not necessary since terrain decals are static. Changing the transformation variables is usually not desired. The variables x
, y
, z
, rotx
, roty
and rotz
should equal zero and scalx
, scaly
and scalz
should equal one. Otherwise the terrain will be in the wrong place.
The created model object can be modified just like every other model object. You can use SetMaterialStageTexture(...) to supply it with a texture, you can use SetModelMaterial(...) to change its color and you can even modify its geometry with the model manipulation functions. The object has multiple levels of detail to reach that it matches the shape of the terrain properly, no matter which LoD this part of the terrain is currently using. This works quite well, but still it may happen that parts of the decal get covered by the terrain sometimes. If this is the case you can increase the value of the variable z
a little and call Step(). This will fix the problem. It is up to you to find the perfect value for z
. It depends on the terrain and on the size of the decal.
For the creation of height and alpha maps, I can recommend a very comfortable and user-friendly program called Earth Sculptor. Its use is really intuitive and it comes with a detailed manual. Unfortunately the unregistered version is a bit limited. Once you have designed some terrain in Earth Sculptor you can save it. The program will then save a height map, a compressed alpha map, and a light map. The height map can be used by Ultimate 3D as is. You can also use the light map as a custom light map if you want to. The alpha map can also be used as it gets exported thanks to the AlphaMapChannel parameter of AddTerrainTexture(...).
Other free programs for the creation of terrains are Terragen, World Machine, Terranim8or and L3DT. An explanation of how to export height maps from Terranim8or is available on the Ultimate 3D Community and a tutorial on the use of L3DT by Ruud v A can be found in the Ultimate 3D Wiki.
© Christoph Peters. Some rights reserved.