Around The World, Part 26: Biomes

I should be working on gameplay, but I got tired of looking at drab grey terrain, so I decided to beautify it first by adding some vegetation. In a way, this is in line with my plan to add a solid technical foundation for the game before stacking too much gameplay on top, because I’m not sure that the hardware will be able to render all those trees in a huge open world.

Let’s take a look at our starting point:

A rendered mountainous island seen from a bird’s eye perspective. Its surface is a drab dark grey.

(That mountain village is placed a bit unrealistically, but I’ll tackle that later.)

You probably don’t remember from a post almost two years ago, but I already implemented generation of temperature, wind and precipitation patterns, and inferred the local climate from those. That was still in C# on a spherical world though, so I dutifully started porting it all to Rust on a flat world. In the end, I got my algorithms to produce a nice climate map using the Köppen-Geiger scheme, like before:

A map of the actual Earth, with different colours indicating different different climates.

This map uses a common colour scheme, same as Wikipedia. Here’s the real world for reference:

A map of the actual Earth, with the same colour scheme
Source: Wikimedia Commons, CC-BY 4.0

You can see that my attempt is far from perfect – in fact, it’s worse than what I got previously in my C# implementation. This is mostly because I simplified wind patterns, and I haven’t added noise to rain distribution yet either. But looking at the coasts (which is what the player will see), I think we’ve got good variety, which is the most important thing.

The next step was to map Köppen climate classes like “temperate oceanic climate” (Cfb) and “cold-summer Mediterranean climate” (Csc) to a set of trees and plants that could plausibly grow there. And that’s where the real trouble began.

To Köppen or not to Köppen

I had originally chosen the Köppen-Geiger scheme because, and I quote myself:

Köppen designed the system based on his experience as a botanist, so his classification is based on the vegetation we encounter in each climate class. That aligns perfectly with my goals.

So logically, these climate classes should cleanly map to particular biomes, right? I never checked this before, so I checked it now…

… and it’s false.

An awesome Wikipedia user has created the following map of the Earth’s biomes:

A map of biomes of the actual Earth
Source: Berkserker on Wikimedia Commons, CC-BY-SA 3.0

These are biomes I can work with; even if I don’t manage to find any data sources, names like “dry steppe and thorn forest” contain enough information to start drawing something plausible. So the question is: how does this compare to the Köppen map? Which Köppen climate zones map to which biomes?

Of course, being a programmer and map nerd, I wasn’t going to determine this by hand. So I figured out by trial and error that the above map was using the Robinson projection, used GDAL and QGIS to reproject the Köppen map into Robinson as well, aligned the two to the best of my abilities in GIMP, and then wrote some Python code to tally the corresponding pixels in each map. Note that the alignment isn’t perfect, so there will be some noise in the results, especially for Köppen zones that cover only a small part of the world. I’ll paste the full results here in case somebody has a use for it, but don’t bother reading the whole thing:

