URP延迟渲染+Native Renderpass踩坑记录
过去两周多折腾URP Deferred + Native Renderpass, 踩了无数的坑, 翻遍国内国外的社区也找不到太多可供参考的资料. 于是在这里简述一下一些不可回避的问题以及其解决方案, 也算是为社区做点贡献.
在开始之前, 我想延迟渲染这种烂大街的玩意就没什么讲的必要了, 而Native Renderpass可能不少人没听过, 这东西底层调用的其实就是vulkan的renderpass/subpass API(metal也有类似的一套), 简单来说就是利用移动端TB(D)R的硬件架构, 相比于传统延迟管线在basepass结束后将gbuffer store回system memory, 之后再在lightpass中load回来这种带宽压力极大的方案, Native Renderpass可以在每个tile的basepass结束后将gbuffer保存在On-Chip Memory上, 以供接下来的lightpass直接使用, 直接优化掉了两个pass之间的store/load操作, 极大减缓了带宽压力, 这种形式的rt也被称之为memoryless.
简单科普到此为止, 详细内容可以看vulkan官方讲subpass的ppt(很好找)或者是其他知乎大佬的文章.
那么接下来就直接讲我踩到的几个坑以及解决方案. 因为是项目素材所以截图是肯定不能放的, 感兴趣可以自己试试去复现, 当然只是URP源码部分的代码我会贴出来.
开启Native RenderPass时激活SceneView窗口导致Native RenderPass失效
很诡异的问题, 不只是在Editor中会这样, 甚至还会影响打包的结果.
开始以为是Bug, 分析源码后发现其原因在于源码中对Native RenderPass的开启做了两次二重判定:
1 | // UniversalRenderer.cs |
也就是说在Scene窗口激活时, 即使开启Native RenderPass(useRenderPassEnabled == true), 也是无效的.
虽然通过修改代码可以强行开启, 但Scene窗口会渲染异常, 同时疯狂报错. 所以解决方案就是framedebug/打包等等操作时找个窗口盖住scene窗口, 然后重新开关Native Rednerpass即可.
充分怀疑是SceneViewCamera存在Unity暂时解决不了的Bug或没有对vk renderpass做适配.
吐槽: 这么重要的问题文档一句不提???
开启Native RenderPass后自定义的RendererFeature渲染出错
图就不放了, 总之打包到移动端真机后哪个tile用到了自定义的RendererFeature哪个tile就马赛克, 完全炸了.
原因是ScriptableRenderer.cs里特别定义了变量用于控制RendererFeature的Native Renderpass的开闭:
1 | // Temporary variable to disable custom passes using render pass ( due to it potentially breaking projects with custom render features ) |
解决方案是在自定义的RendererFeature里重写SupportsNativeRenderPass()函数, 在明确知道符合原条件的情况下, 直接返回ture即可.
1 | public override bool SupportsNativeRenderPass() |
这里需要注意的是父类ScriptableRendererFeature里定义的SupportsNativeRenderPass()函数的访问级别是internal, 想要在自己的命名空间里重写该函数就要全改成public.
吐槽: 这种函数为什么要internal? 不考虑用户扩展?
开启Native RenderPass后移动Camera, Light Layer会出现错位/拖影
逆天大坑, 感兴趣的朋友可以自己试试开个默认场景然后改改light layer再往手机上打个包, 简单说就是在移动camera时会出现light layer不同步的拖影, 实际表现就像是light layer的屏幕空间位置刷新跟不上角色的刷新一样.
折腾了很久后终于排查到了问题所在:
因为Vulkan的一个renderpass会在所有subpass结束后再把要store回system memory的数据store, 也就是说LightPass里load进来的并不是同一帧里前一个subpass(BasePass)生成的light layer, 而是上一帧renderpass结束后store回system memory的light layer, 故而导致移动摄像机时会拖影.
解决方案: 在DeferredLights.cs的Setup()函数中修改以下两处定义为:
1 | //this.DeferredInputAttachments = new RenderTargetIdentifier[4] |
可以看到URP在源码里写死了InputAttachment最大数量为4, 通过DeferredInputAttachments数组指定其为gbuffer0/1/2/4, 并通过DeferredInputIsTransient数组指定对应的gbuffer是否为memoryless, 所以导致light layer只能从system memroy load上一帧的结果. 这恐怕也是因为Unity考虑到要兼容部分只支持四张InputAttachment的Vulkan设备而做的妥协.
C#端开启完毕后, 就可以在shader里用InputAttachment的方式从On-Chip Memory里读取当前帧在basepass生成的lightlayer了:
1 | // StencilDeferred.shader |
这样一来gbuffer5就成功被设置成了memoryless, 再次打包发现light layer错位/拖影的现象已经消失了.
不过真机测试时, Redmi K30 Pro(865)是可以正常渲染的, 而Mi 8(845)就会出现花屏. 具体是不是因为845不支持大于四张的InputAttachment还有待验证与查阅资料.
吐槽: 前面那种小坑文档不说明也就算了, 这种也不说明? 全靠用户琢磨, 你以为你是UE?
大概就是这样, 希望可以帮到遇到同样问题的朋友.