前言
這篇博文是對最近遇到的一次渲染材質(zhì)異常的分析和梳理,打算以簡練的形式記錄下來以備后續(xù)查閱。PS. 個人深感像渲染引擎這樣龐大的工程,要想駕馭得又好又快,還是得靠點滴積累的經(jīng)驗。
正文
原本的問題現(xiàn)象是這樣的:某項目組在美術工程中正常表現(xiàn)的材質(zhì),將資源打包bundle導入到程序工程后出現(xiàn)了表現(xiàn)上的異常(變暗),同時原本支持的實例化能力也消失了。初步排查后發(fā)現(xiàn),程序工程并沒有讀取到AssetBundle內(nèi)正確的著色器資源變體(shader variant),項目所需的變體看來并沒有成功打包到bundle內(nèi)!
Unity在無法正確獲取目標著色器變體時會按照某種“Fallback”規(guī)則定位到另一個存在的備胎,具體可以參考這篇博文中的“變體調(diào)用規(guī)則”部分。我們知道材質(zhì)“變暗”是由于這種Fallback機制所致,不過我們并不關心Unity最終選擇了哪個shader,真正關心的是為何項目所需的變體沒有進入bundle。
第一步是去美術工程確認變體收集情況,可以在目標場景下通過 ProjectSettings->Graphics->Save to asset按鈕激活收集邏輯,保存當前場景所有著色器變體信息到本地。參考如下圖示:

這里面每一個shader可能對應復數(shù)個變體,每個變體是由一種不同與其他變體的KeyWords組合決定的,如果你在編寫shader時使用“shader_feature”去控制/定義關鍵字,那么Unity在收集變體時會判斷該材質(zhì)實際可能使用到的關鍵詞,自動組合,生成如上圖這樣的變體列表。
在美術工程中收集的變體列表中,我們發(fā)現(xiàn)了所需的變體,參考下面節(jié)選代碼標注部分。
unity_collected.shadervariants
- first: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3}
second:
variants:
- keywords:
passType: 8
- keywords: INSTANCING_ON _ALPHATEST_ON
passType: 8
- keywords: _ALPHATEST_ON
passType: 8
- keywords:
passType: 13
//以下是問題變體以及構成該變體的關鍵詞組合:
- keywords: DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON LIGHTMAP_ON _ADDITIONAL_LIGHT_SHADOWS
_MAIN_LIGHT_SHADOWS INSTANCING_ON _NORMALMAP _OCCLUSIONMAP _SHADOWS_SOFT
passType: 13
- keywords: _ADDITIONAL_LIGHT_SHADOWS _ALPHATEST_ON _MAIN_LIGHT_SHADOWS _METALLICSPECGLOSSMAP
_NORMALMAP _OCCLUSIONMAP _SHADOWS_SOFT
passType: 13
- keywords: _ADDITIONAL_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS _METALLICSPECGLOSSMAP
_NORMALMAP _OCCLUSIONMAP _SHADOWS_SOFT
passType: 13
- keywords: _ALPHATEST_ON
passType: 13
美術工程能夠收集到目標信息,但是打包時沒有打入AssetBundle,這種情況下只有可能是打包邏輯在作祟,于是決定利用Vistual Studio的掛載斷點功能單步下具體打包邏輯。
首先是構造出打包的目標資源列表,通過官方提供的 “AssetBundlesBrowser”插件觸發(fā)Unity自己的打包邏輯,具體不贅述。

接下來是要點,打包方法的入口邏輯在 URP自己的Editor工程目錄下的 ShaderPreprocessor.cs文件內(nèi),具體而言是一個叫“OnProcessShader”的方法。我們可以通過實現(xiàn) “IPreprocessShaders”這個接口中的“OnProcessShader”來重載shader variant打包處理邏輯,這是后話了??傊晒帱c進入目標邏輯:

