libnoise logo

A portable, open-source, coherent noise-generating library for C++


Tutorial 5: Generating more complex terrain

In this tutorial, you'll create a complex height map by combining several noise modules together.

Combining noise modules

In libnoise, not all noise modules generate coherent noise. Some noise modules modify the output value from another noise module, while others combine the output values from two or more noise modules in various ways. Using these new types of noise modules, you'll generate a terrain height map that has patches of rough mountainous terrain with flat areas in between.

You'll create five noise modules and combine them as specified in the following diagram:

Noise-module diagram

Each section of this tutorial describes how a specific noise module combines with the other noise modules.

In preparation for this tutorial, open the source file you created in the previous tutorial and erase all the code in it. Add the following code:

#include <noise/noise.h>
#include "noiseutils.h"

using namespace noise;

int main (int argc, char** argv)
{
  utils::NoiseMap heightMap;
  utils::NoiseMapBuilderPlane heightMapBuilder;
  heightMapBuilder.SetSourceModule (mountainTerrain);
  heightMapBuilder.SetDestNoiseMap (heightMap);
  heightMapBuilder.SetDestSize (256, 256);
  heightMapBuilder.SetBounds (6.0, 10.0, 1.0, 5.0);
  heightMapBuilder.Build ();

  utils::RendererImage renderer;
  utils::Image image;
  renderer.SetSourceNoiseMap (heightMap);
  renderer.SetDestImage (image);
  renderer.ClearGradient ();
  renderer.AddGradientPoint (-1.00, utils::Color ( 32, 160,   0, 255)); // grass
  renderer.AddGradientPoint (-0.25, utils::Color (224, 224,   0, 255)); // dirt
  renderer.AddGradientPoint ( 0.25, utils::Color (128, 128, 128, 255)); // rock
  renderer.AddGradientPoint ( 1.00, utils::Color (255, 255, 255, 255)); // snow
  renderer.EnableLight ();
  renderer.SetLightContrast (3.0);
  renderer.SetLightBrightness (2.0);
  renderer.Render ();

  utils::WriterBMP writer;
  writer.SetSourceImage (image);
  writer.SetDestFilename ("tutorial.bmp");
  writer.WriteDestFile ();
  
  return 0;
}

Do not run this code because the compiler will complain that the mountainTerrain object is not declared.

If you carefully examine the source code, you'll note that the color gradient defined by this code differs from the gradient used in the previous tutorial:

Gradient with land coloring only

This gradient has no water values. This is because the flat areas of the terrain height map you'll generate have elevations close to -1 and thus would become flooded. The people living in your valleys would not approve of their homes being flooded.

Generating the rough mountainous terrain

Noise-module diagram with mountainTerrain module selected

To generate the rough mountainous terrain, you'll create a module::RidgedMulti noise module, which generates ridged-multifractal noise. This type of coherent noise is similar to Perlin noise, except it adds "ridges" to the coherent noise. These ridges resemble mountain ranges.

To create this noise module, add the following highlighted code:

