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
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
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!
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:
/**
* 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
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
Let
/**
* 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
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.
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.