如何使用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://developer.apple.com/documentation/metal/shader_authoring/developing_and_debugging_metal_shaders

 

https://docs.microsoft.com/en-au/learn/?WT.mc_id=DT-MVP-5001664

 

欢迎大家关注我的公众号"慕容的游戏编程":chenjd01

posted @ 2020-02-25 13:14  慕容小匹夫  阅读(2038)  评论(2编辑  收藏  举报