The goal

What we’re after is a highly customizable, expandable, infinite and completely procedural way to generate biomes for our open world.

We’ll be using Simplex noise as a smooth noise map. It’s similar to the more popular Perlin noise, but is faster to compute and has less axis-aligned artifacts.

Simplex noise

It looks like this:

Simplex noise

By itself, it’s not too useful, but we can layer it. With each layer we:

  • Scale it down
  • Reduce it’s influence
  • Mix it with other layers

This gives us a smooth noise map on a large scale, while also having details at a smaller scale. It looks something like this:

Layered simplex noise

Height map

We will use this smooth noise map as the height factor for our biome generator. Each biome has a height value, and the biome with the closest value is chosen.

class Biome : ScriptableObject
{
    public float height;
    public Tile groundTile;
    // ...
}
Biome GetBiome(float height)
{
    Biome closest = null;
    float dist = float.MaxValue;
    foreach (Biome b in biomes)
    {
        float curDist = height - b.height;

        if (curDist < dist)
        {
            closest = b;
            dist = curDist;
        }
    }
    return closest;
}

Height map

This maps any height to one biome, and gives us a starting point for our world.

Revision 1

However, you might notice the problem with this, which is that the biomes in the middle eg. beach generate in rings. To fix this, we’ll add another dimension to our noise map.

Moisture map

To avoid rings which make the map boring and ugly, let’s add another variable just like the height. (In the final version I actually used temperature too, but I’ll leave that out of this blog as visualising things in 3D is a lot harder.)

The moisture map is the same texture, just translated by an amount big enough to look unrelated to height. You could also scale it, that’s up to you.

Instead of using the distance on a 1D scale, we’ll use pythagorean distance on a 2D plane. This is usually what’s used in a Whittaker biome system, and looks like this:

Whittaker Biome System

(image from wikipedia)

Biome GetBiome(float height, float moisture)
{
    // ...
    foreach (Biome b in biomes)
    {
        float curDist = Mathf.Sqrt(
            Mathf.Pow(height - b.height, 2f)
            Mathf.Pow(moisture - b.moisture, 2f)
        );
        // ...
    }
    return closest;
}

With pythagorean distance, this is what we get:

Height/Moisture map

However, just working with straight lines is quite limiting and it would be painful to do smaller adjustments.

Using Gradients as a metric

Instead of using a single 0-1 value to decide the biomes position in the graph, let’s use gradients as the metric rather than distance.

Inspector

// class Biome
public Gradient height;
public Gradient moisture;
Biome GetBiome(float height, float moisture)
{
    // ...
    foreach (Biome b in biomes)
    {
        float curDist = (1f / b.strength) * Mathf.Sqrt(
            Mathf.Pow(1f - b.height.Evaluate(height).grayscale, 2f)
            + Mathf.Pow(1f - b.moisture.Evaluate(moisture).grayscale, 2f)
        );
        // ...
    }
    return closest;
}

Height/moisture map with gradients

This gives us much much more control over the way a biome is picked. Here’s what it looks like:

(Brightness - height, Saturation - moisture)

Revision 2

We no longer have the issue of the beach banding around the ocean, as its broken up by the moisture map, giving us a much more interesting map.

This is all procedural due to only using a procedural noise texture. We can change the seed and get the same map with the same seed every time.