top of page
Search
• 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

```

```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 =
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.

```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
}
{
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;
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 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
}
}
}

```