Animate water using Flow Maps

Hey guys,

I wrote a shader that should animate water using a flow map and a noise map, everything is based on the shader developed by Valve for LF4 and Graphics Runner DirectX Sample

The shader itself is really simple, but for some reason a cannot animate the water. I would really appreciate any help, in fact would be cool having a water animated using a flow map in unity free version.

this is the shader:

Shader "WaterTestShader1" 
{
	Properties 
	{
	    _Color ("Main Color", Color) = (1,1,1,0.5)
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_MainTex2 ("Base (RGB)", 2D) = "white" {}
		_FlowMap   ("FlowMap  (RGB)", 2D) = "white" {}
	    _NoiseMap  ("NoiseMap (RGB)", 2D) = "white" {}
	    
	    _WaveScale ("Wave scale", Range (0.02,0.15)) = .07
	    _WaveSpeed ("Wave speed (map1 x,y; map2 x,y)", Vector) = (19,9,-16,-7)
	}
	SubShader 
    {
    
    Pass 
    {

    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    #include "UnityCG.cginc"

    float4 _Color;
	sampler2D _MainTex, _MainTex2, _FlowMap, _NoiseMap;
    float4 _MainTex_ST;
    float4 _MainTex2_ST;
    
    float3 _WaveSpeed;
    float  _WaveScale;
    
    //Flow map offsets used to scroll the wave maps
    float	flowMapOffset0;
    float	flowMapOffset1;

    //scale used on the wave maps
    float waveScale = 1.0f;
    float halfCycle;

struct v2f 
{
    float4  pos  : SV_POSITION;
    float2  uv0  : TEXCOORD0;
    float2  uv1  : TEXCOORD1;
    float3  col  : COLOR0;
};

v2f vert (appdata_base v)
{
    v2f o;
    o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
    o.uv0 = TRANSFORM_TEX (v.texcoord, _MainTex);
    o.uv1 = TRANSFORM_TEX (v.texcoord, _MainTex2);
    o.col = v.normal * 0.5 + 0.5; //shift the colour in the range [0, 1]
    return o;
}

half4 frag (v2f i) : COLOR
{
    //get and uncompress the flow vector for this pixel
	float2 flowmap = tex2D( _FlowMap, i.uv0 ).rg * 2.0f - 1.0f;
	float cycleOffset = tex2D( _NoiseMap, i.uv0 ).r;
	
	float phase0 = cycleOffset * .5f + flowMapOffset0;
	float phase1 = cycleOffset * .5f + flowMapOffset1;
	
	// Sample normal map.
	float3 normalT0 = tex2D(_MainTex, ( i.uv0 * waveScale ) + flowmap * phase0 ).rgb;
	float3 normalT1 = tex2D(_MainTex2, ( i.uv0 * waveScale ) + flowmap * phase1 ).rgb;
	
	float f = ( abs( halfCycle - flowMapOffset0 ) / halfCycle );
	
    float3 normalT = lerp( normalT0, normalT1, f );
	return float4( normalT, 1.0f );

}
ENDCG

    }
}
Fallback "VertexLit"
}

and this is the script I use to update overtime the scrolling textures:

using UnityEngine;

[ExecuteInEditMode]


public class UVScroller : MonoBehaviour
{
	
   float m_fFlowMapOffset0 = 0.0f;
   float m_fFlowMapOffset1 = 0.0f;
   float m_fFlowSpeed = 0.05f;
   float m_fCycle = 0.15f;
   float m_fWaveMapScale = 2.0f;
   
   public void Update () 
   {
       //update the flow map offsets for both layers
       m_fFlowMapOffset0 += m_fFlowSpeed * Time.deltaTime;
       m_fFlowMapOffset1 += m_fFlowSpeed * Time.deltaTime;
     
       if ( m_fFlowMapOffset0 >= m_fCycle )
            m_fFlowMapOffset0 = 0.0f;

       if ( m_fFlowMapOffset1 >= m_fCycle )
            m_fFlowMapOffset1 = 0.0f;
          
       float _fHalfCycle = m_fCycle*0.5f;
     
       Shader.SetGlobalFloat("fFlowMapOffset0", m_fFlowMapOffset0);
       Shader.SetGlobalFloat("fFlowMapOffset1", m_fFlowMapOffset1);
       Shader.SetGlobalFloat("halfCycle", _fHalfCycle);
       Shader.SetGlobalFloat("fWaveSpeed", m_fFlowSpeed);
       Shader.SetGlobalFloat("fWaveScale", m_fWaveMapScale);
  }
}

