从实际项目升级中关于 Unity SRP 的一些评测

Untiy 推出SRP 已经接近一年了,其中官方宣称 LWRP 在2018年年底时已经处于 production ready 既随时可以做产品了,于是改名为URP, 不过 HDRP 还需要2019.4 的到来才能到达完整版。 不过在我看来 URP 还不能说是 production ready 还处于玩具阶段。而且有时候觉得Unity官方对于技术路线偶尔会出现不明确,左右摇摆的情况。比如Unity 2018 新出的Camera.AddCommandBuffer 来做自定义渲染, 这在 Unity 2019 被废除了,取而代之使用 Render Feature /ScriptableRenderPass 来实现,不过这东西也处于实验阶段。

SRP不做任何修改是否可以直接提高项目性能,答案是可以直接减少CPU给GPU准备阶段的性能大约10%左右。无法直接提升GPU的渲染性能,对于不使用任何光照的项目且处于Opengl ES2.0 这类低端机,基本没有任何GPU性能提升。

以目前使用的SRP 有大量Bug 举两个例子
1. [In order to call GetTransformInfoExpectUpToDate, RendererUpdateManager.UpdateAll must be called first.] 莫名的内置渲染错误,无法自己修改。
官方Issue链接 https://issuetracker.unity3d.com/issues/errors-message-at-editor-play?_ga=2.202176470.695125147.1571176891-1511937231.1511185188
2. 使用渲染指令Blit 后,会导致RenderTarget 无法自动恢复原始RenderTarget,需要手动还原SetRenderTarget,这个在之前的CommandBuffer 里都不曾遇到

在项目中期切换到SRP可以直接优化的地方
1.相机Culling优化

https://connect.unity.com/p/unityzhi-zuo-ren-zhuan-chang-unity-aaayou-xi-shen-du-you-hua-zhu-ti-yan-jiang

根据官方优化参考,使用SRP后,可以控制相机 Culling(裁剪)行为,对于项目中有自己实现基于投影器Projector的阴影相机可以复用主相机的 Culling结果, 对于UI上模型RT相机可以不做任何Culling

2.相机 Stack 优化
SRP废弃了多个相机的实现,无法再使用多个相机 (比如我们项目1个GamePlay, 1个HUD, 1个UI相机的。使用官方SRP模板,UI相机背景色会盖住场景内容),原因为

如果只使用1个相机,渲染结果可以直接写入BackBuffer

如果有多个相机,由于第二个相机需要第一个相机结果填充画布后再渲染,因此至少需要一张RenderTexture的临时缓冲,且还需要针对不同的Viewport做裁剪等等,写入backBuffer的时机也会延迟

官方文档废弃Camera Stack原因
https://docs.google.com/document/d/1GDePoHGMngJ-S0Da0Fi0Ky8jPxYkQD5AkVFnoxlknUY/edit

3.UI OverDraw 优化
使用同一个相机绘制UI后,可以考虑给UI添加模板测试,将UI挡住场景的部分,场景可以不被绘制到。

4.UI 批次合并(Opengl 3.0+ Unity2019.2+ with SRP Batcher)
对于场景特效类,基本都无缘SRP batcher 他对Cbuffer的容量有限制
对于UI如果全局自定义Shader可以使用 SRP Batcher 不过目前还是实验阶段。

最后来说下 Camera.AddCommandBuffer 这个功能在 Unity 2019 替换为 ScriptableRenderPass 后如何实现一个XRay
使用 CommandBuffer时仅仅需要 camera.AddCommandBuffer(CameraEvent.AfterForwardOpaque, m_XRayBuffer);
然后再XRayBuffer.drawRenderer(renderer, XrayMat)即可

在2019里 需要创建XrayRenderPassFeature 类来实现
public class XRayRenderPassFeature : ScriptableRendererFeature

ScriptableRendererFeature有2个接口要实现分为
Creata() 创建一个实现具体Xray Pass的接口
AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) 将创角的pass 添加进renderer 队列

在XRayRenderPassFeature 里实现一个 CustomRenderPass : ScriptableRenderPass 来编写具体Xray逻辑
Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) 准备阶段
Execute(ScriptableRenderContext context, ref RenderingData renderingData) 渲染阶段
FrameCleanup(CommandBuffer cmd) 清理阶段

基本实现都在Configure里

1
2
3
4
5
6
7
                    CommandBuffer xraycmd = CommandBufferPool.Get(m_profilerTag);

                    xraycmd.DrawMesh(m_drawMesh, m_xrayTarget.transform.localToWorldMatrix, m_xrayMaterial);
                    context.ExecuteCommandBuffer(xraycmd);

                    CommandBufferPool.Release(xraycmd);
                }

大致流程是,Renderer 会根据 pass 的 renderPassEvent 进行和内置其他pass 比如天空盒,点光,深度 等等其他pass 一起sort, 之后分别在渲染前,渲染,渲染后调用接口

补充一个以官方的SRP FPS Demo 基础来实现XRay
git:https://github.com/Unity-Technologies/UniversalRenderingExamples

1.在FpsSetup 预制体里添加刚刚创建的Feature

2.编写一个简单ZTest Greater的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
Shader "Unlit/XrayShader"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" "LightMode"="LightweightForward" }
        LOD 100

        Pass
        {
            ZTest greater
            offset -1,-1

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/core.hlsl"
   

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                return o;
            }

            float4 frag (v2f i) : SV_Target
            {
                return float4(1,0,0,1);
            }
            ENDHLSL
        }
    }
}

3.在场景中放置一个示例Cube,取名为XRayTarget

最后运行游戏

最后 使用自定义 ScriptableRendererFeature 的话,还需要自己编写对应的Editor代码,比之前繁琐许多。

如果自己来编写SRP的话,RenderPassFeature 需要自己维护pass列表来实现, 也可以仅仅去实现自定义的ForwardRenderer,可以减少很多功能的重复造轮子。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.