The Water of MudRunner

MudRunner is the ultimate version of Spintires, released on PC, and for the first time on consoles.

Like Spintires before it, MudRunner will put players in the driver’s seat of a variety of incredible all-terrain vehicles, venturing across extreme Siberian landscapes with only a map and compass as their guides.

In this blog, we'll be discussing how we render and simulate water in MudRunner. Previously, we discussed how MudRunner renders mud here.

Let’s take an in-game screenshot and deconstruct it:

alt text

Disable SSAO, FXAA, DOF, Sharpen Effect, Color Correction, Motion Blur and Bloom Effect:

alt text

Taking away the layers in the reverse order they are applied:

  1. Particles
    Probably any game has that kind of effect. Particles in MudRunner can be of two types:
  • “billboard” particles: a quad with spray texture, oriented towards the camera
  • “subparticles”: small bits of substance that collide with water, terrain and vehicle wheels. Spawned in large quantities (about 14000 on that screenshot!) and are heavily optimized (spawned and deleted in chunks of 16)

alt text

LUA scripts are used to spawn the particles. Given wheel and chassis positions, velocities, sizes, current water penetration depth and other parameters, scripts compute initial position and velocity for each particle. The advantage of this approach is that it is very customizable – you can tune particles dynamics in any way you want on the fly. The disadvantage is that this process is very technical.

2. Geometric Water Waves

alt text

This layer actually consists of two different effects, have a look at their wireframes:

alt text

Both meshes are generated on the CPU, with a lot of empirical constants involved, and have a very vague connection with the real-world physics of the water.

3. Terrain Wet Decals (will get back to it later)
alt text

And now we are left with the water surface itself:
alt text

Have a look at its wireframe:
alt text


Water in MudRunner can flow in different directions, with varying speed, at any angle, it can have different colour and transparency, and it can get foamy (when vehicles strike it or simply due to its flow intensity). So how does it work?
Any water shader in any game needs to simulate the water waves. There are many different ways to do this. In MudRunner, as you’ve seen above, the water mesh has relatively high tessellation, so our waves are actually geometric. Most simple way (but far from the most realistic) to render the water waves is to mix several instances of texture like this (MudRunner mixes 5 layers while applying different texture coordinates scale and offset):

This texture is actually a procedural noise that you can generate in Photoshop. But different character of that texture yields different character of water surface in a game.

But as will be explained later, if you want your water to flow in any direction at any speed, you will actually need to do the above-mentioned arithmetic 4 times. And if you want your water to be foamy, you will need to do the whole thing with the foam texture too! And that sums down to a lot of shader arithmetic that just won’t work in a real-world scenario. MudRunner’s solution is to pre-mix water waves and water foam texture:

Animated water texture. The only small trick is to make sure those textures can be seamlessly tiled which is easily achieved with some shader math. In the same way, we generate ANIMATED CAUSTICS TEXTURE which we will reference later.

So how do you make your water actually flow? Simple – you scroll the texture coordinates over time. But in which direction, and at what pace? Obviously, that depends on the water direction and water flow speed. In MudRunner, to define water direction and flow speed, map author places “water rivers” in the Level Editor:

Each “river” is a curve with varying width.

When building a level (preparing it for the game), Level Editor generates continuous water surface by mixing all “water rivers” together:

Vertices that define water surface. “Rivers” are not used by the game itself.

Map author uses a brush to paint water flow intensity. So in the end, additionally to the water surface, Level Editor generates that A8R8G8B8 texture for the game to use when it renders water:

Water data per terrain block (terrain blocks are described in the MUD OF MUDRUNNER paper).

So water shader knows water direction and flow intensity (speed), which is actually merely a 2d vector, let’s call it “flowDir”. They key concept to understanding next step is discretization. It means that we pick one of let’s say 16 possible water directions, a direction that is closest to “flowDir” (let’s call it “flowDir1”), and its “neighbor” direction (“flowDir2”), so that
(here and later HLSL code is used)
flowDir = lerp(flowDir1, flowDir2, flowT);

Where “flowT“ is the interpolation parameter of the two directions.
Having “worldPos” as a world position, water shader can now compute texture coordinates for each direction:
float2 angTC1 = float2(
dot(float2(+flowDir1.x, +flowDir1.y), worldPos.xz),
dot(float2(-flowDir1.y, +flowDir1.x), worldPos.xz));
float2 angTC2 = float2(
dot(float2(+flowDir2.x, +flowDir2.y), worldPos.xz),
dot(float2(-flowDir2.y, +flowDir2.x), worldPos.xz));

Which can actually be used to sample animated water texture (having “g_fTime” as animation time, “tcScale” and “tcScrollSpeed” – arbitrary constants):

