【躬行】-深度缓冲和模板缓冲是怎么存储的?

概述

最近在工作中需要实现一个功能,用到了模板测试。但奇怪的是,模板测试竟然不起作用!在解决问题的过程中,发现了一些有趣的知识点。通过本文,可以了解在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纹理的内存。

参考

相关推荐
zhchyun200813 小时前
# 【Unity UI 进阶】仿 Element UI 打造企业级 Unity UI 组件库(06)
ui·unity·游戏引擎
SmalBox13 小时前
【动态着色】Unity 实现全息投影着色器
unity3d·游戏开发·图形学
Behaviour19 小时前
Unity游戏HybridCLR 热更新接入实战
游戏·unity·游戏引擎
LONGZETECH1 天前
新能源汽车动力电池实训教学痛点与虚拟仿真技术解决方案
c语言·3d·unity·架构·汽车·汽车教学软件
王维志1 天前
UiSplineRenderer
unity·游戏引擎
SmalBox1 天前
【动态着色】Unity 实现箭头图案
unity3d·游戏开发·图形学
FairGuard手游加固2 天前
iOS 游戏加固技术解析:代码混淆、资源加密、内存保护与重签名检测
安全·游戏·macos·unity·ios·objective-c
SmalBox3 天前
【扭曲着色器】Unity实现-黑洞扭曲效果
unity3d·游戏开发·图形学
UWA3 天前
GPM 2.0 WebGL 监测能力扩容|覆盖 Unity/Cocos/LayaAir 多引擎微信 & 抖音小游戏
unity·微信·性能优化·小游戏·webgl
Behaviour3 天前
Unity 手游网络同步技术
网络·unity·c#·游戏引擎