Why ?
Having a passion for games and creation, i realized that i tended to be more interested in games that provided me with content changing over time. I get easily bored if i have to go through the same content over and over again, so i am always interested in how they address that issue.
Unfortunately, productions often just ignore the issue and design it for a single playthrough, this unfortunately doesn't work great for me and i see myself loose interest before reaching the end.
One of the most successful approach i have seen so far, when done well, is the tile approach (connecting designed level tiles in random order to create a seemingly random world), this unfortunately seems to work best with enclosed environments (dungeons, caves, underground complex etc.), and i haven't seem much of these for open world environment. However they do turn stale after a while when you get used to them and can recognize them.
Rarely some production go fully procedural, however successful ones mostly tend to be low definition, low polygon, which is more forgiving.
So i decided i would try making one myself and figure out what techniques and approach would work for my aspirations.
Defining my project
The target was to get a procedural generation with plausible results, avoiding the identifiable "perlin noise" artefacts, while retaining some control.
This for now is not achievable with AI as each generation would return different results.
To that end i ended up using a mix of voronoi, and variable sized poisson disc sampling combined with some weighted randomizer.
As i need to be able to generate the exact same procedural world over and over again, the use of a strict random seed is a requirement.
World configuration
For the world configuration i create a unity "Script object" that allows me to hold nested script objects with prefabs and other information (such as flaura and fauna) that can then be attached to each biomes, each with different probability rates, poisson disc sampling distance and a range of scale when justified.
That way a grassland can be defined with different grass details, and trees that could be defined as "poisson clusters" rather than "simple poisson" (more on that later).
A meadow would have more tree options with some level of forest matched against a clamped re-ranged world perlin noise.
Generating the terrains
In the current configuration, the size of the world is random, composed of a grid of X Terrain chunks of 1km² (in this sample, the seed produces a 4 x 5 grid, so 20km²).
Execution stack sample
Here is an example of the execution stack i run to generate my world, i can expand on it and add any other step i need as i add more support to the project.
public void Initialize()
{
_world = World.Instance;
_random = World.NewRandomizer();
// Build execution stack
ExecutionStack = new List<Step>() {
DefineBiomePool,
EvaluateBiomePoissonDisc,
EvaluateVoronoi,
EvaluateBiomeTerrainStamping,
EvaluateRocks,
EvaluateRockClusters,
EvaluateTrees,
EvaluateDetails,
EvaluateDetailClusters,
};
// Run
RunNextStep();
}
Evaluating the biomes regions
Biomes distribution
At the base, the world would be composed of different biomes (meadow, highlands, desert, mountain, swamps ... and any other you could need).
First we want to define which biomes will be used, for this we look at the world configuration biome count and run a randomizer to pick X biomes (as defined in the world settings).
These biomes each have an individual weight and size, then we run a variable size poisson disc sampling on the randomized biomes to come up with a distribution.
After that we generate a voronoi on these to define the area of coverage of each biome type, this will allow us later on to run the biome stamping against it's boundaries.
Biomes stamping
For each biome i then run a variable poisson disc sampling for randomized size and orientation of the biome associated stamps (heightmap image), validating their center position against the defined voronoi area, allowing them to overflow over their defined area, resulting in a more organic and natural transition between biomes.
Biome boundaries
From there i compare the height of each biome stamps with other biomes to create a mask of each biome, with a gradient near the edge based on the difference on height between the two, this allows to influence the probability of each biome feature being attributed to the edges, making them blend into each other.
These resulting mask can be used for texture definition too.
In the process some small biomes areas might get eaten be bigger stamp biomes (mountains), but that if part of the randomization.
Populating the biomes
From here on it is all about generating the different elements in the proper order.
Each element will us the previous as a mask to avoid overlapping. So we start by small/sparse features such as rocks and other points of interest to then expand to other flora elements such as trees.
This is done for each terrain based on the biomes present on the terrain, I would generate a poisson disc sampling and then filter it with the biomes masks with computer shaders, to process it faster.
Element definition have poisson disc diameter range as well as a collision range (used for the masking part for subsequent elements, allowing trees to grow near rocks, but not in rocks ...).
Generating rock clusters
Single rocks on their own are pretty rare (maybe for rare minerals), you could start with them, i prefer clusters.
These are distributed with an other variable poisson disc sampling, they could have an increase chance of spawning near/on mountain biomes too or what ever makes sense for your target (for game use i would likely put useful resources here).
Evaluating Trees
Pretty much like with the rocks, i generate a variable poisson disc distribution of the mixed weighted trees for each biome.
However there is an extra step here, where i validate them against a re-ranged perlin noise (to allow for clearings, the perlin noise remapped gradient will naturally scale the trees height and density near the edges too).
And finally they can be filtered against existing masks (previously generated elements such as rocks, you don’t want trees growing through rocks) and biome.
Each of these validation is processed with a computer shader, this greatly helps as some biomes can have over a hundred thousands of tree to process per terrain chunk.
Evaluating Details
Details follow a similar process (poisson disc sampling followed by filtering based on masks), however there are two main types:
-
Forest details: details that we spawn in forests areas (such as dead branches, leaves, mushrooms and so on). These use the same perlin mask as the trees, but filtered against the previous elements masks (rocks, trees, biome) too.
-
Clearing/open space details: details spawning in clearings or grasslands (grass, flowers, stones and so on). In this case we use the inverted perlin from the trees and still filter against previous steps here too (rocks, trees biomes…).
Due to the way Unity terrains work, I process each details defined individually for each biomes, and apply the filtered result to the terrain.
We generate poisson disc sampling maps for each details defined for the biome (dozens, one map for the model of each leafs, mushrooms, pine cones, branches, grass, shrubs, flowers, stones and any other details needed to populate the world).
Each generated against the relevant masks.
Next steps
-
Water (rivers, and lakes), these are the next tricky part in procedural generation as you need to handle elevations (obviously water cannot flow up).
-
Fauna (some would be part of POIs, such as dens) this is definitely required to bring Life to the world, i have started looking into behavior AI for creatures, but this is an other time sink, and has been put on hold.
-
Other Point of Interests (such as ruins, settlements)...
-
Roads, paths and trails.
-
Restrict biome neighboring to certain biome types to avoid opposite biome being spawned need next to each other (desert and swamps next to each other are unlikely).
Sample Gallery
Using this system it is fairly easy to change the prefabs used and have a completely different resulting world style, here are some randomly generate samples of low and high polygon styles:
Special thanks
During my researches and trials i found a few interesting approaches, none of which answered to my needs or target, but were eye openers and source of inspiration in their own way, i recommend checking them out.
-
The Polygon map generation gave really interesting result for low polygon island generation, but it was very limited to that specific aspect and didn't allow me the level of complexity i was looking for, however i discovered the Voronoi and one of it's application here, i recommend checking it out.
-
The Sebastian Lague's Terrain project series from Sebastian Lague was extremely interesting, however it had the limitation inherent to the full procedural approach where you loose some level of control and kind of "feel" the procedural noise (such as perlin) show up. But definitely worth checking out too!
-
Jason Booth's MicroVerse. While i was working on my project, Jason released this little marvel on Unity which i picked up on day 1 out of curiosity, i must say he had a very interesting approach to the problem, his solution provides lots of control, it is probably the best i saw to author terrains in unity, but it doesn't (or did not last time i checked) generate a procedural terrain at run time based on a set of rules, however it made me discover two things fundamental to my project, for which i am grateful: Terrain Stamping and Compute shaders.
-
My friend JD, that pushed me in doing this post.
Resources
- Voronoi
- Delaunay triangulation
- Poisson disc sampling (procedural object placement by Sebastian Lague)
- Sebastian Lague's Terrain project
- Red Blob Games (Polygon map generation, probabilities ... Great overall resources )
- Jason Booth's MicroVerse
- Rowlan Terrain stamping maps