Help with grabpass shader for 2D water effect.

Hi, I been trying to write a shader for a water effect using grabpass to capture the screen the distorted with a scrollable bump map. But I have been having some isuess with the uv of the grabpass.
Here is the current shader, wich at this moment just distorts the image behind it.

Shader "Custom/WaterGrab" 
{
	Properties 
	{		
		_Colour ("Colour", Color) = (1,1,1,1)
		_MainTex ("Noise text", 2D) = "bump" {}
		_Magnitude ("Magnitude", Range(0,1)) = 0.05
	}
	
	SubShader
	{
		Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Opaque"}
		ZWrite On Lighting Off Cull Off Fog { Mode Off } Blend One Zero

		GrabPass { "_GrabTexture" }
		
		Pass 
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			sampler2D _GrabTexture;
			fixed4 _Colour;
			sampler2D _MainTex;
			float  _Magnitude;

			struct vin
			{
				float4 vertex : POSITION;
				float4 color : COLOR;
				float2 texcoord : TEXCOORD0;
				
			};

			struct v2f
			{
				float4 vertex : POSITION;
				fixed4 color : COLOR;
				float2 texcoord : TEXCOORD0;
				float4 uvgrab : TEXCOORD1;
				float2 screenPos : TEXCOORD2;
					
			};

			float4 _MainTex_ST;

			// Vertex function 
			v2f vert (vin v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.color = v.color;
				o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
				o.screenPos = ComputeScreenPos(o.vertex);


			#if UNITY_UV_STARTS_AT_TOP
				float scale = -1.0;
			#else
				float scale = 1.0;
			#endif
 
				o.uvgrab.xy = (float2(o.vertex.x, (o.vertex.y)* scale) + o.vertex.w) * 0.5;
				o.uvgrab.zw = o.vertex.zw;

				return o;
			}

			// Fragment function
			half4 frag (v2f i) : COLOR
			{				
				half4 bump = tex2D(_MainTex, i.texcoord);
				half2 distortion = UnpackNormal(bump).rg;
				
				i.uvgrab.xy += distortion * _Magnitude;	
			
				fixed4 col = tex2D( _GrabTexture, i.uvgrab);
				return col * _Colour;
			}
		
			ENDCG
		} 
	}
}

The desired effect is this one:

65019-kingdom.jpg

The setup is simple, I just put a quad with the shader, it gets the screen behind it with grabpass then find the right pixels (those that are on top of it in the y coordinate) and then flip it. I know how to apply the displacement and all the other effects in the fragment shader to make the captured image to look like water. But for the life of me I can’t figure out how to map the right portion of the grabpass to the quad.

Please I’ve been trying for a week, learning everything I can about shaders, but I believe this one is over my head.

EDIT: I forgot to mention that my camera moves around in the y and x position and also changes its orthographic size. That is the reason why the following method didn’t work:

// Fragment function
			half4 frag (v2f i) : COLOR
			{				
				half4 bump = tex2D(_MainTex, i.texcoord);
				half2 distortion = UnpackNormal(bump).rg;
				
				i.uvgrab.xy += distortion * _Magnitude;	
				i.uvgrab.y = 1 - i.uvgrab.y + _Offset;
			
				fixed4 col = tex2D( _GrabTexture, i.uvgrab);
				return col * _Colour;
			}

The issue here is that when the Camera moves up or down the reflection moves which should not happen. I also try to control the _Offset via a script based on the objects relative position with the camera like this:

using UnityEngine;
using System.Collections;

public class WaterReflectionHeight : MonoBehaviour
{
    public float factor;
    public float distance;

    float startingDist;
    float startingHeight;
    Renderer rend;
    Material mat;
    MeshRenderer meshRend;

    float ratio;

    void Awake()
    {
        rend = GetComponent<Renderer>();
        mat = rend.material;
        meshRend = GetComponent<MeshRenderer>();
        
    }
    void Start()
    {
        
        startingDist = CameraFollow.instance.transform.position.y -transform.position.y;
        startingHeight = mat.GetFloat("_Offset");
        ratio = startingHeight/ startingDist;
    }

    void LateUpdate()
    {
        distance = CameraFollow.instance.transform.position.y - transform.position.y;
        var height = distance*ratio;
        

        mat.SetFloat("_Offset", height);
    }
}

But there are a few issues with this approach, the main one is that for some reason the reflection still moves a little (not sure why) and the second one is that the starting height (_Offset) will need to be set depending on the quad’s position an the camera view (if, for instance, the screen size is not the same to the one the _Offset was adjusted for, the reflections would be too low or too high). So I think this hole _Offset - Script approach is not good.

I think someone with a better grasp on shaders could fix this. I been going nuts trying to understand matrix, transforms, clip space and all that to end up empty handed.

Thanks!!

Ok I did it.

Shader "Custom/WaterGrab" 
{
	Properties 
	{		
		_Colour ("Colour", Color) = (1,1,1,1)
		_MainTex ("Noise text", 2D) = "bump" {}
		_Magnitude ("Magnitude", Range(0,1)) = 0.05
	}
	
	SubShader
	{
		Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Opaque"}
		ZWrite On Lighting Off Cull Off Fog { Mode Off } Blend One Zero

		GrabPass { "_GrabTexture" }
		
		Pass 
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			sampler2D _GrabTexture;
			fixed4 _Colour;
			sampler2D _MainTex;
			float  _Magnitude;

			struct vin
			{
				float4 vertex : POSITION;
				float4 color : COLOR;
				float2 texcoord : TEXCOORD0;
				
			};

			struct v2f
			{
				float4 vertex : POSITION;
				fixed4 color : COLOR;
				float2 texcoord : TEXCOORD0;
				float4 uvgrab : TEXCOORD1;
					
			};

			float4 _MainTex_ST;

			// Vertex function 
			v2f vert (vin v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.color = v.color;
				o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);


			#if UNITY_UV_STARTS_AT_TOP
				float scale = -1.0;
			#else
				float scale = 1.0;
			#endif			

				o.uvgrab.xy = (float2(o.vertex.x, (o.vertex.y)* scale) + o.vertex.w) * 0.5;
				o.uvgrab.zw = o.vertex.zw;

				float4 top = mul(UNITY_MATRIX_MVP, float4(0, 0.5, 0, 1));
				top.xy /= top.w;

				o.uvgrab.y = 1 - (o.uvgrab.y + top.y);

				return o;
			}

			// Fragment function
			half4 frag (v2f i) : COLOR
			{		
				
				half4 bump = tex2D(_MainTex, i.texcoord );
				half2 distortion = UnpackNormal(bump).rg;
				
				
				i.uvgrab.xy += distortion * _Magnitude;	    			
				fixed4 col = tex2D( _GrabTexture, i.uvgrab);				
				return col * _Colour;
			}
		
			ENDCG
		} 
	}
}

Hi @ josessito,

I’m currently looking for a 2D water shader and when I saw your post I thought to test yours. So I did and unfortunately it seems to not work as expected. I created a material using this shader, put a texture on it and applied the material on a quad into a 2D scene, added a sprite character and hit play. The quad is displayed with a solid color but nothing else. Is there specific set-up to use this shader ?

Thanks