我最近發現了一個與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