使用 unity理解渲染時的 深度緩衝機制

在其他遊戲裏見到過這樣的效果:

人物走到建築後面,被建築擋住的部分做特殊顯示、沒有被建築擋住的部分正常渲染。

想要實現這種效果,關鍵的要點是 ZTest.

今天藉助unity瞭解了 ZTest 機制.再此做一下總結.

創建一個空場景,兩個 cube,距離近的是 cube_1, 遠的是 cube_2

在這裏插入圖片描述

分別給 cube_1 ,cube_2 寫 shader

cube_1 的 shader 叫做 Shader_1. Shader_1 只包含一個顏色屬性,把 cube_1 渲染成藍色
Shader_1 代碼如下

Shader "Custom/Shader_1"
{
    Properties
    {
        _Color("color",Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            fixed4 _Color;   

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return _Color;
            }
            ENDCG
        }
    }
}

cube_2 的 shader 是 Shader_2 ,Shader_2 的 SubShader 包含 2個Properties 和 2個 pass
第一個 Pass 使用 _Color 正常渲染
第二個 Pass 使用 _SubColor 渲染

Shader "Custom/Shader_2"
{
    Properties
    {
        _Color("color",Color) = (1,1,1,1)
        _SubColor("sub color",Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        LOD 100

        // draw normal  
        Pass
        {

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            fixed4 _Color;  

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return _Color;
            }
            ENDCG
        }
        
        // draw hide shadow
        Pass
        {

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            fixed4 _SubColor;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return _SubColor;
            }
            ENDCG
        }
    }
}

給 cube2 的 material 賦值顏色,_Color 賦值爲 紅色,_SubColor 賦值爲 綠色.

此時,場景裏面的 cube2 會顯示爲綠色,因爲採用 subcolor 的 pass 寫在後面, 第2個 pass 會在前一個 pass 的基礎上繼續渲染,二者繪製的像素都一樣,所以結果看起來是第2個 pass 在起作用

此時,如果二者有所重疊,希望 cube2 的 第2個 pass 起作用,沒有重疊的部分 第1個 pass 起作用的話,關鍵點是在 cube2 的 第2個 pass 上面加上 ZTest Greater.

修改之後 shader_2 代碼如下,他跟上面的代碼只有 ZTest 不同

Shader "Custom/Shader_2"
{
    Properties
    {
        _Color("color",Color) = (1,1,1,1)
        _SubColor("sub color",Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        LOD 100

        // draw normal  
        Pass
        {
            ZTest LEqual 

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            fixed4 _Color;  

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return _Color;
            }
            ENDCG
        }
        
        // draw hide shadow
        Pass
        {
            ZTest Greater

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            fixed4 _SubColor;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return _SubColor;
            }
            ENDCG
        }
    }
}

這樣一來, cube_2 被 cube_1 覆蓋的部分,會顯示 pass2 的綠色 繪製結果, 沒有被 cube_1 覆蓋的部分,會正常顯示。
這樣就基本上實現了 我們想要的“被擋住的部分 採用另外一種繪製方式” 的效果。

需要注意的點是,
Unity 在繪製物體時,繪製順序完全按照 Shader 的 Queue 來繪製的。 Queue 值越小,越先繪製,並且繪製時,有一個 “深度緩衝區” 的概念。因此,想要保證效果正確,必須 cube_1 的 queue 值 <= cube_2 的 queue值。

可以這樣理解:
深度緩衝區是一個和屏幕一樣大小的白紙,紙上的每個像素都有一個深度值,起初每個像素的深度值都是 max.
繪製了 cube_1 之後, cube_1 的 shader 沒有制定 z test 和 z write ,因此採用默認的方式, cube_1 會把自己的深度值寫入到深度緩衝區中。

此時,深度緩衝區 裏面 被 cube_1 覆蓋的像素 深度值是 c1. 當再繪製 cube_2 時,由於 cube_2 比 cube_1 距離相機遠,因此其像素的 深度值 c2 > c1 .
按照默認的 shader ,z test 默認是 EqualLess 的模式,也就是說深度值小(距離相機更近)才繪製,這也是常理上的效果,因此正常情況下 cube_2 被擋住的像素 是不會繪製的。
但是由於 cube_2 的 shader 第2 個 pass 裏包含 ZTest Greater ,因此被遮蓋的部分只有在 深度值較大時才繪製。

這樣就達成了 我們想要的效果。

在這裏插入圖片描述

瞭解了這個道理,如果希望 cube_2 不穿透某些物體,則使得這些不被穿透的物體晚於 cube_2 繪製,即 queue 的值 大於 cube_2 shader 裏面設置的值。這樣 cube_2 繪製時深度緩衝區裏還沒有東西, 只能跟默認的 max 比較,由於由於 cube_2 shader 的 第2個 pass 的 ztest 規則是 Greater ,c2 不大於 max, 這樣透視的效果就能失去效果了。

按照這個方法,我測試了 2d,3d 混合測試,帶 texture 測試,在設置正確的基礎上,都能夠達到預期效果。
2d sprite 在設置上有一些需要注意的地方:不光 shader 的 queue 會影響 繪製順序, sort layer 以及 sort index 也都可能會影響繪製順序。如果按照相同方法實現時候出了問題,需要留意 這些地方的設置。

記錄完畢~

發佈了160 篇原創文章 · 獲贊 77 · 訪問量 37萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章