float2 tcScroll = float2(g_fTime, 0) * tcScrollSpeed;
float4 waves = lerp(
tex2D(g_samWaves, (angTC1 + tcScroll) * tcScale),
tex2D(g_samWaves, (angTC2 + tcScroll) * tcScale), flowT);

We have omitted the water flow speed discretization for simplicity here, but it follows the same idea, and thus you need the 4 samples to the animated water texture (“g_samWaves” sampler in the code above) which were mentioned earlier.

But the water waves need to be shaded. The recipe for that is pretty common, and it basically involves two components: Reflections and Refractions.

alt text
Refractions: objects seen through water. Notice caustics effect that we will get back to later.

alt text
Reflections: objects that are mirrored by the water surface. To render reflections, you can put the camera below the water surface and point it upwards (“reflect the camera”), then use a technique called “oblique clipping plane”. But that only works well if your water surface is “planar” – which is not the case for MudRunner. MudRunner uses a technique called “Screen Space Reflections” (SSR - this technique is well documented in a multitude of sources). MudRunner uses SSR only for water reflections, so its version is highly optimized and very lightweight. One of the optimizations is, we render the “water reflections mesh” (see picture) instead of full-screen quad, so we know the position of the shaded fragment from vertex shader instead of having to do z-unprojection, and don’t need to do SSR pixel shader for the entire screen.


MudRunner uses a very simple algorithm to compute water simulation, it involves two A8R8G8B8 textures. In the same fashion as mud simulation, we build and draw special primitives into a render target texture:

alt text
With a knowledge of wheels and chassis positions, their size and velocities, their current water penetration depth, and water speed and direction, CPU forms various “primitives” and draws them into render target textures, which is then read back by GPU. That is pure empirics, and have very vague connection to real-world physics of the water!

The first texture with simulation data looks like this:
alt text
After the primitives are drawn into that texture, MudRunner performs a “GPU simulation shader” that does a simple propagation of foam, water, height and mud parameters to neighbour texture samples. So two instances of each texture are involved: one for read-back, one for output, and they are swapped each frame.

The second texture only stores “WATER MUD”. Note – there are algorithms that simulate much more realistic water dynamics, but they all require floating point textures for the data, which makes algorithm somewhat more resources-heavy.

Let’s have a look at another peculiar effect. For the water render pass, we generate special low-res texture, which we call “water domain texture”:

alt text
Water domain texture uses a concept, similar to parallax effect, and allows mud (not seen at above picture) and foam to be seen “through” the water. It also stores “water flow speed” that we use to pick the mip level of the “animated caustics texture”. The caustics themselves are simply z-projected when water is drawn. Note that water caustics in MudRunner cannot be seen above the water – drawing the caustics above the water would be a nice improvement in the future!


As referenced in the “MUD OF MUDRUNNER”, there are 4 types of decals in MudRunner:

  • Mud decals, produced by “mud” particles and applied to the terrain.
  • Wet decals, produced by “water” particles and applied to the terrain AND water.
  • Oil slick decals – produced by working vehicle engine and emulates slick on the water surface, applied to water only.
  • Dry oil decals – same as oil decals, but emulates oil marks on dry surfaces, so applied to terrain only.

Let’s have a look at oil decals:

alt text

The render process is similar to rendering mud decals:

alt text
Water needs to output a mask when it’s rendered, so decals can distinguish the surface type they are applied to. Water also writes screen normals that decals use for shading.

Thank you for reading!

last edited by Jellyfoosh

I love game spintires mudrunner mr.pavel thanks 🙂

I am waiting seat adjutments,trackIR 5 and Mirror

sorry my english

This picture is cool!
alt text

Your game is the leader in making games water realistic, keep up good work!

@pavel said in The Water of MudRunner:

Geometric Water Waves

Way too mucht intel for me ^^ but thx
Geometric Water Waves look strange on my game. (like flowing in the wrong direction cause they have no real connection to the tire more to the Wheel flow mesh)

The Wheel flow mesh is more like a Wheel in oil mesh. (to thicc for whater)

very nice write up, my favorite part about the water now though is it actually has more force to it. you actually have to worry about the water pushing you over onto your side now, where before it just kind of gentle pushed you down stream.

@8up-local This becomes clear in the lighthouse challange. xD

@checkmypixel yeah, the challenges were fun, but i do wish there were more of them too.

@8up-local waiting for a map from TATA like H.E.L.L.

Yes, it seems stronger to me too. I like it.

Whoa, that is infinitely more complex than I'd thought at first...
Pavel, you've got some serious skills sir.

and he made this all from scratch. brilliant

Really interesting. Good work Pavel!!!