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