之前說了很多shader關鍵字的事情,本篇好好說一下關鍵字和變體。
關鍵字是干什么的
我們寫shader的時候,經常會遇到需要處理不同的情況,比如是否啟用霧,光源是平行光還是點光源,是否使用法線貼圖等等。如果為每一種情況都寫一個單獨的shader,那么這些不同的條件就會組合出非常多的可能性,要寫太多的shader顯然不現實。那么另外一種方法就是像寫程序那樣在shader里面進行邏輯判斷,如果直接用if
就屬于動態分支了,對效率有一定的影響,另外就是像c語言預處理宏那樣使用#if
,#ifdef
這類,而此時的條件就是關鍵字。編譯器會對關鍵字進行預處理,從而產生匹配當前所使用的關鍵字的shader版本,比如開啟霧,使用平行光但不使用法線貼圖的shader版本。
什么是變體
每種關鍵字的組合對應的shader版本就是一個shader變體。所謂變體,就是同一份shader源碼,由于啟用了不同的關鍵字組合,經過編譯器預處理就得到了最終不一樣的shader代碼。
使用關鍵字還是使用動態分支
使用關鍵字可以避免動態分支,從單個shader的效率來說是最高的。但是使用關鍵字會造成變體增多,這就意味著GPU需要切換更多的變體來完成渲染。之前我們討論的SRP Batcher的原理就是只要變體不切換,可以高效的重新綁定CBuffer完成draw call,如果變體切換了只能調用一次set pass call,這就打斷了SRP Batcher。根據Unity的建議,盡量減少變體,讓SRP Batcher包含的draw call數目盡量多是首選。當然了,按照我自己的經驗,如果動態分支實在太費,比如會增加貼圖的采樣次數,或者非常復雜的計算,且這個分支在一個warp中是不可能一致的,那么就還是用關鍵字來代替動態分支吧。當然最靠譜的是需要經過profile來決定。
聲明shader keywords
有兩種聲明關鍵字的指令
#pragma multi_compile
- 聲明一組關鍵字,比如
#pragma multi_compile QUALITY_LOW QUALITY_MEDIUM QUALITY_HIGH QUALITY_ULTRA
- 默認情況下關鍵字是全局作用域的(即針對所有的shader)
- 并且影響所有的shader stage(如VS, FS)
- 構建系統會包含該組中所有關鍵字,例如
#pragma multi_compile a b c
,會分別編譯出包含定義了a,b和c的shader變體。
#pragma shader_feature
和multi_compile
有兩點不同:
- 構建系統只會包含該組中被使用的關鍵字。比如我們可以將shader feature關鍵字在Properties中設置:
Properties
{[MaterialToggle(_USE_FOG)] _UseFog("Use Fog", int)=0
}
#pragma shader_feature _USE_FOG
當材質啟用_USE_FOG時,這個關鍵字就被使用。當然,如果是multi_compile
也可以在材質屬性里面設置,但是無論是否設置該組中的某個關鍵字,這些關鍵字都還是會被編譯,但shader_feature
就只有選擇的那些關鍵字會被編譯。
- 雖然也是聲明一組關鍵字,但是隱含了一個任何關鍵字都沒啟用的情況。
- 但是如果將shader設置到圖形設置窗口的Always Included Shaders中,那么所有的關鍵字都會被包含。
全局關鍵字和本地關鍵字
上面的聲明方式都是全局關鍵字,所謂全局就是可以使用Shader.EnableKeyword針對所有使用該shader的材質統一開啟或關閉的關鍵字。
而本地關鍵字聲明的時候要加上_local
,比如#pragma multi_compile_local
,使用Material.EnableKeyword修改,只影響這個材質。