- alexlpc2015
[Shader Notes] #04 - Simple Water
Hi and welcome to my Shader Notes Series!
This is a comprehensive project with notes about shaders of nearly every common effect included (implemented in Unity). I will explain the key points and theories behind them for you to understand them thoroughly and then enjoy creating your own shaders with proficiency.
Let's start learning together!
#04 - Simple Water
Difficulty: 5/10
Points covered:
Grab Pass
Depth Detection
Distortion
Using the effects from previous shader notes, we will create a simple water shader using grab pass.
Final effect:

Setup:
Create a new material and attach it to an object.
Prepare the properties and declare them in CGPROGRAM.
//Properties
_DisplacementTex("Displacement Texture", 2D) = "white" {}
_Magnitude("Magnitude", Range(0, 1)) = 0.5
_Speed("Speed", float) = 1
_Color("Color", Color) = (0.8,0.8,0.8,1)
_HighlightColor("Highlight Color", Color) = (0.8,0.8,0.8,1)
_Threshold("Threshold", float) = 1
_Power("Power", float) = 8
Vertex shader: similar to the previous shader note.
sampler2D _DisplacementTex;
float4 _DisplacementTex_ST;
//...
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.duv = TRANSFORM_TEX(v.uv, _DisplacementTex);
Process:
To simulate water refraction, we need to manipulate the colors inside and behind the water. To achieve this, we can use grab pass to first grab a texture of the objects rendered before the water.
Just like a pass, write GrabPass{"textureName"}.
GrabPass{"_GrabTex"}
Then we can use the grab texture as the "main texture" of the water and perform the same actions as the previous shader note.
float2 disp = tex2D(_DisplacementTex, i.duv+ frac(_Time.y * _Speed * 0.05)).xy;
disp = ((disp * 2) - 1) * _Magnitude;
float4 col = tex2Dproj(_GrabTex, i.grabPos + float4(disp.xy,0,0));
col *= _Color;
Finally, add depth detection for the edge of the water.

v2f vert (appdata v)
{
//...
o.scrPos = ComputeScreenPos(o.vertex);
COMPUTE_EYEDEPTH(o.scrPos.z);
}
fixed4 frag (v2f i) : SV_Target
{
//...
float sceneZ =
LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture,
UNITY_PROJ_COORD(i.scrPos)));
float selfZ = i.scrPos.w;
float diff =min(pow((1-saturate(sceneZ-
i.scrPos.z))/_Threshold,_Power), 1.0);
col = lerp(col, _HighlightColor,diff);
}
We now have a beautiful water shader!
This is a revision of the techniques discussed in previous note. If there is any confusion, please go to #01/#03 for reference.
Full Shader Code:
Shader "AlexLiu/Water_0"
{
Properties
{
_DisplacementTex("Displacement Texture", 2D) = "white" {}
_Magnitude("Magnitude", Range(0, 1)) = 0.5
_Speed("Speed", float) = 1
_Color("Color", Color) = (0.8,0.8,0.8,1)
_HighlightColor("Highlight Color", Color) = (0.8,0.8,0.8,1)
_Threshold("Threshold", float) = 1
_Power("Power", float) = 8
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue" = "Transparent"}
LOD 100
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
GrabPass{"_GrabTex"}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float2 duv : TEXCOORD1;
float4 vertex : SV_POSITION;
float4 grabPos : TEXCOORD2;
float4 scrPos : TEXCOORD3;
};
sampler2D _GrabTex;
sampler2D _DisplacementTex;
sampler2D _CameraDepthTexture;
float4 _DisplacementTex_ST;
float _Magnitude;
float _Speed;
float _Threshold;
float _Power;
fixed4 _Color;
fixed4 _HighlightColor;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.duv = TRANSFORM_TEX(v.uv, _DisplacementTex);
o.grabPos = ComputeGrabScreenPos(o.vertex);
o.scrPos = ComputeScreenPos(o.vertex);
COMPUTE_EYEDEPTH(o.scrPos.z);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 disp = tex2D(_DisplacementTex, i.duv+ frac(_Time.y * _Speed * 0.05)).xy ;
disp = ((disp * 2) - 1) * _Magnitude;
float4 col = tex2Dproj(_GrabTex, i.grabPos + float4(disp.xy,0,0));
col *= _Color;
float sceneZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.scrPos)));
float selfZ = i.scrPos.w;
float diff =min(pow((1-saturate(sceneZ-i.scrPos.z))/_Threshold,_Power), 1.0);
col = lerp(col, _HighlightColor,diff);
return col;
}
ENDCG
}
}
}
Thank you for reading!