這兩天研究了一些unity官方的例子Angry Bots,發現其中的水面反射挺有趣,就算是作爲讀書筆記寫下來吧。
圖中顯示了地面的水相當於一塊模糊的鏡子一樣。
就先來說說其中的原理吧,生活中你站在一塊鏡子面前,看到的東西都是面向鏡子的那一面,就相當於鏡子中有一個你(虛像)正朝着外面看一樣,高中物理可以解釋。爲此,我們必須準備一臺輔助的camera來充當鏡子中的你(虛像),於是得到一個相反的世界的影像,最後我們還需要把這個虛擬的影響映射到一塊鏡子上,也就是水面。
第一步,創建一個camera,項目中取名爲Main CameraReflectionMain Camera,可以看一下照相機的屬性
注意到Target Texture這個屬性,這是動態生成的紋理,繪製了該照相機在場景中所看到的(相當於鏡子裏看到的),爲了保證Main CameraReflectionMain Camera確實是主照相機的景象,需要以鏡子作爲平面,而兩照相機以平面(鏡面)對稱存在,而工程中,把這部分計算放在了ReflectionFx.cs文件中的RenderReflectionFor函數裏面
先來看看兩個照相機所看到的不同
下面的照相機正事從鏡子裏面看到的影像,現在的工作就是把下面的那張紋理中需要的部分映射到水面上。官方的做法是寫了一個shader,我們來看看這個着色器是如何工作的。
隨便選中一個具有反射效果的物體,我們可以他們的材質上都有一個共同的着色器RealtimeReflectionInWaterFlow,
目前我們只需要關心_ReflectionTex這個參數,傳入了上一張圖中所示的下圖的紋理,即Main CameraReflectionMain Camera的影像。打開RealtimeReflectionInWaterFlow這個文件,其實該文件有用的部分只有第一個SubShader:
Shader "AngryBots/RealtimeReflectionInWaterFlow" {
Properties {
_MainTex ("Base", 2D) = "white" {}
_Normal("Normal", 2D) = "bump" {}
_ReflectionTex("_ReflectionTex", 2D) = "black" {}
_FakeReflect("Fake reflection", 2D) = "black" {}
_DirectionUv("Wet scroll direction (2 samples)", Vector) = (1.0,1.0, -0.2,-0.2)
_TexAtlasTiling("Tex atlas tiling", Vector) = (8.0,8.0, 4.0,4.0)
}
CGINCLUDE
struct v2f_full
{
half4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
half4 normalScrollUv : TEXCOORD1;
half4 screen : TEXCOORD2;
half2 fakeRefl : TEXCOORD3;
#ifdef LIGHTMAP_ON
half2 uvLM : TEXCOORD4;
#endif
};
#include "AngryInclude.cginc"
half4 _DirectionUv;
half4 _TexAtlasTiling;
sampler2D _MainTex;
sampler2D _Normal;
sampler2D _ReflectionTex;
sampler2D _FakeReflect;
ENDCG
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
Pass {
CGPROGRAM
float4 _MainTex_ST;
// float4 unity_LightmapST;
// sampler2D unity_Lightmap;
v2f_full vert (appdata_full v)
{
v2f_full o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord,_MainTex);
#ifdef LIGHTMAP_ON
o.uvLM = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
#endif
o.normalScrollUv.xyzw = v.texcoord.xyxy * _TexAtlasTiling + _Time.xxxx * _DirectionUv;
o.fakeRefl = EthansFakeReflection(v.vertex);
o.screen = ComputeScreenPos(o.pos);
return o;
}
fixed4 frag (v2f_full i) : COLOR0
{
half3 nrml = UnpackNormal(tex2D(_Normal, i.normalScrollUv.xy));
nrml += UnpackNormal(tex2D(_Normal, i.normalScrollUv.zw));
nrml.xy *= 0.025;
fixed4 rtRefl = tex2D (_ReflectionTex, (i.screen.xy / i.screen.w) + nrml.xy);
rtRefl += tex2D (_FakeReflect, i.fakeRefl + nrml.xy * 2.0);
fixed4 tex = tex2D (_MainTex, i.uv.xy + nrml.xy * 0.05);
#ifdef LIGHTMAP_ON
fixed3 lm = ( DecodeLightmap (UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uvLM)));
tex.rgb *= lm;
#endif
tex = tex + tex.a * rtRefl;
return tex;
}
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile LIGHTMAP_OFF LIGHTMAP_ON
#pragma fragmentoption ARB_precision_hint_fastest
ENDCG
}
}
}
先看片段着色器中最後的像素輸出的 tex = tex + tex.a * rtRefl;tex.a是一個(0, 1)之間的小數,官方這麼做,是爲了弱化鏡像紋理的影響,顯得水面更逼真,如果是需要做完完全全的一面鏡子,大可將tex.a捨棄,使得最後效果爲疊加的,讓鏡像更清晰。再看看頂點着色器中o.screen = ComputeScreenPos(o.pos);
前文已經說過,我們必須把紋理中屬於鏡子外面的影像捨棄,這個方法就是用了場景中的物體的頂點作爲限定,映射到屏幕座標,最終得出了Main CameraReflectionMain Camera照相機中看到的哪一部分纔是鏡子的影像,生成了uv座標後傳遞給片段着色器。
如果有什麼地方講的不對也希望大家能指出,共同進步