Unity載入模塊深度解析(Shader)
接上一篇 載入模塊深度解析(二),我們重點討論了網格資源的載入性能。今天,我們再來為你揭開Shader資源的載入效率。
這是侑虎科技第59篇原創文章,歡迎轉發分享,未經作者授權請勿轉載。同時如果您有任何獨到的見解或者發現也歡迎聯繫我們,一起探討。(QQ群465082844)
資源載入性能測試代碼
與上篇所提出的測試代碼一樣,我們對於Shader資源的載入性能分析同樣使用該測試代碼。同時,我們將Shader文件製作成一定大小的AssetBundle文件,並逐一通過以下代碼在不同設備上進行載入,以期得到相應的資源載入性能比較。
測試環境
引擎版本:Unity 5.2版本
測試設備:三台不同檔次的移動設備(Android:紅米2、紅米Note2和三星S6)Shader資源
Shader資源與之前的網格資源和紋理資源不同,其本身物理Size很小。一般來說,一個Shader資源的物理Size僅幾KB,在內存中也不過幾十KB。所以,Shader資源的效率載入瓶頸並不在其自身大小的載入上,而是在Shader內容的解析上。因此,我們在本文中選擇四種大家項目中最常使用的Shader資源,來讓大家更好地了解Shader資源載入的具體開銷。
測試1:不同種類的Shader資源載入效率測試
我們選取了四種當前項目中最為常用的Shader資源,它們分別是Mobile-Diffuse,Mobile-VertexLit,Mobile-Bumped Diffuse和Mobile-Particles Additive 。四組網格資源的內存佔用分別為93KB、115KB、110KB和6.9KB,其對應AssetBundle大小為10KB、7KB、12KB和3KB(LZMA壓縮)。我們在三種不同檔次的機型上載入這些網格資源,為降低偶然性,每台設備上重複進行十次載入操作並將取其平均值作為最終性能開銷。具體測試結果如下表所示。
通過上述測試,我們可以得到以下結論:
1、Shader資源的物理體積與內存佔用雖然很小,但其載入耗時開銷的CPU佔用很高,這主要是因為Shader的解析CPU開銷很高,成為了Shader資源載入的性能瓶頸;
2、Mobile/Particles Additive在解析方面的耗時遠小於Mobile/Diffuse、Mobile/Bumped Diffsue甚至Mobile/VertexLit;
3、除Mobile/Particles Additive外,其他三個主流Shader在載入時均會造成明顯的降幀,甚至卡頓。因此,研發團隊應儘可能避免在非切換場景時刻進行Shader的載入操作;
4、隨著硬體設備性能的提升,其解析效率差異越來越不明顯。
測試2:Mobile Shader vs. Normal Shader
在UWA性能測評報告中,我們發現項目中除使用Mobile Shader之外,也在大量使用Diffuse、Bumped Diffuse等Shader。二者之間在渲染方面的差別大家應該早已知道,那麼在載入方面是否也存在一定的性能差距呢?對此,我們進行了以下這組實驗。我們在測試1中的Shader資源的基礎上加入了與其對應的Normal Shader,並在三種測試設備上重複進行十次載入操作並將取其平均值作為最終性能開銷,具體測試結果如下圖所示。
通過上述測試,我們可以得到以下結論:1、Mobile Shader較之同種Normal Shader在載入方面確實有一定的性能提升;
2、設備性能越低,性能差距越大,比如Mobile/Bumped Diffuse和Bumped Diffuse的載入性能差距在紅米2低端機上達到30ms+。
看到這裡,想必大多數讀者都會產生不小的驚訝,那就是幾個小小的Shader,其載入耗時居然要高於幾張Atlas紋理或者擁有上萬片面的Mesh網格?!是的,沒錯,Shader的載入開銷經常在幾百甚至上千毫秒以上。
在UWA性能測評報告中,我們發現大量項目的Shader載入都佔據了很大的性能開銷。下圖則為一款項目在運行過程中的Shader載入耗時情況。可以看出,在切換場景處,Shader載入達到上百毫秒的CPU佔用,而在一般副本進行時,也會伴有幾十毫秒的CPU耗時。這些對於運行幀率和場景切換效率都會有很大的影響。
那麼,問題來了,我們該如何優化它呢?
在優化之前,我們首先要做的是了解Shader解析時的真正耗時原因。一般情況下,Shader載入的CPU耗時與其Keyword數量有關,Keyword數量越多,則載入開銷也越大。通過Unity 5.x的Inspector可以看到,Mobile/Bumped Diffuse的Keyword變數數量為39,Mobile/Diffuse的Keyword變數數量為27,Mobile/VertexLit的Keyword變數數量為15,Mobile/Particles Additive的Keyword變數數量為1。類似的,在Unity 4.x中,Mobile/Bumped Diffuse的Keyword變數數量為44,Mobile/Diffuse的Keyword變數數量為25,Mobile/VertexLit的Keyword變數數量為6,Mobile/Particles Additive的Keyword變數數量為0。這也是Mobile/Particles Additive解析開銷如此之低的主要原因。
注意:Shader的Keyword數量是會隨著場景設置的不同而變化的。在Unity 5.x中,Unity默認會根據場景設置、Shader Pass等來調整Shader的Keyword,比如如果存在Lightmap的使用,則會默認將對應的Keyword打開,而對於沒有使用Fog的項目,則會直接將相關Keyword關閉。
在了解了Keyword對於Shader載入效率的重要性之後,我們需要想辦法來降低Shader的Keyword數量。對此,我們建議研發團隊嘗試以下方法:
方法一:
對於Unity 5.x項目,可通過skip_variants操作在Shader中直接去除相關Keyword。
舉個例子,在Unity5.2版本中,默認情況下,Mobile/VertexLit的Keyword數量為15,如下圖所示。可以看出在Shader片段#1中存在八個Keyword,對此,我們可以在對應的Shader代碼中添加skip_variants操作將其去除,則去除後,Mobile/VertexLit的Keyword數量變為8,原來的8個Keyword不再使用,僅留下一個默認的。
由上可以看出,該方法可以有效降低Keyword的數量,但該方法同樣有一定的局限,一是目前skip_variants操作僅能在Unity 5.0以上版本中使用,二是該方法需要研發團隊對Shader具備一定程度的了解,可根據項目實際情況有針對性對Shader進行修改。
方法二:
直接去除Shader中的Fallback選項。Fallback功能是對於無法使用當前Shader的硬體設備可以使用對硬體設備要求更低的Fallback Shader來進行渲染,以保證渲染的穩定性。但是,就目前的移動市場而言,不支持Mobile/Diffuse和Mobile/Bumped Diffuse的設備已經相當少(或者說,我們目前還沒遇到不支持Mobile/Diffuse Shader的設備反饋)。因此,對於使用Mobile Shader的項目,可以嘗試直接將其FallBack去掉來大幅降低Keyword的數量。在我們的測試項目中,去掉FallBack功能,Mobile/Bumped Shader的Keyword從原來的39下降到12,Mobile/Diffuse的Keyword從原來的27下降到12。該方法不會像「方法一」那樣完全去除「無用」的Keyword,但該方法簡單易用,只需一步操作,因此,性價比很高。同時,該方法完全支持Unity 4.x引擎的項目。
讀到這裡,你肯定會有一個疑問,即就算Keyword數量可以降下來,Shader的解析效率到底會有多大的提升呢?對此,我們進行了下面的實驗:
測試3:開啟/關閉Fallback功能的載入效率測試
為簡單起見,我們直接關閉Mobile/Bumped Diffuse和Mobile/Diffuse的Fallback功能來製作一組對比數據。關閉Fallback後,這兩個Shader的Keyword數量均為12,而原始Shader的Keyword為39和27。與測試1相同,我們在三種不同檔次的Android機型上重複進行十次載入操作並將取其平均值作為最終性能開銷。具體測試結果如下圖所示。
通過上述測試可以看出,Keyword的降低確實可以大幅降低Shader的解析時間,進而提升載入效率。載入方式
以上我們通過具體實驗展示和分析了Shader的載入性能、耗時成因和優化方法。在真實的項目研發過程中,大家還需要特別關注一點,那就是Shader的載入方式。正如下圖看到的Shader載入情況,每次切換場景時,Shader載入均佔用大量的CPU耗時,而實際上,這其實是大量相同Shader重複解析造成的。究其原因,是因為Shader被打包到不同的AssetBundle文件中,每次切換場景時,AssetBundle均會被頻繁地進行載入和卸載,從而造成了大量相同的Shader被重複載入和卸載。
針對以上問題,如果你正在使用AssetBundle來載入資源,那麼我們推薦的載入方式如下:
1、通過依賴關係打包,將項目中的所有Shader抽離並打成一個獨立的AssetBundle文件,其他AssetBundle與其建立依賴;
2、Shader的AssetBundle文件在遊戲啟動後即進行載入並常駐內存,因為一款項目的Shader種類數量一般在50~100不等,且每個均很小,即便全部常駐內存,其內存總佔用量也不會超過2MB;
3、後續Prefab載入和實例化後,Unity引擎會通過AssetBundle之間的依賴關係直接找到對應的Shader資源進行使用,而不會再進行載入和解析操作。
注意:對於Unity4.x版本,Shader的AssetBundle載入後只需LoadAll即可完成所有Shader的載入和解析,但對於Unity5.x版本,除執行LoadAllAssets操作外,還需要進行Shader.WarmupAllShaders操作,因為在Unity5.x版本中,Shader的解析和CreateGPUProgram操作是分離的。
對於Unity 5.x版本且正在使用Resources.Load來載入資源的研發團隊,可以嘗試使用ShaderVariantCollection來對Shader進行Preload,同樣也可以達到避免相同Shader重複載入的效果。
注意:對於Unity5.x版本,如果可以通過AssetBundle來載入和解析Shader,則不建議通過ShaderVariantCollection來處理Shader的載入。在目前最新的Unity 5.3.5中,我們經過大量測試,發現ShaderVariantCollection在Shader的載入和管理中仍然存在一定的問題,我們暫時無法確定是否為引擎的問題,這已經不屬於本篇文章的討論範疇,在此不再贅述。
通過以上測試和分析,我們對於Shader資源的管理建議如下:
1、在保證渲染效果和項目需求的情況下,儘可能降低Shader的Keyword數量,以提升Shader的載入效率;
2、對於簡單Shader,可嘗試去除Fallback操作,該方法非常適合於目前正在大量使用的Mobile/Diffuse、Mobile/Bumped Diffuse等Built-in Shader;
3、儘可能對Shader進行單獨、依賴關係打包並對其進行預載入,以降低後續不必要的載入開銷。
以上為Shader資源在載入時的性能測試。關於載入模塊的性能問題,我們會不斷推出音頻、動畫片段等其他資源的載入性能分析、資源卸載性能分析、資源實例化性能分析、不同載入方式的性能分析等一系列技術文章,並對目前UWA所檢測過項目的共性問題進行總結,以期讓大家對項目的載入效率有更加深入的認知,並提升對載入模塊的掌控能力。
推薦閱讀:
※Linux性能優化12:網路IO的調度模型
※利用Unity UGUI製作酷炫UI效果(製作篇)
※Wukong 反作弊系統緩存的優化
※#每天一個小目標#Unity技術分享(一)