Af   89.0% tropical rainforest, 8.2% subtropical evergreen forest
Am   84.4% tropical rainforest, 6.3% grass savanna
Aw   29.0% dry forest and woodland savanna, 23.2% tropical rainforest, 23.0% tree savanna, 14.8% monsoon forests and mosaic, 5.7% grass savanna
As   100.0% monsoon forests and mosaic
BWh  44.1% xeric shrubland, 21.1% arid desert, 20.5% semiarid desert, 5.9% dry steppe and thorn forest
BWk  39.3% xeric shrubland, 25.3% dry steppe and thorn forest, 12.8% arid desert, 11.4% montane forests and grasslands, 5.5% semiarid desert
BSh  25.3% grass savanna, 22.8% tree savanna, 19.7% dry forest and woodland savanna, 17.9% dry steppe and thorn forest, 4.8% temperate steppe and savanna
BSk  57.8% temperate steppe and savanna, 10.2% dry steppe and thorn forest, 9.1% Mediterranean vegetation, 9.0% montane forests and grasslands, 6.3% xeric shrubland
Csa  63.9% Mediterranean vegetation, 14.5% temperate steppe and savanna, 11.8% temperate broadleaf forest
Csb  29.1% Mediterranean vegetation, 24.7% montane forests and grasslands, 24.1% temperate broadleaf forest, 9.2% dry steppe and thorn forest, 5.2% temperate steppe and savanna
Csc  38.5% Mediterranean vegetation, 30.8% temperate broadleaf forest, 30.8% montane forests and grasslands
Cwa  44.6% dry forest and woodland savanna, 19.4% temperate broadleaf forest, 14.1% monsoon forests and mosaic, 11.2% subtropical evergreen forest, 3.5% montane forests and grasslands
Cwb  32.3% montane forests and grasslands, 24.6% subtropical evergreen forest, 13.0% dry forest and woodland savanna, 12.2% temperate steppe and savanna, 8.8% tree savanna
Cwc  no matches
Cfa  42.6% temperate broadleaf forest, 25.7% subtropical evergreen forest, 20.7% temperate steppe and savanna, 5.8% dry forest and woodland savanna
Cfb  no matches
Cfc  62.5% taiga, 37.5% temperate broadleaf forest
Dsa  30.3% dry steppe and thorn forest, 29.8% montane forests and grasslands, 28.7% temperate broadleaf forest, 6.2% temperate steppe and savanna
Dsb  76.3% montane forests and grasslands, 13.5% dry steppe and thorn forest, 4.8% temperate steppe and savanna
Dsc  52.2% tundra, 36.0% taiga, 9.9% alpine tundra
Dsd  50.0% alpine tundra, 25.0% tundra, 25.0% taiga
Dwa  82.4% temperate broadleaf forest, 17.5% temperate steppe and savanna
Dwb  46.8% temperate broadleaf forest, 23.0% taiga, 16.3% temperate steppe and savanna, 13.7% montane forests and grasslands
Dwc  61.6% taiga, 17.9% alpine tundra, 15.4% montane forests and grasslands
Dwd  83.0% taiga, 16.0% alpine tundra
Dfa  55.2% temperate steppe and savanna, 44.7% temperate broadleaf forest
Dfb  46.3% temperate broadleaf forest, 44.1% taiga
Dfc  65.9% taiga, 29.3% tundra
Dfd  71.9% taiga, 26.1% tundra
ET   46.0% tundra, 44.8% montane forests and grasslands
EF   83.9% ice sheet and polar desert, 15.2% tundra

The thing that you should be looking at is the first percentage on each line. For example, Köppen class Af is covered for 89% by tropical rainforest – fine, we can just always plant tropical rainforest there. But the majority of climate zones aren’t nearly so clear cut; for example, what to make of BSh (hot semi-arid)? One quarter of it is covered by grass savanna, almost a quarter by tree savanna, and several other biome types as well.

At this point, I concluded that Köppen’s scheme did not fit my purposes as well as I had assumed, so I started looking at alternatives. And that’s when I was reminded of a blog post by the always extremely thorough World Building Pasta: Beyond the Köppen-Geiger Climate Classification System, Part I: Extensions and Alternatives. In it, Pasta describes several alternative climate classification systems with their pros and cons.

BIOME1

The system that caught my attention was the BIOME1 model by Prentice et al., 1992 (PDF). This model fits my purposes very well, because it directly talks about plant types and in which climates they might grow, and offers a very data-driven approach to find out. The core of the paper is this one table:

Screenshot of a table from the Prentice et al. paper

For example, this says that the plant type “cool-temperate conifer” can grow in areas where the minimum temperature is at least −19 °C, the maximum temperature does not exceed 5 °C, there are at least 900 growing degree-days in a year, and the α coefficient is at least 0.65. Let’s unpack this one by one.

Temperatures Tc and Tw are straightforward; Prentice et al. use the monthly average here, which I have readily available from my simulations.

Growing degree-days are also easy to compute, but might warrant some explanation. The idea is that plants do not grow below some particular temperature, here taken as 5 °C, and they grow faster the higher the temperature goes. So to approximate the total amount of growth in a year, for each day we take the number of degrees above the 5 °C threshold, and add those up across the days in a year. For example, a 21 °C day is good for 21 - 5 = 16 growing degree-days, and a 2 °C day doesn’t contribute any growing degree-days at all. Some plants can start growing above 0 °C already, which is what the GDD₀ column is for.

