Devlog: How to Add Flowmaps to Unity Water

Unity Pro offers some great looking water in the Water4 package. Edge fade, foam, refraction, reflection (sort of) and surface waves are just some of the features. For our environments in The Wild Eternal however, we needed flowing water and Water4 does not offer this out of the box. Though the water does move, it is a very simple directional movement. For our creeks and rivers, we need real movement to bring them to life. Not to mention we can use the flow direction to affect gameplay!

Consequently, I ventured to add flow map support to the Water4 package instead, and have been met with favorable results. Here’s the result. Note: the modified source can be downloaded at the bottom of this post.

When I set out to make this addition, I did not know how flow maps worked, and a part of why I wanted to add them was also to understand them. It’s a neat thing, and I’m all about understanding neat things. If you are too, I hope my explanation can get you there. If you want a comprehensive quantitative understanding, I suggest reading this great article by Kyle Hayward at Graphics Runner: Animating Water Using Flow Maps.

How Flow Map Shaders Work

It turns out the effect is fairly simple at its core. Like most materials, we want water to be lit according to some bump map texture. The bump map influences both lighting and refraction distortion, and the flow effect is all about animating the bump map.

For every pixel, we decide what normal it should have by sampling the bump map at some uv-coordinate plus some uv-offset. The uv-offset is the key here, and it requires a flow direction, flow speed, and total flow time. In pseudocode:

uvOffset = flowDirection * flowSpeed * totalTime
bump = ReadTexture(bumpMap, uv + uvOffset)

If we do just this, we will get flowing water, it’s really that simple. But there is a very obvious problem with this technique. Because our texture is being panned across the mesh in arbitrary directions for each pixel, the material will become heavily distorted as time goes on. The effect looks good right at the start, but it quickly becomes an ugly mess.

How do we solve this? Since it looks good at the start, we take advantage of that by keeping the uv-offset small. If we cycle the time variable rather than let it grow continually, the offset will never get too big and the distortion will not be noticeable. So when our time variable exceeds, say, 1, we set it back to 0 and let it cycle again. If we do just this, we remove the ugly mess as a possibility, but incur a jolting animation reset every time the time variable cycles over:

So our last problem is to devise a technique to hide the reset. It helps to look at a table to understand how we will accomplish this:

Time Cycle Quality
0 0 START!
0.25 0.25 Looks good
0.5 0.5 Looks good
0.75 0.75 Some distortion
1 1 RESET!
1.25 0.25 Looks good
1.5 0.5 Looks good
1.75 0.75 Some distortion
2 1 RESET!

The effect looks good for the first half of every second, and worse as time goes on until it resets. If we were to add 0.5 to our cycling time variable, the effect will look good for the second half of every second, and poor for the first half. This is what we capitalize on to hide the reset. With two cycling time variables that increase at the same rate, but which are offset in their cycles so that one looks good when the other looks bad, we can compute the bump for each and fade from one to the other accordingly. Lets look at another table:

Time Cycle 1 Quality 1 Cycle 2 Quality 2
0 0 START! 0.5 START!
0.25 0.25 Looks good 0.75 Some distortion
0.5 0.5 Looks good 1 RESET!
0.75 0.75 Some distortion 0.25 Looks good
1 1 RESET! 0.5 Looks good
1.25 0.25 Looks good 0.75 Some distortion
1.5 0.5 Looks good 1 RESET!
1.75 0.75 Some distortion 0.25 Looks good
2 1 RESET! 0.5 Looks good

Since one bump looks good when the other looks bad, its simply a matter of linearly interpolating between the two based on their quality. We want a value which ping-pongs between 0 and 1, in-sync with the quality of the bump results.

selector = 2 * |CyclingTime1 – 0.5|
bump = LinearlyInterpolate(bump1, bump2, selector)

Absolute value is a nice way to implement a ping-pong function. This final table shows how the quality selector evolves. Notice that I have decreased the time increment from 0.25 to 0.1 to show the granular changes in the selector variable:

Time Cycle 1 Quality 1 Cycle 2 Quality 2 Selector
0 0 Looks good 0.5 Looks good (100%) 1
0.1 0.1 Looks good 0.6 Looks good (80%) 0.8
0.2 0.2 Looks good 0.7 Looks good (60%) 0.6
0.3 0.3 Looks good (60%) 0.8 Some distortion 0.4
0.4 0.4 Looks good (80%) 0.9 Some distortion 0.2
0.5 0.5 Looks good (100%) 1 RESET! 0
0.6 0.6 Looks good (80%) 0.1 Looks good 0.2
0.7 0.7 Looks good (60%) 0.2 Looks good 0.4
0.8 0.8 Some distortion 0.3 Looks good (60%) 0.6
0.9 0.9 Some distortion 0.4 Looks good (80%) 0.8
1 1 RESET! 0.5 Looks good (100%) 1

So there we have it! Our final pseudocode looks something like this:

uvOffset1 = flowDirection * flowSpeed * cyclingTime
uvOffset2 = flowDirection * flowSpeed * (cyclingTime + 0.5)
bump1 = ReadTexture(bumpMap, uv + uvOffset1)
bump2 = ReadTexture(bumpMap, uv + uvOffset2)
selector = 2 * |cyclingTime – 0.5|
bump = LinearlyInterpolate(bump1, bump2, selector)

How Flow Maps Work (and how to make them)

For some of us (like me), generating the textures are just as hard, if not harder, than getting the shader working. Flow map textures are a fairly straight-forward information map, but that does not mean they are easy to make. Still, lets understand them first, then see about making them.