At the moment I am not considering the light I just want to make the flow working.

The flow is scrolling,but there is a problem with normals, apparently I do not unpack normals properly and when the texture reached the endCycle is resetting itself

What I want to do is scrolling normal maps and not the actual texture, this means unpack normals and?

Thank you very much in advance

Finally a pic as reference:

For normal maps you want to use the function UnpackNormals() with your normal map texture sampler as an argument.
e.g.

float3 normalT0 = UnpackNormals(tex2D(_MainTex, ( i.uv0 * waveScale ) + flowmap * phase0 ));

yeah I know I fixed that, but I think I have still some problems, the water looks redish because of the colours of the textures, but usually normals look purple or green-ish… I do not know if this is due to the fact i do not have lighting calculations…

Finally, the main problem is that once the scrolling texture reached the end of his cycle, it jumps back to the start and the visual effect is really ugly to watch.

I kinda think it’d be worthwhile using the _Time variable in the shader itself to control the animation. Rather than scripting it all then passing variables in.

But in…

if ( m_fFlowMapOffset1 >= m_fCycle )
            m_fFlowMapOffset1 = 0.0f;

m_fCycle is 0.15

So the offset is hitting 15% of texture offset then jumping back to 0. That’s not going to look graceful - it’s going to keep snapping back.

Perhaps use

m_fFlowMapOffset1 = Mathf.Repeat(m_fFlowMapOffset1 + m_fFlowSpeed * Time.deltaTime, 1);

which will keep it going and wrap it back round to 0 once it’s passed 1. And if you’ve put it, say 0.1 over 1.0, it’ll wrap back round to 0.1. Keeps it running smoothly.

First of all thanks very much for your help man.

I have put that line of code, and yes it is working much better without jumping back to the start, but still there is something i do not understand completely.

The flow seems working fine, it is cool, but first of all is redish, and after it started flowing it changes coloulors, but when the texture is scrolled completely the flow stops for a second a starts again… the effect is not bas as was before, but still not perfect.

Finally I really would like to know why the normal maps look red ish, here is a screenshot:

637388--22784--$waterFlow2.jpg

For a start, you’ve got two returns going on in your shader, which isn’t right.

You want to remove the last one - the i.col one - just to be sure it’s not screwing things up.

Secondly… are your normal maps tagged as normal maps in the inspector? If they’re not, the UnpackNormals() function’s going to screw up the colouring as it relies on them being DXT5nm compressed (which is what tagging them as normal maps in the inspector does).

yea i have fixed the two returns, before opening the thread, i just didnt notice i posted the old code, but the second return should not be called anyway, sry.

I just changed the two normal maps, they are set as normal maps this is the inspector window for them:

The flow now looks much better (the textures are not even squared…btw but i do not think it s a problem right now), but still it is blue-ish now. It like is getting the ambient colour from this textures…now I have seen normal map rendered and I know how they should look like, imho there is something wrong.

637424--22785--$waterFlow3.jpg
637424--22787--$waterFlow4.jpg

Untick generate from height. You’re passing in proper normal maps, not height maps that need converting.

ok man thx very much again!

I think I have pretty much done the shader with your help, but just one problem left.
I think I commit some mistakes when I try to animate the flow, because the areas that should be around objects in the final scene, are not moving at all. But they should move as you can see in the GraphicsRunner web site!

here is the final polished shader code:

