前言
有项目打包输出的Demo在Vulkan渲染引擎下会频繁出现花屏现象(反馈者的描述),本文简要记录了对该问题的快速整段定位以及修复方案,以备之后查阅。
问题描述
观察发现花屏主要分布在虚化的背景板中,参考下图:
定位问题
粗略定位:
因为花屏并不是在帧缓存的任意位置,且有明显的图层分布趋势,我们推测是某个功能相机内部的问题,但是具体定位问题的最佳手段还是截他一帧!
精细定位:
手机连接RenderDoc截帧,展开后逐相机检查,很快发现在处理背景3D层的专门相机输出处产生了异常的花屏纹理。
挨个查阅了LightCookies、MainLightShadow、ColorGradingLUT、DrawOpaqueObjects、RenderSkyBox、DrawTransparentObjects等Pass都没有确认到任何异常,但是当检测到“Render PostProcessing Effects”中的Blur Pass时出现了显示异常!
很明显FBO的colorattachment被错误的纹理覆盖了,而depth_attachment本身也是一团错误!
观察深度缓冲中白色部分对应的像素能够在颜色缓冲中正常输出,但是黑色部分被阻挡。我们知道白色在[-1,1]
空间中代表最大值1
,黑色代表最小值-1
。作为后处理的全屏矩形面片在变换到OpenGL的NDC(规范化设备坐标系)后,它的深度z
值为0
,介于[-1,1]
区间的中心处(相当于处在深度一半的地方)。如果开启了Z-Test,并且比较函数是 LessEqual
(这些都是Unity的缺省默认设置),那么自然而然深度缓冲中黑色区域会导致对应像素的深度测试失败,从而显示出花屏现象。
往前翻了翻帧缓存使用的深度缓冲,找到了如下管线操作:
首先是管线调用了glInvalidateFramebuffer指令,用于标记帧缓存4679为Invalid状态,既这块GPU内存既不会从Tile(片)上读取数据,也不会把数据写入Tile,以此节约cache line。而从下图箭头部分可知,4679这个帧缓冲里连带的 depth_attachment也会被处理。
其次在Framebuffer 4679的初始化参数中我们可以看到它的深度缓冲是绑定到了一块名为TempBuffer 131的内存段上的。
最终我们确认了问题DC(Eid=1294)所使用的FB0 深度缓冲纹理正是上文提到的TempBuffer 131,这块内存其实在使用前已经被标记成了Invalid状态,其上的数据早已不可靠了。
就此,我们定位到了问题的本质:在名为Blur的后处理全屏Blit渲染阶段,Shader错误的开启了深度测试,同时深度缓冲本身在之前被标记成了Invalid状态,视环境不同,这块缓冲上可能被随机刷上各种异常数据。最终在深度测试的“过滤”下,输出图像形成了“花屏”样纹理。
解决方案:
最简单的是直接修改Blit使用的Shader
SubShader
{
Pass
{
Cull Off
ZTEST Off //这里直接关闭深度测试
HLSLPROGRAM
...
或者参考urp的做法,设置渲染输出使用的RT,让它的depthBufferBits等于0即可。
void DoCustomBlur(CommandBuffer cmd, RenderTargetIdentifier source)
{
int tw = m_Descriptor.width;
int th = m_Descriptor.height;
var opaqueDesc = m_Descriptor;
var lastDown = source;
int iterations = m_blurSetting.iterationTimes.value;
var mat = m_Materials.Blur;
for (int i = 0; i < iterations; ++i)
{
tw = tw / 2;
th = th / 2;
opaqueDesc.width = tw;
opaqueDesc.height = th;
opaqueDesc.depthBufferBits = 0; //直接把深度缓冲的大小设置为0,取消深度测试
opaqueDesc.msaaSamples = 1;
...
以上!