For each pixel in the texture, we want to embed the flow direction and flow speed. This can be done just like a normal map — store a vector which has a direction and magnitude — except we only need two components, not three. Pretty easy, right? Unfortunately, just like making normal maps, its very hard to draw vectors like this. Fortunately, a developer named LoTekK has produced (see if you recognize the engine!) a Flow Map Painter program which can be used to generate flow maps by drawing in the direction of flow. Though it has some usability issues, its the best solution I have found so far for free. I would love to hear about alternatives if anyone finds or makes one!

Flowmap Example
An example flow map texture for The Wild Eternal.

Implementation Details

I have not packaged together the perfect product for you. Sorry. I have instead packaged up the implementation that we use in The Wild Eternal. Below are some details about what I have changed in the Water4 package and what I have not:

  • My modifications only extend to the high quality version of the shader. Medium and low quality implementations have not been changed.
  • The vertex displacement effect that comes packaged with Water4 (called Gerstner Displacement) animates trigonometric waves according to some global directions. The flow of these waves is not in accordance with our flow map system, but still looks good alongside it.
  • Tiling is now forced to be square, and foam tiling can be set explicitly, rather than being derived from the bump tiling.
  • The foam can be animated just like the bump map. This is implemented as well. Bump and foam flow speeds are independent of one another.
  • I have changed the foam implementation to have a smoother falloff and to use the alpha channel for alpha blending.
  • Bump size has been made proportional to local flow speed according to the flow map, which produces a satisfying result for both lighting and refractive distortion.
  • The flow map needs to be stretched across the water surface. In The Wild Eternal we use terrains heavily within our scenes, so it is natural for us to unwrap our flow map according to the terrain size. This is how it is implemented. You will need to provide your own  terrain or world-space coordinates in a script.

I am more than happy to answer any questions in the comments section below.

Downloads

17 thoughts on “Devlog: How to Add Flowmaps to Unity Water”

    1. Hi Dustin, sorry the link isn’t working for you. Is the zip file empty? I just downloaded it again myself and see Water4FlowmapSupport.unitypackage inside it.

      Edit: The problem has been resolved. When opening zip files containing unitypackages, OSX unzips the unitypackage as well, leaving a mess of useless things. Placing the unitypackage within a folder stops this: File.zip/File/File.unitypackage.

  1. Hi there .

    Nice done , really great job , thks a lot you saved my time . Works fine with unity 4 Water4 shader , ( even if i have not yet really understood how to strech the flow map correctly , but this is another point ^^) , but i have some troubles porting it to unity 5 . Did you managed to make it work , and if so , how ? ^^
    Thks in advance . vizu
    Ps : My Unity Skills are great as my english level ,

    1. My bad , it works , but i still don’t understand how scaling the flowmap . Can you point me explain me how it works please .

      Thxs

      1. Glad it works Vizu. The “scaling” is fairly straight forward, here’s the important line of code:

        float4 flowmapResult = tex2D(_FlowMap, (VERTEX_WORLD_POSITION.xz / TerrainSize.xz));

        Essentially we are reading the _FlowMap texture at some uv coordinate (a texture coordinate from [0,1]). In my scene, water spans my entire terrain object, so I can convert the [x, z] vertex world position coordinate (VERTEX_WORLD_POSITION.xz) from world-space to uv-space by dividing the world-coordinate by the terrain size.

        So for example, if my terrain (and water mesh) is 300×300 units in the x-z plane, and I am trying to get a water pixel that is at [100, 50] to render with the correct flow, I need to convert [100, 50] into [0, 1] uv-space, so I divide it by the terrain size. [100/300, 50/300] = [0.333, 0.1666]. That would be my uv-coordinate to read the flowmap for my scene.

        How you decide to implement your flowmap, in terms of what world-space it occupies, is up to you. You just need to convert the world-space pixel coordinate into _FlowMap texture uv-space.

        I hope that helps.

  2. I’m trying to implement this into a Unity 5.1 water4advanced but am getting this error.
    Shader warning in ‘FX/Water4’: Upgrade NOTE: unity_Scale shader variable was removed; replaced ‘unity_Scale.w’ with ‘1.0’ at line 1
    It’s worth noting I’m not a coder but might be able to hack my way through with guidance 🙂

  3. hi Scott,

    thanks for the shader addition.
    I have one question. How do you set the terrain size?

    If I apply the shader to one of my meshes, the direction is all in one direction. It looks like an average value of the flowmap is used for all points on my waterplane. So if I one extreme value in 1/4 of the flowmap, I have no movement.

  4. Hi Scott,

    I am trying to implement it into Unity 5.2, but i get some kind of Warning / Error which says:

    “FX/Water4: Upgrade NOTE: unity_Scale shader variable was removed; replaced ‘unity_Scale.w’ with ‘1.0’ at line 1”

    Could you please look it closer, since i have tried almost everything i can, and still couldn’t get it work…

  5. why I got this error?

    Assets/Standard Assets/Editor/Water (Pro Only)/Water4/WaterBaseEditor.cs(187,25): error CS0103: The name `WaterEditorUtility’ does not exist in the current context

  6. This looks so good – thanks for putting the effort into doing the alterations, and explaining the process – I look forward to trying it out on a current project.
    I appreciate it being a drop-in replacement for Water 4 pro – makes it much simpler to implement for a multi-person project than starting from a new concept base.

    Don
    have a great day

Comments are closed.