How Water Works in Dwarfcorp
I’ve gotten a lot of questions about how the water system works in DwarfCorp recently, so I thought I’d write a post about it.
Simulating the Water
Water in the real world is made up of millions of tiny particles that flow past each other and form temporary atomic bonds. These little interactions between particles follow simple rules based on electrostatic repulsion and attraction. The net result of all these millions of interactions causes interesting effects from water pouring out of a cup to flowing rivers and waves in oceans. Unfortunately, modeling water at this level is still beyond reach of modern computers running in real-time. So we have to make serious simplifications to make something that looks “okay”.
Like in Minecraft or Dwarf Fortress, the water in DwarfCorp is a “cellular automata” (CA) based system. A “cellular automaton” is a kind of program where cells (or in my case, voxels on the scale of about a meter) have values which are modified by simple rules. These simple rules can lead to some cool behaviors, but doesn’t even come close to simulating real-world water.
CA-based water ranges from extraordinarily complex to fairly simple. In DwarfCorp, the rules are very, very simple. By just having a few of these easy rules, the engine is able to simulate lots and lots of water extremely quickly.
We store a grid of “Water Cells” for each voxel in a small “chunk” of the world. Each cell has a few bits of information: the water level (a byte from 0-255), a “flow” vector, and the fluid type (currently just water and lava).
Each iteration, we loop through all of the water cells which have any water in them and perform the following rules:
- Is the water level below an “evaporation threshold” for its type? If so, subtract water from it.
- Check the cell below the water. If it’s occupied, don’t do anything else. Otherwise, if it is below maximum capacity? If so, move as much water to the cell below as possible.
- If any water remains from steps 1 and 2, check all adjacent cells (4-connected) in order according to direction of this cell’s “flow” vector. Move a random amount of water to each of the cells until we can’t move any more. The “flow” vector of each neighbor becomes the vector from the current cell to the neighbor.
- Set the flow vector to zero.
That’s it! For lava, we just move a slightly smaller amount to each adjacent cell in step 3. This leads to some pretty cool effects, like flowing rivers, waterfalls, basins which fill and empty, and underground channels. Unfortunately, the water in DwarfCorp does not currently support a pressure model (as in Dwarf Fortress), so water never moves *upward*. We’re planning on adding this soon, however.
A lot of people have asked how I render the water, as opposed to simulating it. Water in DwarfCorp features reflections and refraction, waves, subtle texturing, and shorelines. All of this is eye-candy implemented in shaders.
Rendering The Water
The water in DwarfCorp is rendered as a surface mesh that’s generated by averaging the heights together between adjacent cells, and culling away faces that are covered by water or obstacles. If we just rendered this mesh as a textured surface, it would look okay, but we wouldn’t be able to get the kind of effects you see in DwarfCorp screenshots. To do this, we use pixel and vertex shaders.
There are a few effects that are layered on top of each other in DwarfCorp:
- Reflections – To do this, I first estimate the plane that represents the surface of the water. This is done via raycasting and averaging. (That step can lead to ugly artifacts if water is rapidly changing heights…). Then, we simulate a camera perspective below the plane. We then render the entire scene from this perspective, culling out anything below the water plane, and store this rendering in a texture. The pixel shader figures out what color to draw for the reflection by indexing into the reflection texture, and using Fresnel’s Law.
- Refraction – This is identical to reflection, except we don’t put the camera below the water plane, and we render everything *below* (rather than above) the water plane.
- Texturing – Because water with just reflections and refractions looks too perfect, we multiply in a cartoony pixel art texture to the scene.
- Shorelines – Vertices of the water that are adjacent to empty cells get a “shore counter” incremented. The higher the “shore counter”, the more of the “shore shader” is mixed into the final pixel output. The “shore shader” works by thresholding on the function “sin(t + s)” where t is the time in seconds, and s is the “shore counter”, and adding an arbitrary color to the pixel when its above the threshold. The effect is a set of bands which appear and disappear over time.
- Waves – The reflections and refractions are distorted by a normal map representing ripples in the water. Each vertex is also pushed around in the vertex shader by a sinusoidal function over time and position. The effect is water which moves widely up and down, and also has visual distortions.
- Splashes- Particle effects and sounds are created whenever liquid of a certain type moves more than an arbitrary amount.
That’s it! Here’s a video of the liquid simulation system in action!
or here:
http://www.dwarfcorp.com/Vids/WaterTests.mp4
You can play with a prototype of the water simulation in 2D here:
http://www.dwarfcorp.com/Prototypes/Watersim/
How well does your engine handle multiple horizontal planes of water? Imagine a stepped slope where each level contained a strip of water (somewhat like in the video, except with still water instead of the flowing waterfall). Do you end up re-rendering the scene for each plane encountered?
Nope, I only render the plane that gets hit by the ray in the center of the screen. This leads to pretty nasty artifacts in the case you describe. You can see them in the video, unfortunately. I might have a compromise where if water is too far away from the plane, it simply doesn’t render reflections/refractions. Sometimes, the artifacts still look okay.
Yeah I thought I saw something like that with the hollow box demo, and how the reflection suddenly changed when you looked down and back up again between the layers. That seems like a reasonable approximation, actually, for most cases.
This was interesting. I think I read about this on your TIGsource posts.
So I’m guessing you could do snow/rain etc? That’d be kind of neat. Like if a cell is exposed to the sky, and that cell is being snowed on (is it cold enough? is it raining? rain would also be fun) Then you fill the cell with a little snow if it snows long enough? Then if the temperature warms up it can melt and act how water does normally. Then include some kind of natural erosion and you could have fresh water streams and rivers! 😀
It’d be kind of awesome if you made a volcano. Make a blob projectile like the lava, and fire some out of the volcano from time to time. Make the water around it steam like a hot spring, and then let the trees that touch lava burn to black, and maybe make a burnt grass texture. That’d be kind of a scary area.
On second thought constantly running water would probably not be very processor efficient. You’re the expert on that though. Consider volcanoes though? :p
Snow is currently not simulated, but we’re definitely going to look at that. Snow could be considered to be an extremely slow fluid. We already do simulate erosion and weathering during creation of the world. This leads to fjords.
Volcanoes are an awesome idea.
As for constantly running water, the water is already constantly running. It’s invariant to how much water is moving at any time. The only slowdown might be due to the particle effects + sounds.
You guys are wizards.
Man, you gave me a great help, thank you for your explanations 😀
Is this project dead??? Look at the dates…