Generating Procedural Terrain
Terrain generation is typically a multi-stage pipeline where each stage refines the output of the previous one. The raw heightmap provides the broad geographic structure, erosion adds realistic landforms, biome assignment determines surface appearance, and detail placement adds the vegetation and objects that bring the landscape to life. Each stage can be tuned independently, giving designers fine control over the final result.
Generate the Base Heightmap
The foundation of procedural terrain is a heightmap, a 2D grid where each cell stores an elevation value. The standard approach generates this heightmap using fractal Brownian motion (fBm), which layers multiple octaves of Perlin or simplex noise at increasing frequencies and decreasing amplitudes.
Start with a single octave of low-frequency noise to establish the continental-scale geography. This first layer determines where mountain ranges, lowland basins, and ocean trenches are located. The frequency should be low enough that features span hundreds or thousands of grid cells, creating broad, sweeping landforms rather than dense, cluttered terrain.
Add successive octaves to introduce detail at finer scales. Each octave typically doubles the frequency (lacunarity of 2.0) and halves the amplitude (persistence of 0.5). Four to eight octaves produce terrain with convincing detail from regional geography down to individual rock formations. The total elevation at each point is the sum of all octave contributions.
Post-processing the heightmap can dramatically change the terrain character. Raising values to a power greater than 1.0 (power redistribution) creates flat lowlands with steep mountain peaks, mimicking the sharp elevation gradient found in real mountain ranges. Applying a terrace function quantizes elevation into discrete steps, creating mesa and plateau formations. Clamping negative values to zero and treating them as sea level creates coastal terrain with islands and peninsulas.
For terrain that needs specific geographic features like a central mountain, a river valley, or an island shape, you can combine the noise heightmap with a distance field or gradient function. Multiplying the noise by a radial falloff creates an island. Adding a gaussian ridge along a path creates a mountain range. These designer-controlled shapes blend with the procedural noise to produce terrain that serves gameplay needs while maintaining organic appearance.
Apply Erosion Simulation
Raw noise terrain has a characteristic puffy, pillow-like appearance because the noise function produces smooth gradients without the sharp features created by real geological processes. Erosion simulation transforms this soft output into terrain with carved valleys, sharp ridges, alluvial fans, and the asymmetric slope profiles that make landscapes look convincing.
Hydraulic erosion simulates water flowing downhill across the terrain surface. The algorithm drops virtual water particles at random positions on the heightmap, then traces their paths downhill using the steepest-descent direction at each step. As a water particle flows, it picks up sediment from the terrain surface based on its speed and the slope angle. When the particle slows down (on flatter ground or in depressions), it deposits the carried sediment. Over thousands of particle simulations, river channels form naturally, V-shaped valleys carve into hillsides, and alluvial fans spread at the base of slopes.
Thermal erosion simulates material falling from steep surfaces due to gravity. At each step, the algorithm checks every cell for slope angles exceeding a threshold (the talus angle, typically around 40 degrees). Material from overly steep cells is redistributed to lower neighbors, softening sharp edges and creating scree slopes at the base of cliffs. Thermal erosion produces the gradual slope transitions visible at the base of real cliff faces.
The order and intensity of erosion passes significantly affect the result. Running hydraulic erosion first, then thermal erosion, produces terrain where river valleys have softened edges. Running them in the opposite order produces terrain where water has cut sharply through already-stabilized slopes. Most implementations run both types iteratively, alternating between them, to produce the most realistic results. The number of iterations controls the degree of erosion, from lightly weathered to deeply carved terrain.
For performance-sensitive applications like real-time terrain generation, simplified erosion approximations can achieve similar visual results at lower computational cost. A common shortcut applies a directional blur along the steepest-descent direction, which mimics the visual effect of water erosion without simulating individual particles. Another approach uses pre-computed erosion masks that are applied as texture overlays rather than modifying the actual heightmap.
Assign Biomes and Climate Zones
Biome assignment transforms a monochrome heightmap into a varied, colorful landscape by classifying each point based on environmental conditions. The standard approach uses two primary inputs: elevation (from the heightmap) and moisture (from a separate noise field or rainfall simulation).
Generate a moisture map using a second, independent noise function with different parameters than the elevation noise. This creates moisture patterns that are uncorrelated with the terrain shape, producing varied biome distributions. Alternatively, simulate rainfall by casting water particles from the sky, tracking where they land based on wind direction and terrain shadowing. Mountains that block prevailing winds create rain shadows, producing deserts on their lee side and wet forests on the windward side.
A Whittaker diagram (or biome lookup table) maps the combination of elevation, moisture, and temperature to specific biome types. Low elevation with high moisture produces tropical rainforest or swamp. Low elevation with low moisture produces desert. High elevation with high moisture produces alpine meadow. High elevation with low moisture produces rocky tundra. The lookup table can have as many or as few biome types as your game requires, from a simple four-biome scheme to dozens of distinct ecosystem types.
Temperature typically decreases with elevation (lapse rate) and increases toward the equator (for games with global-scale terrain). Adding a temperature dimension to the biome lookup produces more realistic distributions: cold deserts at high elevation, warm rainforests near the equator at low elevation, and boreal forests at high latitudes with moderate moisture.
Biome boundaries should not follow sharp threshold lines. In reality, biomes transition gradually through ecotones. Blending biome properties across a transition zone of several cells, or using noise to perturb the boundary positions, creates softer, more natural-looking transitions between forest and grassland, desert and savanna, or tundra and alpine.
Place Vegetation and Details
Once biomes are assigned, each point on the terrain needs appropriate surface detail: trees, shrubs, grass, rocks, flowers, and other objects that sell the illusion of a living landscape.
Poisson disk sampling is the preferred method for placing vegetation because it produces natural-looking distributions with minimum spacing between objects. Unlike uniform random placement, which creates awkward clumps and gaps, Poisson disk sampling ensures that no two trees are closer than a specified minimum distance while maintaining an organic, non-grid appearance. The Bridson algorithm generates Poisson disk samples efficiently in O(n) time.
Each biome defines its own set of placement rules. A temperate forest biome might place deciduous trees at high density with occasional clearings. A desert biome might place sparse cacti and rock formations. A swamp biome might place mangrove trees at the water edge with lily pads on the water surface. These per-biome rules ensure that the detail objects reinforce the biome identity rather than contradicting it.
Slope angle and elevation within each biome further refine placement. Trees avoid steep cliff faces where they could not realistically grow. Grass thins out on rocky ridgelines. Flowers concentrate in meadows and along riverbanks. These ecological constraints make the vegetation distribution feel grounded in physical reality, even in a fantasy setting.
For 3D games, level-of-detail (LOD) systems manage the rendering cost of dense vegetation. Distant trees render as billboards or impostor textures. Mid-distance trees use simplified meshes. Only nearby trees use full-detail geometry. The LOD transitions must be smooth enough that the player does not notice objects visibly changing shape, which typically requires cross-fading between detail levels over a transition distance.
Implement Chunk-Based Streaming
Infinite terrain requires a streaming system that generates and loads terrain chunks on demand as the player moves through the world. The world is divided into a grid of fixed-size chunks, each generated independently when the player approaches and unloaded when the player moves away.
Chunk size is a critical design decision. Large chunks reduce the overhead of chunk management but increase the generation time per chunk and the memory footprint of each loaded chunk. Small chunks generate quickly and use less memory individually but require more frequent loading and unloading, and they increase the number of draw calls for rendering. Common chunk sizes range from 16x16 to 256x256 cells, depending on the game's scale and performance targets.
Because noise functions are position-based and stateless, each chunk can be generated independently from just its world coordinates and the master seed. No chunk needs to know what its neighbors contain. This independence enables parallel generation on multiple threads and means chunks can be generated in any order without affecting the output, a property essential for open-world games where the player's movement direction is unpredictable.
Loading priority should be based on distance and direction from the player. Chunks directly ahead of the player in their movement direction should be prioritized over chunks behind them. A ring-based loading pattern generates the nearest unloaded chunks first, expanding outward until all chunks within the view distance are present. Chunks beyond the unload distance are freed from memory, with any player modifications saved to persistent storage before unloading.
Seamless chunk boundaries are crucial. Because each chunk is generated from the same global noise function, the elevation values at chunk borders naturally match. However, operations that depend on neighboring cells, like erosion simulation and normal calculation, need special handling at boundaries. The standard solution is to generate each chunk with a border overlap of one or two cells, compute the dependent operations using the overlap data, then discard the overlap before storing the final chunk.
Procedural terrain generation is a pipeline, not a single algorithm. Each stage, from noise-based heightmaps through erosion simulation, biome classification, vegetation placement, and chunk streaming, builds on the previous one to produce landscapes that are both visually convincing and technically practical for real-time games.