There are many guides on procedural terrain generation, including my own, but not as many about procedural river generation. On this page I’ll describe the techniques I’ve used, and link to other approaches.
1 River properties#
These are properties I might want to model in my procedural river generators:
- gravity: water flows downhill
- tributaries: smaller rivers merge into larger ones
- distributaries: a river splits into smaller branches
- braiding: a river divides into interconnected streams
- groundwater: water flows underground, resurfacing elsewhere
- endorheic basins: water flows into a closed lake without reaching the ocean
However, I usually implement only gravity and tributaries.
- use triangle diagram from here but generalize this to polygons? so that I can demonstrate with square grids
Water entering a region (via precipitation, springs, or rivers) must exit through rivers, evaporation, groundwater, or vegetation. [this is only mapgen4]
2 Structure#
Let’s start with the main data structures. I’ll first show a square grid and then later others.
Each node is one grid tile. I place the dot in the center of the tiles:
Each edge links two nodes together. On a square grid, I have a choice of allowing movement only in the 4 cardinal directions:
or allowing movement in 8 directions, including 4 diagonals:
3 Water flow#
Water flows downhill. To calculate water flow we need to figure out what’s “downhill”. Let’s start with some elevation. In this map, the west side is mountains and the east side is ocean:
At any place that’s not underwater, we can ask: what direction would a raindrop flow in?
We can check the neighbors of the tile to see which one has the lowest elevation, if any. That’s the direction the raindrop will flow.
Calculating this for all nodes gives us a flow map:
A river will follow that flow map. Here’s an example of a river that flows into the ocean:
Since every node has at most one outflow direction, rivers can join (tributaries) but they can never split (distributaries). Here’s an example of two rivers joining together:
4 Rivers#
In this model, water flows from every location downhill to the ocean:
But that’s too many rivers. Instead, we could start rivers at only some of the points, and let them flow as far as possible:
That’s what I did in mapgen2[1]. I picked random source points (preferring higher elevations) and let rivers flow from those points only.
In mapgen4[2] I picked the rivers differently. I calculated rainfall at every point, and then drew only the larger rivers.
[TODO: need better elevation map to get fewer grid artifacts]
5 Grids#



6 Local minima#
6.1 Ignore#
6.2 Lake#
6.3 Fill#
6.4 Carve#
7 Examples#
7.1 Square
show how it looks on a dense map
7.2 Hexagons
7.3 Irregular grid
One problem with regular grids is the rivers may look too regular. This is partly a problem with the input elevation map. But we can reduce the regularity by using a different graph for the rivers than we do for the map itself. For example, the map might be a square grid, but the rivers might be computed on a subset of grid points, connected together into a Delaunay triangulation.
To ensure that the river flow makes sense, we will also need to assign elevations on the triangle mesh, and then interpolate those back to the square grid.
{ need to show what this looks like }
{I guess I’ll have to calculate triangle centers if I want to draw the tiles…}
8 More features#
- erosion - sediment held in water depends on speed
- sediment deposit - water slows down, drops sediment
- meandering - different rate on inside and outside of curve
- groundwater - most of the rain falling on a region doesn’t directly flow into a river, but instead soaks into the ground, and may emerge elsewhere as a spring which feeds a river
- non-steady-state
- weather - rainfall and snowfall can vary; snow builds up and melts later; rain takes time to go through groundwater and emerge again at a spring
- seasonal - precipitation and evaporation can both vary by season; see
; and that leads to vegetation changes over seasons, like https://old.reddit.com/r/dataisbeautiful/comments/80o1ah/vegetation_intensity_throughout_the_year_for/[3]
- glaciers - this is variation over an even longer period of time
8.1 Infinite map
I think of rivers as a global feature. The amount of water flowing through the Nile in Egypt depends on the rainfall far away in Ethiopia. That means to generate Egypt correctly we need to generate faraway lands, not only Ethiopia to find the water that does come from there, but also potentially farther away, to make sure there’s something blocking water so that it does not come from there.
I think we have to loosen our requirements to get rivers on an infinite map. Every project will have its own tradeoffs.
- we could limit the length of rivers so that we never have to generate too far away
- we could say that rivers don’t always flow downhill
- we could make rivers first and then adjust elevation to match
9 Other approaches#
- rivers then elevation - I didn’t get this working
- what does minecraft do? infinite map requires different solutions ; https://www.alanzucconi.com/2022/06/05/minecraft-world-generation/[4] - edge detection + carving?, biome adjusted
- what does dwarf fortress do? hierarchical? see description https://www.gamedeveloper.com/design/interview-the-making-of-dwarf-fortress[5] but without a lot of details
Are we trying to create a set of good rivers, or a good set of rivers? These two are a little different. Creating a good set, we might want things like:
- ensure the rivers are some distance apart
- ensure the rivers have some variety of lengths and flows