第八章 更復雜的光照(1)

@數組

Unity的渲染路徑

在Unity裏,渲染路徑(Rendering Path)決定了光照是如何應用到Unity Shader中的。所以,若是要和光源打交道,咱們須要爲每一個Pass指定它使用的渲染路徑,只有這樣才能讓Unity知道「哦,原來這個程序員想要用這種渲染路徑,那麼好的,我把光源和處理後的光照信息都放在這些數據裏,你能夠訪問啦」也就是說,咱們只有爲Shader正確的選擇和設置了須要的渲染路徑,該Shader的光照計算才能被正確執行。
Unity支持多種類型的渲染路徑,在Unity5.0版本以前,主要有三種:前向渲染路徑(Forward Rendering Path)、延遲渲染路徑(Deferred Rendering Path)和頂點照明渲染路徑(Vertex Lit Rendering Path)。但在Unity5.0版本之後,Unity作了不少修改,主要有兩個變化:首先,頂點照明渲染路徑已經被Unity拋棄(但目前仍然能夠對以前使用了頂點照明渲染路徑的UnityShader兼容);其次,新的延遲渲染路徑代替了原來的延遲渲染路徑(一樣,目前也提供了對較舊版本的兼容)。
大多數狀況下,一個項目只能使用一種渲染路徑,所以咱們能夠爲整個項目渲染時的渲染路徑。咱們能夠經過在Unity的Edit->Project Settings->Player->Other Settings->Rendering Path中選擇項目所需的渲染路徑。默認狀況下,該設置選擇的是前向渲染路徑,以下圖所示:
在這裏插入圖片描述
但有時,咱們但願可使用多個渲染路徑,例如攝像機A渲染的物體使用前向路徑,而攝像機B渲染的物體使用延遲渲染路徑。這時,咱們能夠在每一個攝像機的渲染路徑設置中設置該攝像機使用的渲染路徑,以覆蓋Project Settings中的設置,如圖所示:
在這裏插入圖片描述
在上面的設置中,若是選擇了Use Player Settings,那麼這個攝像機就會使用Project Settings中的設置;不然就會覆蓋掉Project Settings中的設置。須要注意的是,若是當前的顯卡並不支持所選擇的渲染路徑,Unity會自動使用更低一級的渲染路徑。例如,若是一個GPU不支持延遲渲染,那麼Unity就會使用前向渲染。
完成了上面的設置後,咱們就能夠在每一個Pass中使用標籤來指定該Pass使用的渲染路徑。這是經過設置Pass的LightMode標籤來實現的。不一樣類型的渲染路徑可能會包含多種標籤設置。例如,咱們以前在代碼中寫的:緩存

Pass{
Tags{"LightMode"=''ForwardBase''}
}

上面的代碼告訴Unity,該Pass使用前向渲染路徑中的ForwardBase路徑。而前向渲染路徑還有一種路徑叫作ForwardAdd。下表給出了Pass的LightMode標籤支持的渲染路徑設置選項。
函數

那麼指定渲染路徑到底有什麼用呢?若是一個Pass沒有指定任何渲染路徑會有什麼問題嗎?通俗來說,指定渲染路徑是咱們和Unity的底層渲染引擎的一次重要的溝通。例如,若是咱們爲一個Pass設置了前向渲染路徑的標籤,至關於會告訴Unity:「嘿,我準備使用前向渲染了,你把那些光照屬性都按前向渲染的流程給我準備好,我一下子要用」。隨後,咱們能夠經過Unity提供的內置光照變量來訪問這些屬性。若是咱們沒有指定任何的渲染路徑(實際上,在Unity5.x版本中若是使用了前向渲染又沒有爲Pass指定任何前向渲染適合的標籤,就會被當成一個和頂點照明渲染路徑等同的Pass),那麼一些光照變量極可能不會被正確賦值,咱們計算出的效果頗有多是錯誤的。
那麼,Unity渲染引擎是如何處理這些渲染路徑的呢?下面,咱們對這些渲染路徑,進行更加詳細的解釋。性能

