Unity Shader实现水体的折射效果
水是透明物体,站在清澈的水边,我们可以透过水看到水中的物体。且随着水波晃动,水下的景象会随着水波扭曲。
前面我们实现了水的环境映射、水的反射效果,现在我们来做水的折射效果。
效果展示
透明/半透明物体折射效果的实现
原理
先把透明物体隐藏起来,实时获取摄像机渲染的图像,然后对图像进行扭曲处理,然后再把水的模型显示出来,把处理好的图像用屏幕uv的方式作为贴图贴上去
实现要点
1. GrabPass捕捉屏幕纹理
Unity提供了一个ShaderLab命令— GrabPass,用来实现一种特殊的Pass以获取屏幕图像。
GrabPass支持两种形式:
《Unity Shader入门精要》
直接使用GrabPass{},然后在后续的Pass中直接使用_GrabTexture来访问屏幕图像。但是当场景中有多个物体都使用了这种形式来抓取屏幕时,这种方法的性能消耗比较大,因为对于每一个使用它的物体,Unity都会为它单独进行一次昂贵的屏幕抓取动作。但这种方法可以让每个物体得到不同的屏幕图像,这取决于它们的渲染队列以及它们渲染时当前的屏幕缓冲区中的颜色。
使用GrabPass{ “TextureName” },我们可以在后续的Pass中使用TextureName来访问屏幕图像。使用这种方法同样可以抓取屏幕,但Unity只会在每一帧时为第一个使用名为TextureName的纹理的物体执行一次抓取屏幕的操作,而这个纹理同样可以在其他Pass中被访问。这种方法更高效,因为不管场景中有多少物体使用了该命令,每一帧中Unity都只会执行一次抓取操作,但这也意味着所有物体都会使用同一张屏幕图像。
GrabPass示例
新建一个3D Object(Plane)放在屏幕的左上角的位置,将GrabPass采集的纹理作为Plane的纹理,效果如下所示:
GrabPass示例shader代码
Shader "Hidden/TestGrabPass"
{
Properties{}
SubShader
{
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;
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
sampler2D _MainTex;
sampler2D _GrabTex;
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_GrabTex, 1-i.uv);
// just invert the colors
// col = 1 - col;
return col;
}
ENDCG
}
}
}
为什么GrabPass的uv是反的?
这是坐标系差异所致:
Unity3D从左下角为(0,0)