The past few weeks I have been working on adding erosion simulation to my planet generator/renderer, which you can read more about in this blog post. I wasn't confident that this could actually be done, but I believe to have some success in this area.
The general idea of the terrain generation is to recursively generate hierarchical FBM noise, transform that noise into some nice looking shapes, then apply edits to that elevation data to implement things like destruction, impact craters, volcanos, and other unique geological features. Then, an erosion algorithm is applied to the result to add nice erosion features.
I used the erosion algorithm from the paper “Fast Hydraulic Erosion Simulation and Visualization on GPU” by Mei et al. (2007). This is relatively simple and fast. The paper does have some problems though that prevent a basic implementation from working well. It does not include slippage weathering to enforce slip angles, and perhaps the biggest problem is that it does not include the fluid depth as part of the sediment transport capacity calculation. This means that a tiny stream and huge river have the same sediment capacity, as long the slope and velocity is the same. LOL, what an oversight. I think the authors mis-read the paper they cite, because the cited paper does indeed include fluid depth (as symbol q = v*h). Fixing this issue means that it is much easier to form ravines and other erosion features, because there is a feedback loop that causes more erosion where there is more fluid.
The hierarchical erosion algorithm works by applying the following steps at each LOD:
- Take parent LOD and interpolate/upscale the part corresponding to the tile. Noise, elevation, fluid depth, sediment, and fluid velocity are all transferred from parent to child.
- The key to making it work well (i.e. fluid flow) across tiles is to copy in the border regions from neighboring parent tiles before upscaling. This creates limited cross-tile communication, and also helps with continuity issues between tiles. There are still some seams between LODs, but these are caused by the smaller LODs being more eroded.
- Add white noise to the upscaled parent noise.
- Convert resulting noise to a fancy terrain shape by applying some math functions to warp the noise.
- Apply erosion from parent, which is stored as the difference between the eroded and uneroded elevation maps. This gets interpolated and added to the higher-resolution noise-derived elevation from the child.
- Apply other terrain edits (e.g. impact craters).
- Erode the terrain elevation, with the simulation state initialized to the interpolated data from the parent. I do only 16 time steps, which takes 2.1 ms per 61x61 tile, with heavy SIMD optimizations.
- Calculate the difference between the eroded and original uneroded terrains. This is stored so that it can be used in the child tiles to apply the erosion recursively.
Some unsolved issues are how to generate better global structure in the drainage networks. Basic noise elevation produces lots of tiny watersheds. I added a hack that deliberately fills in local minima, and this helps quite a bit (producing the image above). It's still not quite what I would like, where all points on a continent drain to an ocean. I can hopefully resolve this global structure problem once I focus more on tectonic simulation, which will produce better looking continents and more realistic drainage systems.
It's hard though, because so few time steps are done that it's impossible to have much in the way of emergent phenomena. Eventually, real continents produce elevations with very few local minima, but this is created over hundreds of millions of years of erosion, not just 16 time steps. So, I have to find some efficient way to enforce this global structure.
Overall though I'm very happy with the results so far. There is still a lot of tuning to the algorithms, and eventually all of the algorithm parameters will be controlled automatically by unique planet conditions (e.g. gravity, precipitation, materials, fluid type). Performance is acceptable. It takes about 2 seconds to generate the scene, but then movement is fairly smooth after that as long as you don't move too fast. Once I make the generation asynchronous and multithreaded, it should work quickly without any stuttering at all. Memory usage is reasonable, just about 300MB total for typical quality settings (including the generated meshes).
Awesome! This puts the procedural universe dream to a new level. \:D/
I would be very curious about the singualrities. I guess you use a cube map tiling, so at 8 points there meet 3 tiles instead 4 as usual. Did this cause you serious additional work or problems?
I consider to do such 2D simulations on the surface of quadrangulated meshes, and expect it would cause noticeable artifacts on singular vertices, forcing me to factor in the parametric stretch into the simulation.
But i rather hope i can get far enough with full but slow 3D simulation, not really having time left to work on another simulator…
The biggest issue i see on your results are the axis aligned rivers.
To avoid axis snapping, i have extended the papers proposals with considering all 8 neighbors of a cell, instead just 4. With proper weighting.
I have tested this with a drop of water, ensuring the waves it causes form a true circle, which it did after the extension.
It gave better quality, but cost almost doubled as well.
Maybe a cheaper hack would be to use noise so some parts are more resistant to erosion than others, forcing the water to take more random paths.
But it's a minor issue. I'm much more excited to see this first attempt of doing proper procedural modeling in real time. It's day and night compared to the old standards based on mixing various noise patterns and hoping something natural pops out.
True progress! :D