The Priestley-Taylor coefficient α is a bit more complicated to determine. Prentice et al. describe an iterative algorithm that models moisture storage in the soil, but I used a simpler approach: α = precipitation / PET, where PET is the potential evapotransipiration computed using the outdated, but simple, Thornthwaite equation. I compute α separately for each month (assuming 10 in the case where PET is zero, because this empirically gave better results than 1), then take the average over the year.

Finally, there’s the column “D”, the dominance class. This is needed because, even if a plant type could technically grow in a particular climate, other plants are better adapted and will outcompete it. This is why, for example, we won’t see any conifers in tropical rainforest. The rule is: if any plant type with a particular dominance class can grow in a particular climate, all other types with higher-numbered dominance classes are excluded. Plants with the same dominance class can coexist though! This model is simple, straightforward, wrong, and oh so useful to me because it’s easy to implement and efficient to evaluate.

With the list of plant types in hand, the BIOME1 model also specifies how to map these to biomes:

A table mapping sets of plant types to biome names

So the biomes are computed from the plants that can grow, rather than the other way round. Since I only want to plop down some plants, you might wonder why I’d bother with biomes at all. There are two reasons: one, I’d like to plot these on a map so I can compare it to the paper; and two, I want to assign different colours and textures to the base terrain depending on the biome.

Here’s the output computed by Prentice et al. themselves:

Map from Prentice et al. paper

And, using the same colour scheme, here’s mine:

Map of the Earth showing biomes according to Prentice et al.

Again, it’s far from perfect, but this is clearly because of my simplistic climate model and not because of my implementation of the BIOME1 model. Most importantly, all classes are represented here.

Now, not to repeat my previous mistake with the Köppen model, I checked beforehand whether I could find a list of specific plant species for each of these biomes. That more or less worked by patching together various sources, and validating using the amazing Map of Life whether their occurrence actually corresponds to that biome. In the end, I have a list of 2-3 distinct and characteristic species for each plant type, and since most biomes contain more than one plant type, this should give a decent impression of biodiversity on my world.

Colouring terrain

Actually modelling, placing and rendering those plants is for a future blog post, because this one is getting plenty long already. So I’ll tackle the terrain colours first. I manually assigned each biome a plausible colour, and then mapped it to the terrain using the above biome map:

The same mountainous island as before, but now with coloured terrain

It works exactly as designed! We’re looking at cool mixed forest (yellowish green), cool conifer forest (blueish green), tundra (yellowish) and ice/polar desert (white) here. This makes sense, because this island is situated at 56° north, and should have a climate comparable to the Hebrides off the coast of Scotland.

But maybe, just maybe, real biomes don’t have such straight and hard borders? Actual biomes blend into each other. Interpolating between biomes is tricky though, because they are discrete classes. I could instead interpolate the output colours, but I think it would look more natural to add some domain warping using simplex noise instead:

The same mountainous island as before, but now the biomes no longer have straight edges

Better. Never mind that the snow line no longer corresponds properly to height; snow is something I’ll tackle later.

One cheap trick to make every terrain instantly look much more varied is to have nothing grow on steep slopes, so let’s expose grey rock underneath:

The same island again, but now steeper sections are coloured grey again

Finally, terrain near the waterline will not be overgrown, or at least not by the same type of plants, because of tides and spray from the salty sea. So let’s expose the bare rock there too:

The same island yet again, but now coastlines are grey too

The effect is subtle from this perspective, but it helps ground the terrain much better, and also makes the underwater bits less unnaturally green. (Underwater biomes and vegetation is something that should be handled separately, if needed.)

Notice how there’s not a single texture in sight. I’m not great at drawing textures, and I hate UV unwrapping, so I’m trying to get away with a mostly texture-free art style in this game. We’ll see more of that style when I finish modelling my arboretum.