int main (int argc, char** argv)
{
  module::RidgedMulti mountainTerrain;

  utils::NoiseMap heightMap;
  utils::NoiseMapBuilderPlane heightMapBuilder;

The default settings of this noise module will work fine.

Now, you're ready to render the rough mountainous terrain. Compile and run the program, then open the tutorial.bmp image file:

Mountainous terrain height map

Note that this terrain height map is covered in mountains. Soon, you'll use another noise module to replace parts of this terrain with flat terrain.

Generating the base values for the flat terrain

Noise-module diagram with baseFlatTerrain module selected

Next, you'll generate the base values for the flat terrain. These values are not directly used in the final terrain. Later in this tutorial, you'll create a noise module that lowers and flattens these values.

To generate these base values, you'll create a module::Billow noise module, which generates billowy, lumpy noise similar to Perlin noise. To create this noise module, add the following highlighted code:

  module::RidgedMulti mountainTerrain;

  module::Billow baseFlatTerrain;

  utils::NoiseMap heightMap;
  utils::NoiseMapBuilderPlane heightMapBuilder;

Next, you'll increase the frequency of this noise module by a factor of 2. This will generate smaller, more numerous lumps in the flat terrain. Add the following highlighted code:

  module::Billow baseFlatTerrain;
  baseFlatTerrain.SetFrequency (2.0);

To view the output of this noise module, modify the following highlighted code:

  utils::NoiseMap heightMap;
  utils::NoiseMapBuilderPlane heightMapBuilder;
  heightMapBuilder.SetSourceModule (baseFlatTerrain);
  heightMapBuilder.SetDestNoiseMap (heightMap);
  heightMapBuilder.SetDestSize (256, 256);

Now you're ready to view the output. Compile and run the program, then open the tutorial.bmp file:

Height map containing the base values for the flat terrain

Generating the flat terrain

After viewing the rendering of the base values, you've probably noticed that this so-called "flat" terrain is anything but flat — it's covered in lumps that are as high as mountains. This is demonstrated in the following graph of the billow-noise module used to generate these base values:

Billow-noise-function graph

In this section, you'll flatten and lower these lumps by connecting the billow-noise module to a new type of noise module called a modifier module:

Noise-module diagram with flatTerrain module selected

Specifically, you'll create a module::ScaleBias modifier module, which applies a scaling factor and a bias to the output of the noise module connected to it. To create this modifier module, add the following highlighted code:

  baseFlatTerrain.SetFrequency (2.0);

  module::ScaleBias flatTerrain;

  utils::NoiseMap heightMap;
  utils::NoiseMapBuilderPlane heightMapBuilder;

A noise module that is used as a source of values for another noise module is called a source module. In this section, you'll use the billow-noise module as the source module for the modifier module. To do this, pass the billow-noise module to the SetSourceModule() method, which is implemented in all modifier modules.

The SetSourceModule() method also requires an index value for the source module. Index values are consecutively numbered starting at zero. Because the module::ScaleBias object only requires one source module, the index value is always zero.

To connect these two noise modules, add the following highlighted code:

  module::ScaleBias flatTerrain;
  flatTerrain.SetSourceModule (0, baseFlatTerrain);

Now that these two noise modules are connected, you can now apply a low scaling factor to the billow-noise module to flatten the lumps. A scaling factor of 0.125 (1/8) will flatten them to 1/8 of their original height. To set this scaling factor, call the SetScale() method. Add the following highlighted code:

  module::ScaleBias flatTerrain;
  flatTerrain.SetSourceModule (0, baseFlatTerrain);
  flatTerrain.SetScale (0.125);

This modifies the output of the billow-noise module as follows:

Scaled billow-noise-function graph

Next, you'll apply a bias to the billow-noise module to lower the lumps. You'll apply a negative bias of 0.75 units, which reduces the elevations of the lumps to approximately -1. To apply this bias, call the SetBias() method. Add the following highlighted code:

  module::ScaleBias flatTerrain;
  flatTerrain.SetSourceModule (0, baseFlatTerrain);
  flatTerrain.SetScale (0.125);
  flatTerrain.SetBias (-0.75);

This modifies the output of the billow-noise module as follows:

Scaled and biased billow-noise-function graph

To view the output of this noise module, modify the following highlighted code:

  utils::NoiseMap heightMap;
  utils::NoiseMapBuilderPlane heightMapBuilder;
  heightMapBuilder.SetSourceModule (flatTerrain);
  heightMapBuilder.SetDestNoiseMap (heightMap);
  heightMapBuilder.SetDestSize (256, 256);

Now you're ready to view the output. Compile and run the program, then open the tutorial.bmp file:

Flat terrain height map

The rendering of this terrain height map now appears noticably smoother. Also note that most of the areas are green; this is because most of the elevations in this height map are near -1.

Generating the terrain-type map

Now that you've generated the mountainous terrain and the flat terrain, you'll now define the areas that these terrains will appear in the final height map. To do this, you'll create a terrain-type map using a Perlin-noise module:

Noise-module diagram with terrainType module selected

Unlike all other noise modules used so far in this tutorial, this Perlin-noise module won't actually contribute elevation values to the final height map. Instead, it's used to define the areas that these terrains will appear in. Areas with positive values will indicate mountainous terrain. Areas with negative values will indicate flat terrain.

To create this terrain-type map, add the following highlighted code:

  flatTerrain.SetBias (-0.75);

  module::Perlin terrainType;
  terrainType.SetFrequency (0.5);
  terrainType.SetPersistence (0.25);

  utils::NoiseMap heightMap;
  utils::NoiseMapBuilderPlane heightMapBuilder;

The low frequency and persistence values will create large areas of similar terrain in the final height map.

The following image shows the terrain-type map. Red areas indicate mountainous terrain and green areas indicate flat terrain.

Terrain-type map

Generating the final terrain

At long last, you'll combine all these noise modules together and generate the final terrain. This involves creating a new type of noise module called a selector module:

Noise-module diagram with finalTerrain module selected

To create a selector module, you'll instantiate a module::Select class. This noise module selects the output value from one of two source modules connected to it. The value it selects is determined by the output value from the control module connected to it.

In this section, you'll use the noise modules that generate the mountainous terrain and the flat terrain as the two source modules. You'll use the noise module that generates the terrain-type map as the control module.

To connect these two source modules to the selector module, add the following highlighted code:

  terrainType.SetPersistence (0.25);

  module::Select finalTerrain;
  finalTerrain.SetSourceModule (0, flatTerrain);
  finalTerrain.SetSourceModule (1, mountainTerrain);

  utils::NoiseMap heightMap;
  utils::NoiseMapBuilderPlane heightMapBuilder;

Note that the index values you gave these two source modules are 0 and 1. These values are very important, as you'll soon see.

Now, you'll connect the control module to the selector module. Add the following highlighted code:

  module::Select finalTerrain;
  finalTerrain.SetSourceModule (0, flatTerrain);
  finalTerrain.SetSourceModule (1, mountainTerrain);
  finalTerrain.SetControlModule (terrainType);

Next, you'll set the range of values that will cause the selector module to output the source module that has an index value of 1. Recall that this is the noise module that generates the mountainous terrain. You'll set up the range of values such that all positive values from the control module will cause the selector module to output the mountainous terrain. Outside of that range, it will output the flat terrain instead. Because you'll need to specify a range of values, you'll set this range from 0 to some large number like 1000. Add the following code:

  module::Select finalTerrain;
  finalTerrain.SetSourceModule (0, flatTerrain);
  finalTerrain.SetSourceModule (1, mountainTerrain);
  finalTerrain.SetControlModule (terrainType);
  finalTerrain.SetBounds (0.0, 1000.0);

To view the output of this noise module, modify the following highlighted code:

  utils::NoiseMap heightMap;
  utils::NoiseMapBuilderPlane heightMapBuilder;
  heightMapBuilder.SetSourceModule (finalTerrain);
  heightMapBuilder.SetDestNoiseMap (heightMap);
  heightMapBuilder.SetDestSize (256, 256);

You're now ready to generate and render the final terrain height map. Once again, compile and run the program, then open the tutorial.bmp file. Compare this rendering to the terrain-type map:

Final terrain height map Terrain-type map

Note that the red (positive) areas of the terrain-type map correspond to the mountainous terrain, while the green (negative) areas correspond to the flat terrain.

However, if you look closely, you will notice that the transitions between the two terrain types are unnaturally harsh. You can smooth this transition by calling the SetEdgeFalloff() method. That method requires a floating-point value that specifies the width of the transition. You'll set this value to 0.125. Add the following highlighted code:

  module::Select finalTerrain;
  finalTerrain.SetSourceModule (0, flatTerrain);
  finalTerrain.SetSourceModule (1, mountainTerrain);
  finalTerrain.SetControlModule (terrainType);
  finalTerrain.SetBounds (0.0, 1000.0);
  finalTerrain.SetEdgeFalloff (0.125);

Compile and run the program, then open the tutorial.bmp file to view the rendering of the final terrain height map.

Final terrain height map with smooth transitions

There's now a nice smooth transition between terrain types.

Conclusion

In this tutorial, you've combined several noise modules together to create a more complex height map. By combining these noise modules in various imaginitive ways, you can create almost any type of terrain.

For an example of what you can do with a large combination of noise modules, take a look at the complex planetary surface page. It contains a program that generates an Earth-sized planet from a combination of over one hundred noise modules, complete with mountains, hills, badlands, and plains. The smallest features on that planet is roughly 7.5 meters.