Skip to content

Using Noise for Terrain Generation

Noise is a powerful tool that has been applied in many (if not all) subdomains of Computer Graphics. Terrain generation is no exception. In fact, using noise to generate mountains was one of the first application, going back as far as the late eighties with the seminal work of Kenton Musgrave.

For terrains, the core idea is to use the scalar value returned by the noise as the altitude at a given point. You can think of this as a world-space plane, with the elevation of points defined by the noise function. Below is an example of the same noise rendered on a 2D texture and on a 3D displaced plane.

Now let's see how to do that in practice. Our goal is to define the procedural elevation function h using some noise function n. Recall that h takes as input a 2D point in input and return the elevation, or put otherwise it is defined as h(p):R2R.

Note

If you want to dig into the details of how noise functions work internally, please refer to this page.

Our first noise-based terrain

Noise functions are simply dependent on the position at which they are evaluated - they thus belong to the category of procedural functions. They require only very few data to be stored and rely on a procedure to compute a scalar value. If we extend our ProceduralHeightField class introduced in the previous part, we may define the elevation function h again using our Perlin noise:

class ProceduralHeightField extends BaseHeightField {
    constructor(horizontalExtents, verticalExtents) {
        super(horizontalExtents, verticalExtents);
    }

    // Compute the altitude of a 2D point procedurally using Perlin noise
    Elevation(p) {
        return amplitude * perlin.noise(x * freq, y * freq, 0.0);
    }
}

This gives us the following result. Use the mouse to turn around it, and play with the frequency and amplitude!

Controls
Frequency
Amplitude

Notice the impact of amplitude which vertically stretch the terrain, and frequency which changes the horizontal scales of the bumps. These two values are just a way to modify the output of our function according to our needs.

Tip

For the noise algorithm, we rely on Three.js Perlin noise implementation, which was itself used to showcase a procedural terrain here with the code here.

Another common control not showcased here is the number of successive octaves, which is used for doing sum of perlin noise, also called a fractal noise. If we were to write the equivalent formula and code for a fractal sum of noise, it would look like this:

h(p)=i=0oain(pfi)

ai=a0/2i

fi=f0 2i

/**
* Fractal sum of perlin noise.
* p: 2D point with x and y members.
* a0 is the amplitude of the first octave.
* f0 is the frequency of the first octave.
*/
function computeElevation(p) {
    var ret = 0.0;
    var a = a0;
    var f = f0;
    for (var i = 0; i < o; i++) {
        ret += a * perlin.noise(p.x * f, p.y * f, 0.0);
        a *= 0.5;
        f *= 2.0;
    }
    return ret;
}

With a0 the base amplitude, f0 the base frequency. Notice how at the same time, we increase the frequency of successive octaves, and decrease their respective amplitude. This function is also commonly referred to as a turbulence, or fractional brownian motion (fBm for short). Put simply, it is a sum of noise values.

Info

The multipliers for the amplitude and frequency are respectively called the lacunarity and persistence. You may slightly change their values and create terrains with slightly different looks. This is left as an exercise to the reader.

At this point you may not really believe that using noise can help creating realistic terrains. You can play around with the amplitude, frequency and will probably improve the results. However, noise-based terrains still have several limitations.

The issues of Noise

Creating terrains using fractal noise has been done extensively in video games for multiple decades. While you can definitely get a mountainous look for your terrain, getting a proper valley for instance might be more difficult, or even impossible if you limit yourself to simple, uniform fractal noise. We could list some of the limitations of the above example, including:

  • No valleys: you either get a very mountainous terrain or a very flat terrain, but having a mix of both is difficult.

  • You may notice the same pattern is everywhere. This is one interesting property of noise function: self-similarity. In our case, it's also a limitation.

  • Similarly to vallyes, mountains lacks the ridge structure typically found in real mountain ranges. They are just really isolated peaks here.

In the end, it seems that noise is only capable of generating small bumps. Can we alleviate this issue somehow?

Multifractals to the rescue?

Some of these problems can be partially solved by using a more advanced technique called a multifractal noise. The core idea is to modulate the amplitude a of successive octave based on the noise value of previous iterations. This way areas with a low amplitude at iteration k will get a noise value of lower amplitude at iteration k+1, and the opposite will happen for mountainous areas.

Let tk denote the fractal noise at iteration k, this gives us the following maths and code:

tk+1(p)=tk(p)+α(tk(p)) ak+1 n(pfi)t0(p)=a0 n(pfi)

/**
* Weight function for multifractal perlin noise.
* t accumulated turbulence value so far.
* o current iteration number
*/
function alpha(t, o) {
  if (o == 0) {
    return 1.0;
  }
  let frequency = 1.0;
  let H = 0.75;
  for (let i = 0; i < o; i++) {
    frequency *= 2.0;
  }
  return t * Math.pow(frequency, -H);
}

/**
* Multifractal perlin noise.
* p: 2D point with x and y members.
* a0 is the amplitude of the first octave.
* f0 is the frequency of the first octave.
*/
function computeElevation(x, y) {
  var ret = 0.0;
  var f = f0;
  for (var i = 0; i < o; i++) {
    ret += alpha(ret, i) * perlin.noise(x * f, y * f, 0.0);
    f *= 2.0;
  }
  return a0 * ret;
}

The function α:RR computes the weight of the next octave k based on the total accumulated value so far tk.

Below is an example that shows different types of noise for generating terrain shapes, including classical perlin and ridge noise, as well as their multifractal variants. Play around with the different settings to get a feel of how noise behaves.

Controls
NoiseType
PerlinMultifractal
Frequency
Amplitude
Octave

Multifractals do not solve everything. However, the idea of using procedural functions to generate shapes is interesting and can be pushed further: what if we could create specific functions to represent dunes, cliffs, mountains, mountain ridges, rivers, and more? This is the subject of the next chapters, where we will design a library of procedural terrain shapes and combine them together to create more visually interesting and varied terrains.

Going further

You may try to use other kinds of noise to create terrains, for instance Worley (or cellular) noise, its fractal variants, Billowy noise, or try to model different effects such as domain warp.

Associated files

Files associated with this page are available here.