Wednesday, 10 February 2016

Skyship Aurora Unity Water Tutorial



DOWNLOAD complete project:
https://www.dropbox.com/s/0gdq68f9axi2f7l/LongDistanceWater.zip?dl=0

We gonna make water like this:



Now there is a lot of different waters for Unity. Some of them are very awesome and looks extremely real. But water is often suitable for first person human look and also sources and shaders are extremely complicated for modify and learning. For such game like Skyship Aurora we need not very complicated but fast water with reflection and waves. I've looked at existed solutions and found only HORIZON[ON]. Quite awesome thing for long distance rendering, but at same time quite complicated and hard to modifying.  Best of all works all together with built-in terrains rendering system. So  I've decided to make my own water again :). Once I've did it, so I have a vector to move on. Let's check previous water tutorial and modify it for long distance:

http://kostiantyn-dvornik.blogspot.com/2013/05/unity-worlds-coolest-tutorial-about.html

Warning:
Keep in mind that this solution works with deffered rendering legacy only. So please turn on this rendering method for your project.

First we need to remove all underwater things and leave only above water stuff, also we need to remove tranparent soft edge blending ( cuz we need to make as fast shader as possible ).

Let's take a look at our previous solution. We have polygon with water material applied, mirror reflection script that makes reflection texture and script that detects underwater and change shader lod that makes underwater effect. Everything else should be removed like this.



You can download start (cleanup) project Unity 5 here:
https://www.dropbox.com/s/ssu9l6ajdvm9u82/LongDistanceWaterStart.zip?dl=0

This small dot at huge cube is a human size

Shader looks like this:

Ok lets remove step by step things that we don't need. Because of water have no transparent effect lets remove grab pass that do transparent things of water and texture accoding to this

Before:
sampler2D _GrabTexture : register(s0);
sampler2D _BumpMap : register(s2);
sampler2D _ReflectionTex : register(s3);

sampler2D _CameraDepthTexture; // : register(s4);

After:
sampler2D _BumpMap;
sampler2D _ReflectionTex;

only 2 textures we need to make such long distance water. After remove everything that cause error when shader compiles.

You will get this small shader that makes water:

Shader "Dvornik/Long Distance Water" {
Properties {
 _SpecColor ("Specular Color", Color) = (0.5,0.5,0.5,1)
 _Shininess ("Shininess", Range (0.01, 1)) = 0.078125
 _Refraction ("Refraction", Range (0.00, 100.0)) = 1.0 
 _ReflectColor ("Reflection Color", Color) = (1,1,1,0.5)
 _DepthColor ("Depth Color", Color) = (1,1,1,0.5)
 _BumpReflectionStr ("_BumpReflectionStr", Range(0.00,1.00)) = 0.5
 _ReflectionTex ("_ReflectionTex", 2D) = "white" {}
 _BumpMap ("Normalmap", 2D) = "bump" {}
}

SubShader 
#pragma surface surf BlinnPhong
sampler2D _BumpMap;
float4 _ReflectColor;
struct Input {
void surf (Input IN, inout SurfaceOutput o) 
 float2 offset = o.Normal * _Refraction;
}


 Tags { "RenderType"="Opaque" }

CGPROGRAM

#pragma target 3.0

sampler2D _ReflectionTex;

float _Shininess;
float _Refraction;
float _BumpReflectionStr;

 float2 uv_MainTex;
 float2 uv_BumpMap;
 float3 worldRefl; 
 float4 screenPos;
 float3 viewDir;
 INTERNAL_DATA
};


 //Specular stuff
 o.Gloss = _SpecColor.a;
 o.Specular = _Shininess;

 //Normal stuff
 o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap - 33.0 * _Time.y ));
 o.Normal += UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap - 100.0 * _Time.y));
 o.Normal *= 0.5;

 IN.screenPos.xy = offset * IN.screenPos.z + IN.screenPos.xy; 

 float3 worldRefl = WorldReflectionVector(IN, o.Normal*half3(_BumpReflectionStr,_BumpReflectionStr,_BumpReflectionStr));
 worldRefl.y = -worldRefl.y;
 worldRefl.x = -worldRefl.x;

 half4 reflcol = tex2Dproj(_ReflectionTex, IN.screenPos);
 reflcol = reflcol * _ReflectColor;

 half4 resCol = reflcol; 

 o.Emission = resCol;
 o.Albedo = o.Emission;

ENDCG
}

FallBack "Reflective/Bumped Diffuse"
}


And result image will looks like this:


looks bad, but just because of settings. So lets continue our experiments. I tweaked a lot of parameters like wave speed and color like this:


Also I've found not so tilable texture that can be used in wave generation.

Result shader looks like this:

Shader "Dvornik/Long Distance Water" {
Properties {
 _SpecColor ("Specular Color", Color) = (0.5,0.5,0.5,1)
 _Shininess ("Shininess", Range (0.01, 1)) = 0.078125
 _Refraction ("Refraction", Range (0.00, 1.0)) = 0.02 
 _ReflectColor ("Reflection Color", Color) = (1,1,1,0.5) 
 _ReflectionTex ("_ReflectionTex", 2D) = "white" {}
 _BumpMap ("Normalmap", 2D) = "bump" {}
}

SubShader 


 Tags { "RenderType"="Opaque" }

CGPROGRAM

#pragma surface surf BlinnPhong

sampler2D _BumpMap;
sampler2D _ReflectionTex;

float4 _ReflectColor;
float _Shininess;
float _Refraction;
float _BumpReflectionStr;

struct Input {
 float2 uv_MainTex;
 float2 uv_BumpMap; 
 float4 screenPos;
 float3 viewDir;
 INTERNAL_DATA
};

void surf (Input IN, inout SurfaceOutput o) 

 //Specular stuff
 o.Gloss = _SpecColor.a;
 o.Specular = _Shininess;

 //Normal stuff
 o.Normal = UnpackNormal(tex2D(_BumpMap, 2.0 * IN.uv_BumpMap + 0.01 * _Time.y ));
 o.Normal += UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap - 0.01 * _Time.y));
 o.Normal *= 0.5;

  float2 offset = o.Normal * _Refraction;
 IN.screenPos.xy = offset * IN.screenPos.z + IN.screenPos.xy; 
  
 half4 reflcol = tex2Dproj(_ReflectionTex, IN.screenPos);
 reflcol = reflcol * _ReflectColor;

 half4 resCol = reflcol; 

 o.Emission = resCol;
 o.Albedo = o.Emission;

}
ENDCG
}

FallBack "Reflective/Bumped Diffuse"
}

And we got it:

Note:
In mirror reflection script you can tweak layers that will be render for reflection to minimize drawcalls and make your render faster.