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:

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!!
Answer by josessito · Mar 02, 2016 at 08:53 PM
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, any chance you could elaborate more on how you set this up in Unity itself? create a material, applying to a quad and placing in scene doesnt work. Is there an associated script needed for this?
Answer by Kiupe · Apr 16, 2017 at 07:15 AM
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
Your answer
 
             Follow this Question
Related Questions
Keep water4 from reflecting certain objects 0 Answers
How to use reflection probes/cubemaps to make a reflection shader 0 Answers
Why does my mirror not reflect my water shader? 0 Answers
Removing the mirror effect in Reflection probes 0 Answers
How do you reflect a directional light Sun in Unity5 water? 2 Answers