1. 前向渲染路徑

前向渲染路徑是傳統的渲染方式,也是咱們最經常使用的一種渲染路徑。在本節,咱們首先會歸納前向渲染路徑的原理,而後再給出Unity對於前向渲染路徑的實現細節和要求,最後給出UnityShader中哪些變量是用於前向渲染路徑的。學習

1.1 前向渲染路徑的原理

每進行一次完整的前向渲染,咱們須要渲染該對象的渲染圖元,並計算兩個緩衝區中的信息:一個是顏色緩衝區,一個深度緩衝區。咱們利用深度緩衝區來決定一個片元是否可見,若是可見就更新顏色緩衝區中的顏色值。咱們可使用下面的僞代碼來描述前向渲染路徑的大體過程:測試

Pass{
for(each primitive in this model){
for(each fragment covered by this primitive){
if(failed in depth test){
//若是沒有經過深度測試,說明該片元是不可見的
discard;}
else{
//若是該片元可見
//就進行光照計算
float4 color = Shading(materialInfo,pos,normal,lightDir,viewDir);
//更新幀緩衝
writeFrameBuffer(fragment,color);
}
}
}
}

對於每一個逐像素光源,咱們都須要進行上面一次完整的渲染流程。若是一個物體在多個逐像素光源的影響區域內,那麼該物體就須要執行多個Pass,每一個Pass計算一個逐像素光源的光照結果,而後在幀緩衝中把這些光照結果混合起來獲得最終的顏色值。假設場景中有N個物體,每一個物體受M個光源的影響,那麼渲染整個場景須要N*M個Pass。能夠看出,若是有大量的逐像素光照,那麼須要執行的Pass數目也會很大。所以渲染引擎一般會限制每一個物體的逐像素光照數目。this

1.2 Unity中的前向渲染

