如何使用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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章