【躬行】-深度緩衝和模板緩衝是怎麼儲存的?

2023-11-07 06:01:20

概述

最近在工作中需要實現一個功能,用到了模板測試。但奇怪的是,模板測試竟然不起作用!在解決問題的過程中,發現了一些有趣的知識點。通過本文,可以瞭解在unity中,深度緩衝和模板緩衝到底是怎麼儲存的。

測試環境的搭建

Unity版本:2021.3.16f1

URP版本:12.1.8

RenderDoc:1.29

需要注意的是,URP的版本迭代,程式碼改動較大,最好與上面的版本一致。否則,可能會因為版本不同,產生無謂的麻煩。

後面的實驗需要使用到RenderDoc。關於怎麼在Unity中使用RenderDoc,可以檢視最後的參考文獻部分。

  1. 由於後續需要修改URP的原始碼進行測試,所以需要移動URP原始碼的路徑。新建URP專案,原始碼的路徑是類似這種:xxx\Library\PackageCache(xxx是URP專案的資料夾名)。需要將以下兩個URP原始碼資料夾移動到xxx\Packages資料夾下:

    移動後,Packages資料夾類似這樣:

  2. 實現一個基礎的Shader,包含了深度測試和模板測試。程式碼很簡單,就不贅述了。如下所示:

    Shader "Test/Hello World"
    {
        Properties
        {
            _Color ("Main Color", Color) = (1,1,1,1)
            
            [Header(Stencil)]
            [Enum(UnityEngine.Rendering.CompareFunction)]_StencilComp ("Stencil Comparison", Float) = 8
            [IntRange]_Stencil ("Stencil ID", Range(0,255)) = 0
            [Enum(UnityEngine.Rendering.StencilOp)]_StencilPass ("Stencil Pass", Float) = 0
        }
        SubShader
        {
            Tags { "Queue" = "Geometry" "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }
            
            Pass
            {
                Tags { "LightMode" = "UniversalForward" }
                Cull Off
                ZTest LEqual
                ZWrite On
                
                Stencil
                {
                    Ref [_Stencil]
                    Comp [_StencilComp]
                    Pass [_StencilPass]
                }
                
                HLSLPROGRAM
                
                #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
                
                #pragma vertex vert
                #pragma fragment frag
                
                struct Attributes
                {
                    float4 positionOS: POSITION;
                };
                
                struct Varyings
                {
                    float4 vertex: SV_POSITION;
                };
                
                half4 _Color;
                
                Varyings vert(Attributes input)
                {
                    Varyings output = (Varyings)0;
                    
                    output.vertex = TransformObjectToHClip(input.positionOS.xyz);
                    
                    return output;
                }
                
                half4 frag(Varyings input): SV_Target
                {
                    return _Color;
                }
                ENDHLSL
                
            }
        }
    }
    
    
  3. 需要設定一下測試的場景環境。使用上面的Shader新建兩個材質球:Far和Near,如下設定:

    Far材質球,設定為總是通過模板測試,替換模板值3,Render Queue設為2000。

    Near材質球,設定模板緩衝值為3時才通過,保留模板緩衝值,Render Queue設為2010。

    通過上面的設定,會先渲染Far材質球,寫入模板緩衝3。然後再渲染Near材質球,只有模板緩衝中值為3的區域才會渲染。

    使用Frame Debugger檢視渲染流程,可以發現,確實是先渲染Far,再渲染Near。整體的渲染流程如下:

    注意上圖中紅框中的部分,是顏色緩衝紋理的名稱。在程式碼中使用全域性搜尋,可以找到如下部分:

    通過觀察分析,可以發現,深度緩衝和模板緩衝,主要是受到下面程式碼的影響:

    colorDescriptor.depthBufferBits的程式碼註釋如下:

    這個值代表渲染紋理的深度緩衝精度位元值,支援0,16,24,32這四個值。

    下面,分別把colorDescriptor.depthBufferBits設為上面的四個值,檢視效果。

    實驗

    實驗一 設為0

    colorDescriptor.depthBufferBits = (useDepthRenderBuffer) ? 0 : 0;
    
    1. 場景效果

    2. Frame Debugger

    3. RenderDoc

      分析:從場景效果看,只渲染了天空盒,沒有顯示出Far或Near。但從Frame Debugger上看,流程並沒有改變,還是先渲染Far,再渲染Near,接著再渲染天空盒。只是天空盒將Far和Near都覆蓋了。從RenderDoc看,只有顏色紋理RT0。從這些內容分析以下,應該是因為沒有了深度緩衝和模板緩衝,導致深度測試和模板測試不起作用了。

實驗二 設為16

colorDescriptor.depthBufferBits = (useDepthRenderBuffer) ? 16 : 0;
  1. 場景效果

  2. Frame Debugger 與上面相同,略

  3. RenderDoc

    分析:從場景效果看,顯示出Far和Near,但是模板測試並沒有起作用,因為完整的渲染出了Near。從Frame Debugger上看,流程並沒有改變。從RenderDoc看,除了顏色紋理RT0,還多渲染了一張紋理DS(從名字看,應該是Depth Stencil)。在RT0中右鍵選中Far範圍內的一點,再切換到DS,可以在RenderDoc的底部看到選中點的深度、模板資訊。從上圖可以看出,DS紋理的格式是R16,後面的值是選中點的深度緩衝值。這樣,可以推測,有了深度緩衝,深度測試應該是起作用了,但是模板緩衝還是沒有起作用,因為沒有模板緩衝。

實驗三 設為24

colorDescriptor.depthBufferBits = (useDepthRenderBuffer) ? 24 : 0;
  1. 場景效果

  2. Frame Debugger 與上面相同,略

  3. RenderDoc

    分析:從場景效果和Frame Debugger上看,效果和流程與開始實驗前完全一樣。從RenderDoc看,與設為16時一樣,都有RT0和DS兩張紋理。但DS紋理的格式和內容是不同的,在上圖底部可以發現,DS的格式是D32S8,後面還有深度緩衝值和模板緩衝值。與設為16時相比,DS紋理的格式不同,紋理的資訊中,還多了模板緩衝值。這樣,可以推測,深度緩衝和模板緩衝都有了,深度測試和模板測試也都起作用了。

實驗四 設為32

colorDescriptor.depthBufferBits = (useDepthRenderBuffer) ? 32 : 0;
  1. 場景效果與上面相同,略

  2. Frame Debugger 與上面相同,略

  3. RenderDoc與上面相同,略

可以發現,設為32時,與設為24時的效果完全相同。這是為什麼呢?

colorDescriptor.depthBufferBits的原始碼如下:

    public int depthBufferBits
    {
      get => GraphicsFormatUtility.GetDepthBits(this.depthStencilFormat);
      set => this.depthStencilFormat = RenderTexture.GetDepthStencilFormatLegacy(value, this.graphicsFormat);
    }

設為24時,單步偵錯的結果如下:

設為32時,單步偵錯的結果如下:

從上面可以發現,設定depthBufferBits的int值,並不會向一般的屬性那樣直接儲存int值。而是經過計算之後,儲存到GraphicsFormat型別的變數中。而當設定的值是24和32時,儲存的GraphicsFormat型別的變數都是D32_SFloat_S8_UInt。這也就解釋了為什麼設為24和32時,RenderDoc中完全一致的問題。

實驗結論

上面的實驗結果,可以用下面的圖表簡潔表達:

DS紋理 深度測試 模板測試
0
16
24
32

回到最初遇到的問題:模板測試不起作用。根據上面的表格,在專案中查了一下,是因為depthBufferBits設為了0,導致深度測試和模板測試都不起作用了。思路延伸一下:從效能優化的角度考慮,如果某種情況下不需要深度測試或模板測試,可以賦予depthBufferBits一個比較低的值,這樣,DS紋理佔用的記憶體會比較小,甚至不需要申請DS紋理的記憶體。

參考