白纸一张

三十而立,四十而不惑

0%

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

概述

最近在工作中需要实现一个功能,用到了模板测试。但奇怪的是,模板测试竟然不起作用!在解决问题的过程中,发现了一些有趣的知识点。通过本文,可以了解在unity中,深度缓冲和模板缓冲到底是怎么存储的。

测试环境的搭建

Unity版本:2021.3.16f1

URP版本:12.1.8

RenderDoc:1.29

需要注意的是,URP的版本迭代,代码改动较大,最好与上面的版本一致。否则,可能会因为版本不同,产生无谓的麻烦。

后面的实验需要使用到RenderDoc。关于怎么在Unity中使用RenderDoc,可以查看最后的参考文献部分。

  1. 由于后续需要修改URP的源码进行测试,所以需要移动URP源码的路径。新建URP项目,源码的路径是类似这种:xxx(xxx是URP项目的文件夹名)。需要将以下两个URP源码文件夹移动到xxx:

    移动后,Packages文件夹类似这样:

  2. 实现一个基础的Shader,包含了深度测试和模板测试。代码很简单,就不赘述了。如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    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材质球

    Far材质球,设置为总是通过模板测试,替换模板值3,Render Queue设为2000。

    near材质球

    Near材质球,设置模板缓冲值为3时才通过,保留模板缓冲值,Render Queue设为2010。

    通过上面的设置,会先渲染Far材质球,写入模板缓冲3。然后再渲染Near材质球,只有模板缓冲中值为3的区域才会渲染。

    使用Frame Debugger查看渲染流程,可以发现,确实是先渲染Far,再渲染Near。整体的渲染流程如下:

    注意上图中红框中的部分,是颜色缓冲纹理的名称。在代码中使用全局搜索,可以找到如下部分:

    通过观察分析,可以发现,深度缓冲和模板缓冲,主要是受到下面代码的影响:

    colorDescriptor.depthBufferBits的代码注释如下:

    这个值代表渲染纹理的深度缓冲精度比特值,支持0,16,24,32这四个值。

    下面,分别把colorDescriptor.depthBufferBits设为上面的四个值,查看效果。

    实验

    实验一 设为0

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

    2. Frame Debugger

    3. RenderDoc

      分析:从场景效果看,只渲染了天空盒,没有显示出Far或Near。但从Frame Debugger上看,流程并没有改变,还是先渲染Far,再渲染Near,接着再渲染天空盒。只是天空盒将Far和Near都覆盖了。从RenderDoc看,只有颜色纹理RT0。从这些内容分析以下,应该是因为没有了深度缓冲和模板缓冲,导致深度测试和模板测试不起作用了。

实验二 设为16

1
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

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

  2. Frame Debugger 与上面相同,略

  3. RenderDoc

    分析:从场景效果和Frame Debugger上看,效果和流程与开始实验前完全一样。从RenderDoc看,与设为16时一样,都有RT0和DS两张纹理。但DS纹理的格式和内容是不同的,在上图底部可以发现,DS的格式是D32S8,后面还有深度缓冲值和模板缓冲值。与设为16时相比,DS纹理的格式不同,纹理的信息中,还多了模板缓冲值。这样,可以推测,深度缓冲和模板缓冲都有了,深度测试和模板测试也都起作用了。

实验四 设为32

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

  2. Frame Debugger 与上面相同,略

  3. RenderDoc与上面相同,略

可以发现,设为32时,与设为24时的效果完全相同。这是为什么呢?

colorDescriptor.depthBufferBits的源码如下:

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

设为24时,单步调试的结果如下:

9、bit为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纹理的内存。

参考

-------------本文结束感谢您的阅读-------------