如何使用Xcode调试Shader代码Bug导致的渲染问题
我最近发现了一个与Unity中的表面着色器有关的小Bug。 你可以看到如下所示的渲染瑕疵。
有时人们会将相似的渲染瑕疵归因于同时使用HDR和Bloom效果,但实际上,表面着色器是错误的,至少在本文中所讨论的情况是这样的。
所以我写这篇文章来记录调试此问题的过程。 同时,本文还将介绍如何使用Xcode的工具调试着色器代码以查找存在的渲染错误。
The Debugging process
如果你想调查iOS上的渲染问题,只需从Xcode生成并运行你的项目,然后单击Xcode调试工具栏上的照相机按钮即可启用Metal Frame Debugger。
ref apple doc
然后选择你感兴趣的render encoder,例如与Bloom downsample相关的encoder。
bloom process
如你所见,在Bloom的Downsample过程开始时,有一个看起来不正常的像素。此时我们可能已经找到了目标。 但是在Bloom过程中,纹理已被Downsample。 因此为了找到原始像素,让我们来探索一下在Bloom之前的render encoder,以查看是否还有其他有价值的发现。
encoder 0
乍一看,似乎没有什么异常,然后让我们修改Attachment的设置以过滤出我们的目标。 (顺便说一句,右键单击Attachment,你可以选择显示可见的叠加层,例如线框等等,还可以渲染结构垂直翻转等等)
encoder 0
它在这里! 就像夜空中最闪亮的星星(此处我有点怀疑zhihu的压缩图片)。 😄
找到目标像素后,我们可以使用着色器探查器工具即Shader Profiler来调试该像素的像素着色器代码。 看到它的值非常不正常,我几乎可以肯定这个“Legacy Shaders/Bumped Specular”着色器中存在一个Bug。
R: 50176.0 G: 4992.0
选择目标像素,然后单击“Debug”按钮。 让我们看看到底问题出在哪!
u_xlat16_3.x = dot(input.TEXCOORD1.xyz, float3(u_xlat16_2.xyz));
u_xlat16_3.y = dot(input.TEXCOORD2.xyz, float3(u_xlat16_2.xyz));
u_xlat16_3.z = dot(input.TEXCOORD3.xyz, float3(u_xlat16_2.xyz));
u_xlat16_0.x = dot(u_xlat16_3.xyz, u_xlat16_3.xyz);
u_xlat16_0.x = rsqrt(u_xlat16_0.x);
u_xlat0.xyz = float3(u_xlat16_0.xxx) * float3(u_xlat16_3.xyz);
如你所见,dot(u_xlat16_3.xyz, u_xlat16_3.xyz).x
的结果为0,然后rsqrt运算符对该值进行运算。 是的,倒数平方根为0时结果会无穷大,而负值的平方根则返回NaN(不是数字)。 我认为我们找到了着色器代码的错误。
简而言之,着色器代码会在此处进行不安全的归一化计算。
对float3向量的归一化的实现,可以是下面这样。
float3 normalize(float3 v)
{
return rsqrt(dot(v,v))*v;
}
ShaderLab代码
许多Unity开发人员都知道,我们在Unity中编写的着色器称为ShaderLab,然后Unity会将ShaderLab编译为用于各个图形库/平台的着色器代码。
Legacy Shaders / Bumped Specular
是内置的着色器。 因此,我们可以从Unity下载页面下载其ShaderLab代码,并使用IDE打开该文件。
surface shader code
如上所示,这个着色器的代码非常非常简单,并且没有针对矢量的归一化操作。 为什么?
因为它是Unity的表面着色器。 而Unity中的表面着色器的实现其实是一种基于代码生成的方案,即Unity会生成一些额外的代码,以简化编写着色器的复杂度。
如果要查看从表面着色器生成的真实的ShaderLab代码,可以单击着色器Inspector窗口上的“show generated code”按钮。
show generated code
然后,我们获得了真实的vertex/pixel ShaderLab代码,这些代码稍后将被编译为对应图形库的着色器代码。 除此之外,我们还发现了Unity生成的不安全的归一化操作代码。
generated shader code
Workaround
当然,正如我在本文开头所说,本文的目的主要是记录调试着色器代码Bug的过程,并介绍Xcode的Shader Profiler工具。 但是,该问题或类似问题应如何解决呢?
我个人认为就是不要使用表面着色器。 因为它们的大多数代码是由Unity生成的,所以你无法控制细节。 因此大家可以选择使用传统的标准着色器,也可以考虑使用新的用于可编程渲染管线的着色器。
Useful Links
https://docs.microsoft.com/en-au/learn/?WT.mc_id=DT-MVP-5001664
欢迎大家关注我的公众号"慕容的游戏编程":chenjd01