3D Procedural World Generation

procedural terrain & vegetation

In procedural geometry generation, we need two things: algorithms to define the shape & algorithms to construct it. Those algorithms that define the shapes are often based on perlin or simplex noise & other mathematical functions. These often take spatial values (xyz values) in its inputs & return the vertex displacement value.

The other class of algorithms are those that actually perform the construction of geometry (mesh) based on the return values of these algorithms. Those include marching cubes, dual contouring, surface nets & so on.

In this post, I’ll cover the former set of techniques & instead of relying on geometry construction algorithms (such as marching cubes), I’ll simply take a primitive mesh & modify its vertices based on the algorithm; since this is much faster for a real-time video game. However, the major drawback is that, we cannot make complex shapes this way such as caves. But those are not in scope of this post anyway (I’ll probably mention them in another post later).

Terrain techniques

Basic vertex displacement

Typically, we start with a noise function. Noise functions such as perlin or simplex noise provide a good way to add randomization in an organic way. In above image, assume that it is an elevation of terrain; thus the whiter parts are peaks & darker parts are valleys.

for vertex in my_mesh.vertices:
    vertex.y += perlin_noise(vertex.x, vertex.z)

The above pseudocode displaces vertices based on perlin noise in range (-1, 1) typically. If you want to control the elevation of terrain, just multiply a scalar above.

When we are making a big game world, we do not create an entire terrain at once. We only create chunks that are around the player; thus we get player position and create meshes in xz range (player_position – a, player_position + a).

Now that we have multiple chunks of terrain, we do not simply add perlin noise value to vertex.y (as shown above), since all vertices are in local object space & simply doing it will cause all chunks to look same. We first get global position of vertex & then add perlin noise value to it. This way, the terrain will be continuous across different chunks (separate meshes).

for vertex in chunk.vertices:
    vertex.y += perlin_noise(vertex.x + chunk.x, vertex.z + chunk.z)

Different topology functions

Different topology can be achieved by combinations of different mathematical functions applied on bare perlin noise. Below, I’ll mention some of the basic ones. If we are aiming for ridges, dunes, canyons, terraces and so on, we need to pass our noise values through these functions, in order to modify the value.

1. For ridges:

vec3 ridges(vec3 value){ return -abs(value); }

This function inverts the noise after discarding its sign. We are assuming that noise value passed in it is in range(-1, 1).

2. For canyons: we apply power to our noise value to amplify its effect where noise value is greater & flatten the part where noise value is less. Noise values must be in range(0, 1):

vec3 canyons(vec3 noise_value, float power){
return pow(noise_value, power);
}

3. Terraces:

vec3 terrace(vec3 noise_value, float no_of_terraces){
    float step = 1.0 / no_of_terraces;
    return math.floor(noise_value / step) * step
}

4. Craters: these are circular depressions in map.

vec3 crater(vec3 noise, vec3 center, float radius):
    float distance = length(noise.xz - center);
    return noise.y * math.exp(-distance / radius);
}

You can experiment with more functions such as sqrt(), log() and so on.

Multiple topologies in single terrain

In a real video game, you often want some parts of map to be mountains, and some other as ridges. Yet you want some parts to be plains only. For this, you need to use another sample of perlin noise. You will use this new noise for controlling the intensity of different functions across your world.

Note that if you simply use noise value ranges & if-statements to find where to put your mountains or plains, you’ll get discontinuity in terrain where borders meet. So one solution to deal with this is to sample a different noise for each topology’s intensity.

Erosion

After you have created a general terrain using noise & other mathematical functions, it is a good idea to apply effects such as erosion. There are algorithms to approximate erosion that result in a very realistic & better looking terrain. I’ll soon write a comprehensive text on erosion algorithms, but for now, I’ll point to some useful resources:

  1. water erosion on heightmap terrain
  2. Hydraulic Erosion – Sebastian Lague
  3. simulating hydraulic erosion – jobtalle

Oceans

Oceans are parts of less elevation compared to continents. For oceans, we can sample another noise of very less frequency. But make sure to deal with situations such as beaches. Since it is unnatural to have oceans immediately followed by high mountains. We can cope with this problem by decreasing the intensity of all other functions as values approach beaches.

As for ocean water, I have written an ocean shader post where I have made a gerstner wave based ocean. We can place it wherever the ocean is (typically below noise values 0).

Vegetation

Placement of vegetation can be done using poisson disc sampling, which ensures user-defined separation between vegetation is met & trees or bushes do not clump. Clumping typically occurs when we use noise functions.

Moisture, winds, and so on

Another good way is to use existing noises to calculate humidity, dryness, temperature & winds for different parts of your terrain. For example, parts nearer to ocean are more moist, and the most distant parts from oceans are dry. Similarly, wind patterns can be made by considering that wind goes upward in hot dry regions due to heat, which is replaced by wind coming from ocean, thus bringing some moisture with it. This way, your vegetation could be more naturally distributed.

If your game has clouds & rain, the rain distribution can be more realistic.

More reading

Since I did not provide too much in-depth coverage of this topic and this post is still an early draft, I have some good links to share that are useful for procedural generation. Also, in future, I have decided to write about procedural terrain shader that shades diverse textures on a single large terrain.

0. https://www.youtube.com/watch?v=wbpMiKiSKm8&list=PLFt_AvWsXl0eBW2EiBtl_sxmDtSgZBxB3 a basic 101 like this post

1. https://www.redblobgames.com/maps/terrain-from-noise/

2. http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/

3. https://www.youtube.com/watch?v=M3iI2l0ltbE&t=218s marching cubes

4. https://www.youtube.com/watch?v=eaXk97ujbPQ hydraulic erosion

5. https://www.youtube.com/watch?v=7WcmyxyFO7o&list=PLFt_AvWsXl0cbBEB7Z0QSeTCXDRL9IIH0 poisson disc sampling

Table of Contents