Shader:優化破解變體的“影分身”之術

本期我們將剖析剛上新的Shader Analyzer中和Shader變體相關的規則“Build後生成變體數過多的Shader”、“項目中可能生成變體數過多的Shader”和“項目中全局關鍵字過多的Shader”。我們將力圖以淺顯易懂的表達,讓職場萌新或優化萌新能夠深入理解。

首先我們來了解下相關的概念與意義。


1、什麼是Shader 和 變體(Variant)?

Shader從字面意義來講就是“着色器”;功能上來講就是用以實現圖形渲染的一種技術,更直白地說就是一段實現特定功能的代碼程序。Unity工程中可以說所有物體的顏色、光照效果或質感等等,都和Shader有千絲萬縷的關聯。如圖,Unity 2019.3.7中在Project界面可以創建多種預設的不同類型的Shader。

很多時候,不同效果之間只有一些微小的差距,爲每一種渲染效果去專門寫一個Shader是很不現實的。從設計原則的角度講,我們應當儘可能共用重複的代碼,而Shader的關鍵字(Keyword)就爲我們提供了這個功能。

開發人員在寫Shader時,可以在Shader的代碼段中去定義一些關鍵字,然後在代碼中根據關鍵字開啓與否,去控制物體的渲染過程。如此一來同一份Shader源碼就可以具備多種不同的功能。另外,我們可以在Runtime通過開啓或關閉關鍵字的方式動態改變渲染效果。

這樣在項目最終編譯的時候,引擎就會根據不同的關鍵字組合去生成多份Shader程序片段。每一種關鍵字組合對應生成的程序就是這個原始Shader的一個變體(Variant)。

 

2、什麼是關鍵字(Keyword)?

通俗地說,Shader中的關鍵字就是一個個標籤,方便材質在渲染時綁定不同的Shader變體,實現不同的效果。我們可以在Shader片段中使用編譯指令(compile directives)來定義Shader關鍵字。從變體生成特點上可分爲“multi_compile”和“shader_feature”兩類,從作用範圍角度可分爲局部關鍵字和全局關鍵字。

在Unity中multi_compile類型的關鍵字定義方式如下:

該編譯指令會導致編譯時生成所有關鍵字組合的的變體,如下圖:

而shader_feature類關鍵字定義方法如下:

一般來講,帶有multi_compile類關鍵字的Shader,在Build時會把所有可能的關鍵字排列組合的變體全部生成,由此導致不必要的冗餘和包體體積增大;但好處是方便動態選擇Shader變體;

而對shader_feature而言,Unity在Build時,不會將未使用的shader_feature關鍵字生成的變體 包含入內,只有實際被材質使用到的關鍵字對應的變體纔會被Build和打入包中,從而減少了內存佔用,精簡了包體體積。

但代價是自己要做額外的工作,舉例來說,有些shader_feature關鍵字對應的變體在Build時沒有被材質使用到,但是在運行時可能會通過代碼開啓。這類變體實際需要使用,卻沒有被打入包中 ,就會導致理想中的效果無法生成。這時,就需要使用Shader Variant Collection,手動將這些體加入到變體收集器裏面。

需要說明的是,shader_feature 預編譯指令行至少有兩個關鍵字。如果只定義了一個關鍵字KW_X,則會默認生成一個下劃線關鍵字。以下兩行指令等價:

一般Shader片段中multi_compile類關鍵字每增加一個,或者啓用的shader_feature類關鍵字增加一個,該Shader的變體數量就會增加一份。而對於變體數與內存、顯存的關係,UWA曾做過以下實驗:

使用#pragma multi_compile定義的一行關鍵字爲一組,每組包含兩個關鍵字,對產生的內存進行統計,結果如下:

由此可見變體數和ShaderLab的內存佔用基本成正比。而由於沒有使用Shader進行渲染,GfxDriver內存不會增加,沒有參與渲染的Shader變體是不會經歷CreateGPUProgram傳入GfxDriver內存中的。

然後我們來結合這次新功能中的相關規則進行具體說明變體對項目優化的意義。

 

3、可能生成變體數過多的Shader

對Unity項目而言,Shader變體有其存在的積極意義。除了代碼的共用與運行時渲染效果的動態改變之外,還增加了Shader程序在GPU上的執行效率。

對GPU來說,處理類似於“if-else”結構的分支語句不是它的強項,GPU的特點和功能決定了它更適合去並列地“執行”重複性的任務,而不是去“選擇”。所以Shader變體的存在就很好地解決了這個問題,GPU只需要根據關鍵字去執行對應的Variant內容就可以,避免了性能下降的可能。同時,項目在運行時,可以通過在代碼中選擇不同的Shader變體,從而動態地改變着色器功能。

但是Shader變體是一把雙刃劍。在帶來以上便利的同時,也存在着各種問題:

1)在Build階段,過多的Shader變體數量會使得Build耗時明顯上升,而最終的項目包體體積也會變得臃腫。

2)在項目運行階段,Shader變體會以其龐大的數量產生可觀的內存佔用,同時也會導致項目加載時間的增加,也就是俗說的“卡頓”。

所以本條規則會掃描項目中的Shader腳本,根據項目中Material上開啓的關鍵字情況去計算可能生成的變體數。開發團隊可以在找出這些可能生成過多變體數的Shader後,結合項目實際情況去進行相應的修改。

 

4、全局關鍵字過多的Shader

由於Unity支持的全局關鍵字的總數有限(256個全局關鍵字,64個局部關鍵字),而Unity內部關鍵字已經佔用了約60個“名額”,所以我們建議開發團隊儘可能使用局部關鍵字(shader_feature_local和multi_compile_local)。本條規則會對所有預編譯指令定義的關鍵字進行識別,找出那些全局關鍵字過多的Shader以方便開發團隊進行進一步的檢查與修改。

 

5、Build後生成變體數過多的Shader

項目進行打包(Build)的時候,會將項目實際使用的資源封裝到包裏面(如Scenes In Build中的場景依賴的所有資源等)。因此,並非所有的Shader資源都會被帶入包中。另外,本文介紹的第一條規則,僅會檢測目標路徑下的Shader腳本文件,對於項目使用的一些內置的(Built-in)Shader則無法檢測到。所以本條規則的意義,就在於統計打包後實實在在使用的Shader資源對應的變體。

我們模擬了項目的Build流程,將那些在Build後生成變體數過多的Shader統計出來,方便開發團隊根據項目的實際需求去進行進一步的檢查和修改。(此外需要說明的是,本規則只支持Unity2018.2及其以上的版本。)

希望以上這些知識點能伴隨本次的功能更新而在實際的開發過程中爲大家帶來幫助。需要說明的是,每一項檢測規則的閾值都可以由開發團隊依據自身項目的實際需求去設置合適的閾值範圍,這也是本地資源檢測的一大特點。同時,也歡迎大家來使用UWA推出的本地資源檢測服務,可幫助大家儘早對項目建立科學的美術規範。

 

往期優化規則,我們也將持續更新。
《動畫優化:關於AnimationClip的三兩事》
《材質優化:如何正確處理紋理和材質的關係》
《紋理優化:讓你的紋理也“瘦”下來》
《紋理優化:不僅僅是一張圖片那麼簡單》



 

萬行代碼屹立不倒,全靠基礎掌握得好!

 

性能黑榜相關閱讀

《那些年給性能埋過的坑,你跳了嗎?》
《那些年給性能埋過的坑,你跳了嗎?(第二彈)》
《掌握了這些規則,你已經戰勝了80%的對手!》

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