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

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!

14 views0 comments

Recent Posts

See All
Post: Blog2_Post
bottom of page