事實上,一個Pass不只僅能夠用來計算逐像素光照,它也能夠用來計算逐頂點等其它光照。這取決於光照計算所處流水線階段以及計算時使用的數學模型。當咱們渲染一個物體時,Unity會計算哪些光源照亮了它,以及這些光源照亮該物體的方式。
在Unity中,前向渲染路徑有三種處理光照(即照亮物體)的方式:逐頂點處理、逐像素處理,球諧函數(Spherical Harmonics,SH)處理。而決定一個光源使用哪一種處理模式取決於它的類型和渲染模式。光源類型指的是該光源是平行光仍是其餘類型光源,而光源的渲染模式指的是該光源是不是重要的(Important)。若是咱們把一個光照的模式設置爲Important,意味着咱們告訴Unity,「嘿老兄,這個光源很重要,我但願你能夠認真對待它,把它當成一個逐像素光源來處理」咱們能夠在光源的Light組件中設置這些屬性,以下圖所示:
在這裏插入圖片描述
在前向渲染中,當咱們渲染一個物體時,Unity會根據場景中各個光源的設置以及這些光源對物體的影響程度(例如,距離該物體的遠近、光源強度等)對這些光源進行一個重要度排序。其中,必定數目的光源會按逐像素的方式處理,而後最多有4個光源按逐頂點的方式處理,剩下的光源能夠按SH方式處理。Unity使用的判斷規則以下。
(1)場景中最亮的平行光老是按逐像素處理的
(2)渲染模式被設置成Not Important的光源,會按逐頂點或者SH處理
(3)渲染模式被設置成Important的光源,會按逐像素處理。
(4)若是根據以上規則獲得的逐像素光源數量小於Quality Setting中的逐像素光源數量(Pixel Light Count),會有更多的光源以逐像素的方式進行渲染。
那麼,在哪裏進行光照計算呢?固然是在Pass裏。前面提到過,前向渲染有兩種Pass:BasePass和Additional Pass。一般來講,這兩種Pass進行的標籤渲染設置以及常規光照計算如圖所示:
在這裏插入圖片描述
上圖有幾點須要說明的地方
(1)首先,能夠發如今渲染設置中,咱們除了設置Pass的標籤外,還使用了#pragma multi_compile_fwdbase這樣的編譯指令。雖然#pragma multi_compile_fwdbase和#pragma multi_compile_fwdadd在官方文檔中尚未給出相關說明,但實驗代表,只有分別爲BasePass和Additional Pass使用這兩個編譯指令,咱們才能在相關的Pass中獲得一些正確的光照變量,例如光照衰減值等。
(2)Base Pass旁邊的註釋給出了Base Pass中支持的一些光照特性。例如在Base Pass中咱們能夠訪問光照紋理(lightmap)
(3)Base Pass中渲染的平行光默認是支持陰影的(若是開啓了光源的陰影功能),而Additional Pass中渲染的光源在默認狀況下是沒有陰影效果的,即使咱們在它的Light組件中設置了有陰影的Shadow Type。但咱們能夠在Additional Pass中使用#pragma multi_compile_fwdadd_fullshadows代替#pragma multi_compile_fwdadd編譯指令,爲點光源和聚光燈開啓陰影效果,但這須要在Unity內部使用更多的Shader變種。
(4)環境光和自發光也是在Base pass中計算的。這是由於對於一個物體來講,環境光和自發光咱們只但願計算一次便可,而若是咱們在Additional Pass中計算這兩種光照,就會形成疊加屢次環境光和自發光,這不是咱們想要的。
(5)在Additional Pass的渲染設置中,咱們還開啓和設置了混合模式。這是由於咱們但願每一個Additional Pass能夠與上一次的光照結果在幀緩存中進行疊加,從而獲得最終有多個光照的渲染效果。若是咱們沒有開啓和設置混合模式,那麼Additional Pass的渲染結果會覆蓋掉以前的渲染結果,看起來好像該物體只受該光源的影響。一般狀況下,咱們選擇的混合模式是Blend One One
(6)對於前向渲染來講,一個UnityShader一般會定義一個Base Pass(Base Pass也能夠被定義屢次,例如須要雙面渲染的狀況)以及一個Additional Pass。一個Base Pass僅會執行一次(定義了多個Base Pass的狀況除外),而一個Additional Pass會根據影響該物體的其餘逐像素光源數目被屢次調用,即每一個逐像素光源會執行一次Additional Pass。
上圖給出的光照計算是一般狀況下咱們在每種Pass中進行的計算。實際上,渲染路徑的設置用於告訴Unity該Pass在前向渲染路徑中的位置,而後底層的渲染引擎會進行相關計算並填充一些內置變量(如_LightColor0等),如何使用這些內置變量進行計算徹底取決於開發者的選擇。例如咱們徹底能夠利用Unity提供的內置變量在Base Pass中只進行逐頂點光照;一樣,咱們也能夠徹底在Additional Pass中按逐頂點的方式進行光照計算,不進行任何逐像素計算。code

1.3內置的光照變量和函數

前面說過,根據咱們使用的渲染路徑(即Pass標籤中LightMode的值),Unity會把不一樣的光照變量傳遞給Shader。
在Unity5中,對於前向渲染(即LightMode爲ForwardBase或ForwardAdd)來講,下表給出了咱們能夠在Shader中訪問到的光照變量。
orm

咱們在之前已經給出了一些能夠用於前向渲染路徑的函數,例如WorldSpaceLightDir、UnityWorldSpaceLightDir和ObjSpaceLightDir。爲了完整性,咱們在下面再次列出前向渲染中可使用的內置光照函數。

須要說明的是,上面給出的變量和函數並非完整的,一些前向渲染可使用的內置變量和函數官方文檔並無給出說明。在後面的學習中,咱們會使用到一些不在這些表中的變量和函數,那時咱們會特別說明。

2.頂點照明渲染路徑

