文章出處

http://www.seven-fire.cn/archives/174

Unity3D Shader加載時機和預編譯

 焱燚(七火)  |    2016年7月6日  |   UnityShader  |    0 條評論  |    5055

 

 

一、Shader與Shader Variants  

      著色器(Shader)是在GPU上執行的小程序,通常情況下,我們自己寫的一個著色器文件(xxx.shader)對應一個著色器變體,對應一個GPU程序。但如果著色器中引入了關鍵字(Keyword)或者可變的RenderSetup等技術,那么一個著色器文件就可能對應N個著色器變體,對應N個GPU程序。

      怎樣理解這里面的對應關系呢?可以這樣簡單的認為,著色器文件:著色器變體:GPU Program=1:N:N

 

      查看一個著色器對應幾個變體的方法:

      1.1、點擊著色器文件在Inspector面板中查看




      就這樣一個普通的新建的SurfaceShader就有569個變體,全編譯會生成569個GPU程序

 

      1.2、在ShaderVaruantsCollection中查看




      在這里顯示有356+4=360個變體,少了9個,原因嘛就要問Unity官方了,或者你也可以在留言中告訴我

 

 

二、加載和編譯

       加載和編譯著色器需要一點點時間。通常加載單個獨立的GPU程序不需要多少時間,但是就如上面所說,著色器有時會有很多的著色器變體。
       比如Unity 5.x的Standerd Shader,如果完全編譯的話,會有幾萬個不同的GPU程序(后面會有圖示),而這會引發兩個問題:
              1、大量的著色器變體會增加游戲打包時間,會讓游戲的總包體積變大;
              2、加載大量的著色器變體會很慢,并且消耗大量的內存。

       當然這兩個問題都是我們不愿意看到了,怎么解決呢?繼續往下看。

 

 

三、剔除多余著色器變體

       3.1、在生成時剔除多余著色器變體

       在生成游戲包時,Unity可以檢測如果某些內置的著色器變體沒有被使用到,就不會把他們打到游戲包中,這個方法可適用于以下幾種情況:
               1)個別著色器特性,比如使用 #pragma shader_feature的著色器,如果沒有材質使用到了這個特性,那么就不會把它打包進去;
               2)沒有被任何場景使用到的霧效(Fog)或光照貼圖模式(Lightmap)的著色器變體,也不會打包進去。

       通過以上兩種方式的結合可以大大減少著色器變體的數量。比如Standard著色器有35254中變體,如果完全編譯的話,就會占用有幾百兆存儲,但是一般的工程用到的只會有幾兆,而且打包時會進一步壓縮。

 

       3.2、在加載時剔除多余著色器變體

       1)Unity Shader的默認加載行為

       在默認設置中,Unity加載著色器文件到內存中,但內部使用的著色器變體(GPUProgram)直到真正使用的時候才會創建(這里要區分4.x和5.x,后面解釋)。這也就是說,打進游戲包中的著色器變體只是可能被用到,但是在用到之前是不消耗加載時間和占用內存的。
       比如,著色器常常有一個處理點光源陰影的變體,但是如果游戲中從來沒有使用過點光源陰影,那么就不會加載這個相應的變體。
當然這個默認的行為就會造成一些變體在第一次被用到的時候會出現卡頓(因為需要加載一個新的GPU程序代碼到圖形驅動中),這是不能接受的,因此Unity提供了另一個方案來解決這個問題。

PS:shader加載造成的卡頓有兩種情況:

 

       i、著色器變種已經打包到APP中,只需要加載該變體,創建GPUProgram就可以了



       ii、著色器變種沒用被打包,這時需要shaderlab文件進行解析和編譯相應的變種,然后創建GUPProgram



       以上兩種情況都是實際中遇到過的,但后一種使用目前的項目工程難以重現,所以圖片來自網絡,只作參考。等有時間了在單獨創建測試工程來重現這兩種情況。



       2)使用著色器變體群(ShaderVariantCollection)

       ShaderVariantCollection是Unity在5.x后提供的一個著色器預編譯的方案,使用它可以輕松的指定需要著色器變體,在需要的時候加載、解析、編譯等一條龍服務。
       ShaderVariantCollection其實就是一個著色器資源列表,是一個由通道類型+著色器關鍵字組合的列表。如下圖




       添加著色器窗口




       添加著色器變體就是如下一個關鍵字選擇器

 





四、創建ShaderVariantCollection

       4.1、全手工創建

       這是最笨的一種方法,需要自己在Project視圖下創建ShaderVariantCollection資源,然后使用上面介紹的兩個窗口自己添加著色器變體

 

 

 

       4.2、使用Unity收集當前使用到的著色器變體

       為了最快速、最方便的幫助我們創建和收集那些真正被用到的著色器和他們的變體,Unity已經在編輯器中內置了可以追蹤那些已經被使用到的著色器和他們的變體,并可以直接生成相應的ShaderVariantCollection資源。
       打開圖形設置面板(Edit->Project Settings->Graphics)

 



       這時候只需要依次打開我們的所有場景,把需要的物體都顯示一遍,Unity就會自動記錄下來哪些著色器的哪些著色器變體已經被使用到。統計完后只需點擊下面的保存按鈕就可以生成我們所需要的ShaderVariantCollection資源。當然你也可以為你的每一個場景或者按需生成足夠多的ShaderVariantCollection資源。

 

 

 

五、使用ShaderVariantCollection

       5.1、啟動時加載

       最簡單最粗暴的使用方式就是在游戲啟動的瞬間就直接加載ShaderVariantCollection資源并編譯里面的著色器變體,Unity已經為我們做好這一步了,依然還是在圖形設置面板里,只需把需要啟動是就編譯的ShaderVariantCollection添加在Preloaded Shaders里面,如下圖所示:

 

 

       5.2、使用代碼加載

       由于ShaderVariantCollection也是一種資源,可以跟紋理、模型等等資源一起打包和加載等,只需在加載之后調用一句WarmUp,如下:

 

 

 

六、一些后話

       目前來說,對于shader的預編譯有兩種:
ShaderVariantCollection::WarmUp和Shader::WarmupAllShaders
       該怎么選擇呢?

       1、如果你是Unity4.x,就大膽的使用Shader::WarmupAllShaders吧,因為沒得選

       2、如果你是Unity5.x

       i、沒有使用5.x新的shader(Standard和StandardSpecular),自定義shader也沒有使用大量關鍵字等還是可以使用Shader::WarmupAllShaders
       ii、使用了5.x新的shader,自定義shader也使用有大量關鍵字的話,你可以考慮使用ShaderVariantCollection::WarmUp了

       iii、還有一些很特殊的情況,就看情況而論了

 

 

 

       Unity4.x和5.x對于shader編譯上的一些差異:

       在Unity4.x中,只要把shader加載進內存后就會自動的編譯該shader,而在Unity5.x中,加載就僅僅只是加載了,如果還需要編譯的話,需要游戲中實際使用到或者主動使用shader.WarmupAllShaders又或者ShaderVariantCollection.WarmUp來編譯shader。

       至于原因嘛,大概是在Unity5.0以前,每一種shader都比較獨立,單個shader對應的變種并不多,所以5.0以前的shader的文件數量會比較多;但是到了5.0以后,引入Standard和StandardSpecular這兩個shader后,由于這兩個著色器的變種實在太多(Standard有35254個變種,StandardSpecular有32950個變種),如果這兩個著色器全編譯,后果就是內存一下少了幾百兆(當然這是官方的說法,實際測試貌似不是這樣的)。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()