這個函數(shù)的入?yún)⒑芮逦?,shader就是目前在處理的待打包的著色器本體,snipperData則是一系列Unity收集好的屬性信息,最后一個List存放了所有在變體收集階段獲得到的變體信息。該方法內(nèi)有嵌套的兩次循環(huán),外層循環(huán)負責遍歷所有變體,內(nèi)層循環(huán)負責校驗參與構成當前變體的所有關鍵字是否“合法可用”,由“StripUnused”負責處理。
//URP-Editor::ShaderPreprocessor.cs
public void OnProcessShader(Shader shader, ShaderSnippetData snippetData, IList<ShaderCompilerData> compilerDataList)
{
...
for (int i = 0; i < inputShaderVariantCount;)
{
bool removeInput = true;
foreach (var supportedFeatures in ShaderBuildPreprocessor.supportedFeaturesList)
{
if (!StripUnused(supportedFeatures, shader, snippetData, compilerDataList[i]))
{
removeInput = false;
break;
}
}
...
}
...
}
然后很快就在如下位置發(fā)現(xiàn)了蹊蹺:打包代碼阻止了關鍵詞“Additional_Light_Shadows”進入變體,這會使得所有包含了該條關鍵詞的著色器變體文件無法生成!
//URP-Editor::ShaderPreprocessor.cs
bool StripUnusedFeatures(ShaderFeatures features, Shader shader, ShaderSnippetData snippetData, ShaderCompilerData compilerData)
{
...
// No additional light shadows
if (IsFeatureEnabled(ShaderFeatures.ShadowsKeepOffVariants, features))
{
if (stripTool.StripMultiCompileKeepOffVariant(m_AdditionalLightShadows, ShaderFeatures.AdditionalLightShadows))
return true;
}
else
{
if (stripTool.StripMultiCompile(m_AdditionalLightShadows, ShaderFeatures.AdditionalLightShadows))
return true;
}
...
}
通過進一步追蹤,在如下代碼中發(fā)現(xiàn),美術工程并沒有開啟對AdditionalLightShadows的支持!
//URP-Editor::ShaderPreprocessor.cs
private static ShaderFeatures GetSupportedShaderFeatures(UniversalRenderPipelineAsset pipelineAsset, int rendererIndex)
{
...
if (pipelineAsset.additionalLightsRenderingMode == LightRenderingMode.PerPixel || clusteredRendering)
{
if (pipelineAsset.supportsAdditionalLightShadows)
{
shaderFeatures |= ShaderFeatures.AdditionalLightShadows;
}
}
return shaderFeatures;
}
該項(supportAdditionalLightShadows)可以通過查看pipelineAsset文件獲知,而這個Asset就是URP渲染管線的管線資源文件,可以在Grapics頁簽下的“Scriptable Render Pipeline Settings”中找到。

我們在Inspector頁簽中觀察目標Asset文件,記得只有切換到Debug模式才能看到所有設置數(shù)據(jù)。
下圖中紅框標注的地方就是所謂的控制標識符“supportsAdditionalLightShadows”數(shù)值的地方,美術工程中并沒有開啟額外光源的陰影渲染。

事實上出于性能考量,項目組并不打算要開啟主光源之外燈光的陰影渲染,那么為何我們在變體收集階段還是能收集到這一項關鍵詞呢?經(jīng)過進一步診斷后發(fā)現(xiàn):
Unity收集shader變體的工作是在運行時進行的,urp管線默認總是會嘗試添加 AdditionalLightsShadowCastPass,如果在收集變體的美術場景中不小心放入了額外的光源,使得場景總光源數(shù)大于1,那么如下運行時代碼的Setup操作就會返回true,這樣如果你的shader中帶有 shader_feature Additional_Light_Shadow 這樣的變體關鍵字,那么就會被成功收錄到變體序列中,與其他重要且不可或缺的KeyWords黏合到一個變體中去。
//URP-Runtime::UniversalRenderer.cs
public override void Setup(ScriptableRenderContext context, ref RenderingData renderingData)
{
...
bool additionalLightShadows = m_AdditionalLightsShadowCasterPass.Setup(ref renderingData);
...
if (additionalLightShadows)
EnqueuePass(m_AdditionalLightsShadowCasterPass);
}
最后我們在美術場景中找到多余的那一盞“點光源”,將之去除后再次收集+打包,成功輸出了目標shader variant。
后記
我們最好能通過覆寫 “IPreprocessShaders”的方式,給shader打包邏輯增加必要的告警,盡量避免或提早發(fā)現(xiàn)某些變體關鍵詞被拒絕后導致的連坐其他重要關鍵詞(比如 Instance_On)的情況。
以上!