頂點照明渲染路徑是對硬件配置要求最少、運算性能最高,但同時也是獲得的效果最差的一種類型,它不支持那些逐像素才能獲得的效果,例如陰影、法線映射、高精度的高光反射等。實際上,它僅僅是前向渲染的一個子集,也就是說,全部能夠在頂點照明渲染路徑中實現的功能均可以在前向渲染路徑中完成。就如它的名字同樣,頂點照明渲染路徑只是使用了逐頂點的方式來計算光照,並無什麼神奇的地方。實際上,咱們在上面的前向渲染路徑中也能夠計算一些逐頂點的光源。但若是選擇使用頂點照明渲染路徑,那麼Unity會只填充那些逐頂點相關的光源變量,意味着咱們不可使用一些逐像素光照變量。

2.1 Unity中的頂點照明渲染

頂點照明渲染路徑一般在一個Pass中就能夠完成對物體的渲染。在這個Pass中,咱們會計算咱們關心的全部光源對該物體的照明,而且這個計算是按逐頂點處理的。這是Unity中最快速的渲染路徑,而且具備最普遍的硬件支持(但遊戲機上並不支持這種路徑)

2.2 可訪問的內置變量和函數

在Unity中,咱們能夠在一個頂點照明的Pass中最多訪問到8個逐頂點光源。若是咱們只須要渲染其中兩個光源對物體的照明,能夠僅使用下表中內置光照數據的前兩個。若是影響該物體的光源數目小於8,那麼數組中剩下的光源顏色會設置成黑色。

能夠看出,一些變量咱們一樣能夠在前向渲染路徑中使用,例如unity_LightColor。但這些變量數組的維度和數值在不一樣渲染路徑中的值是不一樣的。
下表給出了頂點照明渲染路徑中可使用的內置函數。

3. 延遲渲染路徑

前向渲染的問題是:當場景中包含大量實時光源時,前向渲染的性能會急速降低。例如,若是咱們在場景的某一塊區域放置了多個光源,這些光源影響的區域互相重疊,那麼爲了獲得最終的光照效果,咱們就須要爲該區域內的每一個物體執行多個Pass來計算不一樣光源對該物體的光照結果,而後在顏色緩存中把這些結果混合起來獲得最終的光照。然而,每執行一個Paa咱們都須要從新渲染一遍物體,但不少計算其實是重複的。
延遲渲染是一種更古老的渲染方法,但因爲上述前向渲染可能會形成的瓶頸問題,近幾年又流行起來。除了前向渲染中使用的顏色緩衝和深度緩衝外,延遲渲染還會利用額外的緩衝區,這些緩衝也被稱爲G緩衝(G-buffer),其中G是英文Geometry的縮寫。G緩衝區存儲了咱們所關心的表面(一般指的是離計算機最近的表面)的其餘信息,例如該表面的法線、位置、用於光照計算的材質屬性等。

3.1延遲渲染的原理

延遲渲染主要包含了兩個Pass。在第一個Pass中,咱們不進行任何光照計算,而是僅僅計算哪些片元是可見的,這主要是經過深度緩衝技術來實現,當發現一個片元是可見的,咱們就把它的相關信息存儲到G緩衝區中。而後,在第二個Pass中,咱們利用G緩衝區的各個片元信息,例如表面法線、視角方向、漫反射係數等,進行真正的光照計算。
延遲渲染的大體過程能夠用下面的僞代碼來描述:

Pass1{
//第一個Pass不進行真正的光照計算
//僅僅把光照計算須要的信息存儲到G緩存中
for(each primitive in this moadel){
for(each fragment covered by this primitive){
if(failed in depth test){
//若是沒有經過深度測試,說明該片元是不可見的
discard;
}else{
//若是該片元可見
//就須要把信息存儲到G緩衝區中
writeGBuffer(materialInfo,pos,normal,lightDir,viewDir)
}
}
}
}
Pass2{
//利用G緩衝區中的信息進行真正的光照計算
for(each pixel in the screen){
if(the pixel is valid){
//若是該像素是有效的
//讀取它對應的G緩衝中的信息
readBuffer(pixel,materialInfo,pos,normal,lightDir,viewDir);
//根據讀取到的信息盡行光照計算
float4 color=shading(materialInfo,pos,normal,lightDir,viewDir);
//更新幀緩衝
writeFrameBuffer(pixel,color);
}
}
}

