Angry Bots水面反射效果剖析

這兩天研究了一些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.04.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_MVPv.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(_Normali.normalScrollUv.xy));
            nrml += UnpackNormal(tex2D(_Normali.normalScrollUv.zw));
            
            nrml.xy *= 0.025;
                                        
            fixed4 rtRefl = tex2D (_ReflectionTex, (i.screen.xy / i.screen.w) + nrml.xy);
            rtRefl += tex2D (_FakeReflecti.fakeRefl + nrml.xy * 2.0);
                        
            fixed4 tex = tex2D (_MainTexi.uv.xy + nrml.xy * 0.05);
        
            #ifdef LIGHTMAP_ON
                fixed3 lm = ( DecodeLightmap (UNITY_SAMPLE_TEX2D(unity_Lightmapi.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座標後傳遞給片段着色器。

如果有什麼地方講的不對也希望大家能指出,共同進步

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章