TBR 和 TBDR 架构网上的文章实在太多了,并且不同的硬件,内部具体的实现原理都不太一样(例如 Early-DT 的实现技术对于 PowerVR 是 Hidden Surface Removal (HSR),对于 Adreno 是 Low Resolution Z (LRZ)……)更何况细节,虽说这些都不算机密,但是也不是说你想拿到就拿到,除非是像这种相当专业的专利文,因此网上极大多数文章也都只能说是对各种渠道信息的总结和搬运,不能保证100%正确性,甚至还有些内容只是单纯推测,尽管如此,它们依旧是有不错的参考价值的
这里不会再对一些比较常识性的东西进行简述和总结,只看单一篇文章也必有疏漏,因此直接贴链接供完全不了解这块的人做个参考好了,当然这种分析硬件架构的文章是没法原创的,说到底都是拾人牙慧,好听点的话叫翻译和总结:
一个常识是:如果片段中做了 clip,也就是 Alpha-Test 的物体,是无法进行提前深度测试(Early-DT)的,而以 HSR 为例,其作为减少 overdraw 的一种类 Early-DT 手段,遇到 Alpha-Test 的物体,必然也会失效。但是问题就在这里,所谓“失效”,流程上会有哪些改变?会带来多大的性能损失?补救方案又是什么?这里网络上还是有不少争议点的,很多篇关于 TBDR 架构的文章,也多多少少会提到这一块

先放张图,后面可以用作参考,其实这里主要讨论的就是这一块 Defer


网上一个主流的说法是:HSR 对于不透明物体(Opaque)并不会关心(或者并不知道)当前的每一个 DrawCall 是否 Alpha-Test,等到 shading 时发现有片段被 clip 了,这时才后知后觉发现原先 HSR 的时候可能会出错,从而对于当前 clip 所在片段的 Tile 直接从头来过,暴力将前面所有的图元(fragment)都跑 ps
这听上去很恐怖:只要当前 Tile 有 clip 操作发生,整个 HSR 就会退化成同等于完全没有生效: overdraw 的问题当然也不会得到任何改善,也因为这个,Alpha-Test 在很多人心里性能会还不如 Alpha-Blend
但是这个科学么,其实是不太科学的,做驱动的不会这么暴力,但如果上面的说法没错,那么就应该是丢失了细节?
后面查阅了更多的资料,甚至还参考了 PowerVR 专利文,大致能得出一个更合理的推测,或者说为上面补充细节
首先提两个关键信息:
根据专利论文,中关于 FIG.25 的流程描述,可以得知:

好了,对于最后一个 ,由于他会挡住
,并且已经是最后一个 DrawCall 了,确认在最上面就直接走下一步进行 shading,当此 Tile 下的所有 geometry 的都处理完,合法的片元信息都被送到 pipeline 的后面
总结一下,对于 ABCDEF 六个 DrawCall,如果都没有开启 Alpha-Test,那么最后会被送到 pipeline 的下一步进行 shading 的只会是最前面的 (完美情况),而对于实际情况:
开启了 Alpha-Test,那么最后被送到 pipeline 的下一步进行 shading 的就会有
因此可以得出结论:

除了将 Alpha-Test 物体放在最后面渲染外,还有一种方案就是对其现做一个软件的 Early-DT,也就是 Depth PrePass
一个经典的例子就是树叶的渲染,出于性能考虑,单片树叶的顶点数不能太多,因此树叶的形状需要用 clip 像素的方式扣出来,而绘制一棵树的时候还会有个比较吃性能的地方:就是树叶在大多数视角方向上,都有大量的 fragment 叠加(用人话说就是一条射线可以穿过无数片树叶),因此在不做 Early-Z 的情况下,一棵树必然会出现大量的 overdraw
但上面也分析了 HSR 和 Alpha-Test,看上去硬件并没有办法帮我们解决这么多 overdraw 的问题,实际打出的手机包拉近看树叶,确实也出现了掉帧的情况,因此为了减少计算量,Depth PrePass 就再所难免了

这样做还有一个好处是:对于 Alpha-Test 物体,可以直接在 Depth PrePass 中做 clip 操作,这样在后面绘制的时候,是可以完全当成不透明物体的,不但可以减少大量的片段着色计算,也同等于把 Alpha-Test 的物体也提到了最前面去画,对于上面的流程就是:
搞定!如果树遮挡了大量地形,后面地形 overdraw 的问题也不会出现
DepthPrePassRenderFeature.cs:
- public class MOpaqueEarlyZRenderFeature : ScriptableRendererFeature
- {
- RenderObjectsPass _preDepthPass;
- RenderObjectsPass _RenderersPass;
-
- public override void Create()
- {
- string profilerPreDepthTag = "Opaque EarlyZ PreDepth";
- string[] preDepthShaderTagIds = {"OpaqueEarlyZPreDepth" };
- RenderObjects.CustomCameraSettings cameraSettings = new RenderObjects.CustomCameraSettings();
- _preDepthPass = new RenderObjectsPass(profilerPreDepthTag, RenderPassEvent.BeforeRenderingOpaques, preDepthShaderTagIds,
- RenderQueueType.Opaque,-1, cameraSettings);
-
- string profilerRenderersPassTag = "Opaque EarlyZ";
- string[] renderersPassTagIds = { "OpaqueEarlyZ" };
- _RenderersPass = new RenderObjectsPass(profilerRenderersPassTag, RenderPassEvent.BeforeRenderingOpaques, renderersPassTagIds,
- RenderQueueType.Opaque, -1, cameraSettings);
- }
-
- // Here you can inject one or multiple render passes in the renderer.
- // This method is called when setting up the renderer once per-camera.
- public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
- {
- renderer.EnqueuePass(_preDepthPass);
- renderer.EnqueuePass(_RenderersPass);
- }
- }
DepthPrePassShader:
- Pass
- {
- //仅写入深度的 Pass,在这里做好 clip
- Tags{ "LightMode" = "OpaqueEarlyZPreDepth" }
- Blend One Zero, One Zero
- ZTest LEqual
- ZWrite On
- ColorMask 0
-
- HLSLPROGRAM
- #pragma prefer_hlslcc gles
- #pragma exclude_renderers d3d11_9x
- #pragma target 3.5
- #pragma multi_compile_instancing
-
- #pragma vertex DepthOnlyVertex
- #pragma fragment DepthOnlyFragment
- #define _ALPHATEST_ON
- #define SC_OBJECT
- #define USE_DISSOLVE_DISTANCE
- #include "../Include/SceneDepthOnlyPass.hlsl"
- ENDHLSL
- }
-
- Pass
- {
- //实际绘制物体的 Pass
- }
- half4 DepthOnlyFragment(Varyings input) : SV_TARGET
- {
- UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
- #if USE_ALPHATEST
- half a = 1.0;
- #ifdef _ALPHATEST_ON
- a = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv).a * _Color.a;
- a = a - _Cutoff;
- #endif
- #if USE_DITHER_ALPHATEST_CLIP
- half dist = distance(input.worldPos, _WorldSpaceCameraPos);
- a = min(a,DitherAlpha(input.scrPos, saturate(dist * 0.2)));
- #endif
- clip(a);
- #endif
- #if _Test_OUTPUT
- return half4(1.0, 0.0, 0.0, 1.0);
- #else
- return 0;
- #endif
- }