Shader "WaterTestShader1" 
{
	Properties 
	{
	    //color
	    _Color ("Main Color", Color) = (1,1,1,0.5)
	    
	    //main water texture
	    _AmbientTex ("Water Base Texture", 2D) = "white" {}
		
		//first main normal map to scroll
		_NrmTex1 ("First scrolling normal map (RGB)", 2D) = "bump" {}
		
		//second main normal map to scroll
		_NrmTex2 ("Second scrolling normal map", 2D) = "bump" {}
		
		//flowmap
		_FlowMap   ("FlowMap  (RGB)", 2D) = "white" {}
	    
	    //noise map
	     _NoiseMap  ("NoiseMap (RGB)", 2D) = "white" {}
	    
	    //colour textures overlay
	    _ColorTextureOverlay ("_ColorTextureOverlay", Range (0.0, 1.0)) = 0.75
	    
	    //fresnel power for reflections
	    _FresnelPower ("_FresnelPower", Range (0.1, 10.0)) = 2.0
	    
	    //ambient power light
	    _Ambient ("_Ambient", Range (0.0, 1.0)) = 0.8
	    
	    //world light direction
        _WorldLightDir("_WorldLightDir", Vector) = (0,0,0,1)
        
        //specular shiness
        _Shininess ("_Shininess", Range (0.1, 60.0)) = 1.0
        
        //spec colours
        _SpecColor ("Spec Color", Color) = (0.5,0.5,0.5,0.5)
	    
	    _WaveScale ("Wave scale", Range (0.02,0.15)) = .07
	    
	   // _WaveSpeed ("Wave speed (map1 x,y; map2 x,y)", Vector) = (19,9,-16,-7)
	}
	
	Category 
	{
		Tags { "Queue"="Transparent" "RenderType"="Transparent" }
		Blend SrcAlpha OneMinusSrcAlpha
		Cull Off
		//ColorMask RGB
		//Lighting off ZWrite Off
		
	SubShader 
    {
    
    Pass 
    {

    CGPROGRAM
    #pragma target 3.0 
    #pragma vertex vert
    #pragma fragment frag
    #include "UnityCG.cginc"

    //main colour
    float4 _Color;
    
    //texsampler
	sampler2D _NrmTex1, _NrmTex2, _FlowMap, _NoiseMap, _AmbientTex;
	
	//texture's st
    float4 _NrmTex1_ST;
    float4 _NrmTex2_ST;
    float4 _AmbientTex_ST;
    float4 _FlowMap_ST;
    
    //wave speed
    float3 fWaveSpeed;
    
    //wave scale
    float  _WaveScale;
    
    //Flow map offsets used to scroll the wave maps
    float	flowMapOffset0;
    float	flowMapOffset1;

    //scale used on the wave maps
    float fWaveScale = 1.0f;
    
    //the half cycle needed to blend the 2 normal maps when one reaches the top the other's contribuition should be 0
    float halfCycle;
    
    //color texture overlay
    float _ColorTextureOverlay;
    
    //reflection power
    float _FresnelPower;
    
    //ambient cols
    float _Ambient;
    
    //spec shiness
    float _Shininess;
    
    //spec colour
    float4 _SpecColor;
    
    //world light direction used to calculate the fresnel law
    float4 _WorldLightDir;

//vertex structure from vertex shader to pixel
struct v2f 
{
    float4  pos         : SV_POSITION;
    float2  uv0         : TEXCOORD0;
    float2  uv1         : TEXCOORD1;
    float3 viewDirWorld : TEXCOORD2;
	float3 TtoW0        : TEXCOORD3;
	float3 TtoW1        : TEXCOORD4;
	float3 TtoW2        : TEXCOORD5;
    float3  col         : COLOR0;
    float2 uv2          : TEXCOORD6;
};

v2f vert (appdata_full v)
{
    //declaration of the evrtex structure
    v2f o;
    
    //determine the position in world space applying the model view projection transform
    o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
    
    //unpack texture coords for the scrolling normal maps (they are the same so there is no need to waste memory for another float2 texcoord variable)
    o.uv0 = TRANSFORM_TEX (v.texcoord, _NrmTex1);
    
    //unpack texture coord for the flow map
    o.uv1 = TRANSFORM_TEX (v.texcoord, _FlowMap);
    
    //unpack tex coords for ambient water base texture
    o.uv2 = TRANSFORM_TEX (v.texcoord, _AmbientTex);
    
    //reverse view dir										
	o.viewDirWorld = -WorldSpaceViewDir(v.vertex);
		
	//calculates the tangent normals for the fresnel law													
	TANGENT_SPACE_ROTATION;
	o.TtoW0 = mul(rotation, _Object2World[0].xyz * unity_Scale.w);
	o.TtoW1 = mul(rotation, _Object2World[1].xyz * unity_Scale.w);
	o.TtoW2 = mul(rotation, _Object2World[2].xyz * unity_Scale.w);		
     
    //returns the output structure
    return o;
}

half4 frag (v2f i) : COLOR
{
    //get and uncompress the flow vector for this pixel
	float2 flowmap = tex2D( _FlowMap, i.uv1 ).rg * 2.0f - 1.0f;
	
	//determines the noise clycle offset from the noise mask
	float cycleOffset = tex2D( _NoiseMap, i.uv1 ).r;
	
	//determines the phase0
	float phase0 = cycleOffset * .5f + flowMapOffset0;
	
	//determines the phase1
	float phase1 = cycleOffset * .5f + flowMapOffset1;
	
	// Sample normal map1
	float3 normalT0 = UnpackNormal(tex2D(_NrmTex1, ( i.uv0 * fWaveScale ) + flowmap * phase0 ));
	
	//Sample normal map2
	float3 normalT1 = UnpackNormal(tex2D(_NrmTex2, ( i.uv0 * fWaveScale ) + flowmap * phase1 ));
	
	//determines the flow function
	float f = ( abs( halfCycle - flowMapOffset0 ) / halfCycle );
	
	//unroll the normals retrieved from the normalmaps
    normalT0.yz = normalT0.zy;	
	normalT1.yz = normalT1.zy;
	
	normalT0 = 2.0f * normalT0 - 1.0f;
    normalT1 = 2.0f * normalT1 - 1.0f;
	
	//determins the resulting normal
    float3 normalT = lerp( normalT0, normalT1, f );
    
    // declare world normal
	half3 worldNormal;

    //calculate the world normals
	worldNormal.x = dot(i.TtoW0, normalT.xyz);
	worldNormal.y = dot(i.TtoW1, normalT.xyz);
	worldNormal.z = dot(i.TtoW2, normalT.xyz);		
					
	// normalize
	worldNormal = normalize(worldNormal);
	i.viewDirWorld = normalize(i.viewDirWorld);
	
	// color
	float4 color = tex2D(_AmbientTex, i.uv2);
	color = lerp(half4(0.6, 0.6, 0.6, 0.6), color, _ColorTextureOverlay);	
    
    // REFLECTION
	float3 reflectVector = normalize(reflect(i.viewDirWorld, worldNormal));
	half4 reflColor = 0.75;
	
	// FRESNEL CALCS
	float fcbias = 0.20373;
	float facing = saturate(1.0 - max(dot(-i.viewDirWorld, worldNormal), 0.0));
	float refl2Refr = max(fcbias + (1.0 - fcbias) * pow(facing, _FresnelPower), 0);	
	
	color.rgba *= (lerp(half4(0.6,0.6,0.6, 0.6), half4(reflColor.rgb,1.0), refl2Refr)); 
					 
	// light
	color.rgb = color.rgb * max(_Ambient, saturate(dot(_WorldLightDir.xyz, worldNormal)));
	
	// a little more spec in low quality to have at least something going on 
	color.rgb += _SpecColor.rgb * 2.0 *  pow(saturate(dot(_WorldLightDir.xyz, reflectVector)), _Shininess); 
	
	//returns the light color computer modulated by the diffuse color		
	return _Color * color;
			
}
ENDCG

}


    }
}
Fallback ""//"VertexLit"
}

and here is a pic as reference:

edit double post sry

I think you might be interested, I’ve created a flow editor - http://algoholic.eu/flow-shader-fix-flow-editor/

Cheers.

Hey I have just checked your page. Look really cool. Many thanks for the flow map editor, really handy.

I’ve introduced some useful changes:

  1. Fast preview/export using triangulated irregular network linear interpolation. This looks a bit worse than Natural Neighbor method but it’s a lot faster.
  2. Thanks to using OpenMP for Natural Neighbor high quality export, it should be approximately N times faster (where N is the number of CPU cores you have)
  3. When loading image, flowed automatically looks for corresponding .ff file and tries to load it
  4. Antialiasing of the drawing scene
  5. Added Repeat Last Export button
  6. Fixed bug in Natural Neighbor which used to cause artifacts near right border of the flow map

http://algoholic.eu/flow-field-editor-update/