能夠看出,延遲渲染使用的Pass數目一般就是兩個,這跟場景中包含的光源數目是沒有關係的。換句話說,延遲渲染的效率不依賴於場景的複雜度,而是和咱們使用的屏幕空間大小有關。這是由於,咱們須要的信息都存儲在緩衝區中,而這些緩衝區能夠理解成是一張張2D圖像,咱們的計算實際上就是在這些圖像空間中進行的。

3.2 Unity中的延遲渲染

Unity有兩種延遲渲染路徑,一種是遺留的延遲渲染路徑,即Unity5以前使用的延遲渲染路徑,而另外一種是Unity5.x中使用的延遲渲染路徑。若是遊戲中使用了大量的實時光照,那麼咱們可能但願選擇延遲渲染路徑,但這種路徑須要必定的硬件支持。
新舊延遲渲染路徑之間的差異很小,只是用了不一樣的技術來權衡不一樣的需求。例如,較舊版本的延遲渲染路徑不支持Unity5的基於物理的Standard Shader。如下咱們只討論Unity5後使用的延遲渲染路徑。
對於延遲渲染路徑來講,它最適合在場景中光源數目不少、若是使用前向渲染會形成性能瓶頸的狀況下使用。並且,延遲渲染路徑中的每一個光源均可以按照逐像素的方式處理。可是,延遲渲染也有一些缺點。
(1)不支持真正的抗鋸齒(anti-aliasing)功能。
(2)不能處理半透明物體
(3)對顯卡有必定要求。若是要使用延遲渲染的話,顯卡必須支持MRT(Multiple Render Targets)、Shader Mode3.0及以上、深度渲染紋理以及雙面的模板緩衝。
當使用延遲渲染時,Unity要求咱們提供兩個Pass。
(1)第一個Pass用於渲染G緩衝。在這個Pass中,咱們會把物體的漫反射顏色、高光反射顏色、平滑度、法線、自發光和深度等信息渲染到屏幕空間的G緩衝區中。對於每一個物體來講,這個Pass僅會執行一次。
(2)第二個Pass用於計算真正的光照模型。這個Pass會使用上一個Pass中渲染的數據來計算最終的光照顏色,再存儲到幀緩衝中。
默認的G緩衝區(注意,不一樣Unity版本的渲染紋理存儲內容會有所不一樣)包含了如下幾個渲染紋理(Render Texture,RT)。
●RT0:格式是AGRB32,RGB通道用於存儲漫反射顏色,A通道沒有被使用
●RT1:格式是AGRB32,RGB通道用於存儲高光反射顏色,A通道用於存儲高光反射的指數部分。
●RT2:格式是ARGB2101010,RGB通道用於存儲法線,A通道沒有使用
●RT3:格式是ARGB32(非HDR)或ARGBHalf(HDR),用於存儲自發光+lightmap+反射探針(reflection probes)
當在第二個Pass計算光照時,默認狀況下僅可使用Unity內置的Standard光照模型。若是咱們想要使用其它的光照模型,就須要替換掉原有的Internal-DefferedShading.shader文件。

3.3可訪問的內置變量和函數

下表給出了處理延遲渲染路徑可使用的光照變量。這些變量均可以在UnityDefferedLibrary.cginc文件中找到它們的聲明。

3.4 選擇哪一種渲染路徑

Unity的官方文檔給出了4種渲染路徑(前向渲染路徑、延遲渲染路徑、遺留的延遲渲染路徑和頂點照明渲染路徑)的詳細比較,包括它們的性能比較(是否支持逐像素光照、半透明物體、實時陰影等)、性能比較以及平臺支持。 整體來講,咱們須要根據遊戲發佈的目標平臺來選擇渲染路徑。若是當前顯卡不支持所選渲染路徑,那麼Unity會自動使用比其低一級的渲染路徑。 咱們主要講Unity的前向渲染路徑。

相關文章
相關標籤/搜索