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

@函數

Unity的光源類型

在前面的例子中,咱們場景中都僅僅只有一個光源且光源類型是平行光(若是你的場景不是這樣的話,可能會獲得錯誤的結果)。只有一個平行光的世界很美好,但好夢總有行的那一天,這是咱們就要在Unity Shader中處理更復雜的光源類型以及數目更多的光源。在本節中,咱們將會學習如何在Unity中處理點光源(point light)和聚光燈(spot light)。
Unity一共支持4中光源類型:平行光、點光源、聚光燈和麪光源(area light)。面光源盡在烘焙時纔可發揮做用,所以咱們不討它。因爲每種光源的幾何定義不一樣,所以它們對應的光源屬性也就不一樣。這就要求咱們要區別對待它們。幸運的是Unity提供了不少內置函數來幫咱們處理這些光源,咱們後面會說到。如今咱們要先來理解它們的原理。工具

1. 光源類型有什麼影響

咱們來看一下光源類型的不一樣到底會給Shader帶來哪些影響。咱們能夠考慮Shader中使用了哪些屬性。最多見的光源屬性有光源的位置、方向(更具體的說就是,到某點的方向)、顏色、強度以及衰減(更具體的說是,到某點的衰減,與該點到光源的距離有關)這5個屬性。而這些屬性和它們的幾何定義息息相關。性能

1.1 平行光

對於咱們以前使用的平行光來講,它的幾何定義是最簡單的。平行光能夠照亮的範圍是沒有限制的,它一般是做爲太陽這樣的角色在場景中出現的。下圖給出了在Unity中平行光在Scene視圖中的表示以及Light組件的面板。
在這裏插入圖片描述
平行光之因此簡單,是由於它沒有一個惟一的位置,也就說它能夠放在場景中的任何位置(回憶一下,小時候咱們是否是總以爲太陽和咱們一塊兒移動)。它的幾何屬性只有方向,咱們能夠調整Transform組件中的Rotation屬性來改變它的光源方向,並且平行光到場景中全部點的方向都是同樣的,這也是平行光名字的由來。除此以外,因爲平行光沒有一個具體的位置,所以也沒有衰減的概念,也就是說,光照強度不會隨着距離而發生改變。學習

1.2 點光源

點光源的照亮空間則是有限的,它是由空間中的一個球體定義的。點光源能夠表示由一個點發出的、向全部方向延伸的光。下圖給出了點光源在Scene視圖中的表示以及Light組件的面板。
在這裏插入圖片描述
須要提醒讀者的一點是,咱們須要在Scene視圖中開啓光照才能看到預覽光源是如何影響場景中的物體的。下圖給出了開啓Scene視圖光照的按鈕。
在這裏插入圖片描述
球體半徑能夠由面板中的Range屬性來調整,也能夠在Scene視圖中直接拖拉點光源的線框(如球體上的黃色控制點)來修改它的屬性。點光源是有位置屬性的,它是由點光源的Transform組件中的Position屬性定義的。對於方向屬性,咱們須要用點光源的位置減去某點的位置來獲得它到該點的方向。而點光源的顏色和強度能夠在Light組件面板中調整。同時,點光源也是會衰減的,隨着物體逐漸遠離點光源,它接受到的光照強度也會逐漸減少。點光源球心處的光照強度最強,球體邊界處最弱,值爲0。其中的衰減值能夠由一個函數定義。this

1.3 聚光燈

聚光燈是這3種光源類型中最複雜的一種。它的照亮空間一樣是有限的,但再也不是簡單的球體,而是由空間中的一塊錐形區域定義的。聚光燈能夠用於表示由一個特定位置出發、向特定方向延伸的光。下圖給出了Unity聚光燈在Scene視圖中的表示以及Light組件的面板。
在這裏插入圖片描述
這塊錐形區域的半徑由面板中的Range屬性決定,而錐體的張開角度由SpotAngle屬性決定。咱們一樣也能夠在Scene視圖中直接拖拉聚光燈的線框(如中間的黃色控制點以及四周的黃色控制點)來修改它的屬性。聚光燈的位置一樣是由Transform組件中的Position屬性定義的。對於方向屬性,咱們須要用聚光燈的位置減去某點的位置來獲得它到該點的方向。聚光燈的衰減也是隨着物體遠離點光源而逐漸減少,在錐形的頂點處光照強度最強,在錐形的邊界處強度爲0。其中的衰減值能夠由一個函數定義,這個函數相對於點光源衰減計算公式更加複雜,覺得咱們須要判斷一個點是否在錐體的範圍內。調試

2.在前向渲染中處理不一樣的光源類型

在瞭解3種光源的幾何定義後,咱們來看一下如何在Unity Shader中訪問它們的5個屬性:位置、方向、顏色、強度以及衰減。須要注意的是,本節均創建在使用前向渲染路徑的基礎上。
學完本節後,咱們能夠獲得相似下圖的效果:
在這裏插入圖片描述code

2.1 實踐

爲了讓物體受多個光源的影響 ,咱們再新建一個點光源,把其顏色設爲綠色,以和平行光進行區分。
咱們的代碼使用了Blinn-Phong光照模型,併爲前向渲染定義了Base Pass和Additional Pass來處理多個光源。在這裏咱們只給出其中關鍵的代碼。
(1)咱們首先定義第一個Pass——Base Pass。爲此,咱們須要設置該Pass的渲染路徑標籤:orm

Pass{
//Pass for ambient light & first pixel light (directional light)
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
//Apparently need to add this declaration
#pragma multi_compile_fwdbase
}

須要注意的是,咱們除了設置渲染路徑外,還使用了#pragma編譯指令。#pragma multi_compile_fwdbase指令能夠保證咱們在shader中使用光照衰減等光照變量能夠被正確賦值。這是不可缺乏的。
(2)在Base Pass的片元着色器中,咱們首先計算了場景中的環境光:blog

//Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz

(3)而後,咱們在Base Pass中處理了場景中的最重要的平行光。在這個例子中,場景中只有一個平行光。若是場景中包含了多個平行光。Unity會選擇最亮的平行光傳遞給Base Pass進行逐像素處理,其它平行光會按照逐頂點或在Additional Pass中按逐像素的方式處理。若是場景中沒有任何平行光,那麼Base Pass會當成全黑的光源處理。咱們提到過,每個光源有5個屬性:位置、方向、顏色強度以及衰減。對於Base Pass來講,它處理的逐像素光源必定是平行光。咱們可使用_WorldSpaceLightPos0來獲得這個平行光的方向(位置對平行光來講沒有意義),使用_LightColor0來獲得它的顏色和強度(_LightColor0已是顏色和強度相乘後的結果),因爲平行光能夠認爲是沒有衰減的,這裏咱們直接令衰減值爲1.0。相關代碼以下:

//Compute diffuse term
fixed3 diffuse=_LightColor0.rgb*_Diffuse.rgb*max(0,dot(worldNormal,worldLightDir));
......
//Compute specular term
fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(0,dot(worldNormal,halfDir)),_Gloss);
//The attenuation of directional light is always 1
fixed atten =1.0;
return fixed(ambient+(diffuse+specular)*atten,1.0)

至此,Base Pass的工做就完成了
(4)接下來,咱們須要爲場景中其它逐像素光源定義Additional Pass。爲此咱們首先須要設置Pass的渲染路徑標籤:

Pass{
//Pass for other pixel lights
Tags{"LightMode"="ForwardAdd"}
Blend One One
CGPROGRAM
//Apparently need to add this declaration
#pragma multi_compile_fwdadd
}

除了設置渲染路徑標籤外,咱們一樣使用了#pragma multi_compile_fwdadd指令,如前面所說,這個指令能夠保證咱們在Addition Pass中訪問到正確的環境變量。與Base Pass不一樣的是,咱們還使用了Blend命令開啓和設置了混合模式。這是由於咱們但願Additional Pass計算獲得的光照結果能夠在幀緩存中與以前的光照結果進行疊加。若是沒有使用Blend命令的話,Additional Pass會直接覆蓋掉以前的光照結果。在本例中,咱們選擇的混合係數Blend One One,這不是必須的,咱們能夠設置成Unity支持的任何混合係數。常見的還有Blend SrcAlpha One。
(5)一般來講,Additional Pass的光照處理和Base Pass的處理方式是同樣的,所以咱們只須要把Base Pass的頂點和片元着色器代碼粘貼到Additional Pass中,而後再稍微修改一下便可。這些修改每每是爲了去掉Base Pass中的環境光、自發光、逐頂點光照、SH光照的部分,並添加一些對不一樣光源的支持。所以在Additional Pass的片元着色器中,咱們沒有再計算場景中的環境光。因爲Additional Pass處理的光源類型多是平行光、點光源或是聚光燈,所以在計算光源的5個屬性——位置、方向、顏色、強度和衰減時,顏色和強度咱們仍可使用_LightColor0來獲得,但對於位置、方向和衰減屬性,咱們就須要根據光源的類型分別計算。首先,咱們來看如何計算不一樣光源的方向:

#ifdef USING_DIRECTIONAL_LIGHT
fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz-i.worldPostion.xyz);
#endif

在上面的代碼中,咱們首先判斷了當前處理的逐像素光源的類型,這是經過使用#ifdef指令判斷是否認義了USING_DIRECTIONAL_LIGHT來獲得的。若是前向渲染的Pass處理的光源類型是平行光,那麼Unity的底層渲染引擎就會定義USING_DIRECTIONAL_LIGHT。若是判斷得知是平行光的話,光源方向就能夠直接由_WorldSpaceLightPos0.xyz獲得;若是是點光源或聚光燈,那麼_WorldSpaceLightPos0.xyz表示的是世界空間下的光源位置,而想要獲得光源方向的話,咱們就須要用這個位置減去世界空間下的頂點位置。
(6)最後,咱們須要處理不一樣光源的衰減:

#ifdef USING_DIRECTIONAL_LIGHT
fixed atten =1.0;
#else
float3 lightCoord=mul(_LightMatrix0,float4(i.worldPosition,1)).xyz;
fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
#endif

咱們一樣經過判斷是否認義了USING_DIRECTIONAL_LIGHT來決定當前處理的光源類型。若是是平行光的話,衰減值爲1.0。若是是其它的光源類型,那麼處理更復雜一些。儘管咱們可使用數學表達式來計算給頂點相對於點光源和聚光燈的衰減,但這些計算每每涉及開根號、除法等計算量較大的操做,所以Unity選擇了使用一張紋理做爲查找表(Lookup Table,LUT),以在片元着色器中獲得光源的衰減。咱們首先獲得光源空間下的座標,而後利用該座標對衰減紋理進行採樣獲得衰減值。關於Unity的衰減值咱們後面會說到。
咱們能夠在場景中添加更多的逐像素光源來照亮物體。須要注意的是,本節只是爲了講解處理其餘類型光源的實現原理,上述的代碼並不會真正的用於項目中。

2.2 實驗:Base Pass和Additional Pass的調用

建立四個點光源,把它們的顏色設爲相同的紅色,咱們能夠獲得相似下圖的效果:
在這裏插入圖片描述
那麼,這樣的結果是怎麼來的呢?當咱們建立一個光源時,默認狀況下它的Render Mode(能夠在Light組件中設置)是Auto。這意味着,Unity會在背後爲咱們判斷哪些光源會按逐像素處理,而哪些光源按頂點或SH的方式處理。因爲咱們沒有更改Edit->Project Settings->Quality->Pixel Light Count中的數值,所以默認狀況下一個物體能夠接收除最亮的平行光外的4個逐像素光照。在這個例子中,場景中共包含了5個光源,其中一個是平行光,它會在Chapter9-Forward Rendering的Base Pass中按逐像素的方式進行處理;其他4個都是點光源,因爲它們的Render Mode爲Auto且數目正好等於4,所以都會在Chapter9-Forward Rendering的Additional Pass中按逐像素的方式被處理,每一個光源會調用一次Additional Pass。
在Unity5中,咱們還可使用幀調試器(Frame Debugger)工具來查看場景的繪製過程,使用方法是:在window->Franme Debugger中打開幀調試器,以下圖所示
在這裏插入圖片描述
從幀調試其中能夠看出,渲染這個場景的Unity一共進行了6個渲染事件,因爲本例中只包含了一個物體,一次這6個渲染事件幾乎都是用於渲染該物體的光照結果。咱們能夠經過依次單擊幀調試器中的渲染事件,來查看Unity是怎樣渲染物體的。下圖給出了本例中Unity進行的6個渲染事件。
在這裏插入圖片描述
從圖中能夠看出,Unity是如何一步步將不一樣光照渲染到物體上的:在第一個渲染事件中,Unity首先清楚顏色、深度和模板緩衝,爲後面的渲染作準備;在第二個渲染事件中,Unity利用Chapter9->ForwardRendering的第一個Pass,即Base Pass,將平行光的光照渲染到幀緩存中;在後面的4個渲染事件中,Unity使用Chapter9->ForwardRendering的第二個Pass,即Additional Pass,一次將四個點光源應用到物體上,獲得最後的渲染結果。
能夠注意到,Unity處理這些點光源的順序是按照它們的重要度排序的。在這個例子中,因爲全部的點光源顏色和強度都相同,所以它們的重要度取決於它們距離膠囊體的遠近,所以上圖中首先繪製的是距離膠囊體較近的光源。可是若是光源的強度和亞瑟互不相同,那麼距離就再也不是惟一的衡量標準。例如,若是咱們把如今距離最近的點光源的強度設爲0.2,那麼從幀調試器中咱們能夠發現繪製順序發生了變化。此時,首先繪製的是距離膠囊體第二近的光源,最近的點光源會在最後被渲染。Unity官方文檔中並無給出光源強度、顏色和距離物體的遠近是如何具體影響光源的重要度排序的,咱們僅知道排序結果和這三者都有關係。
對於場景中的一個物體,若是它不在一個光源的光照範圍內,Unity是不會爲這個物體調用Pass來處理這個光源的。咱們能夠把本例中距離最遠的點光源的範圍調小,使得膠囊體在它的照亮範圍外。此時,再查看幀調試器,咱們能夠發現渲染事件比以前少了一個,以下圖所示:
在這裏插入圖片描述
一樣,若是一個物體不在某個聚光燈範圍內,Unity也不會爲該物體調用相關的渲染事件的。
咱們知道,若是逐像素光源的數目不少的話,該物體的Additional Pass就會被調用屢次,影響性能,咱們能夠經過把光源的Render Mode設爲Not Important來告訴Unity,咱們不但願把該光源當成逐像素處理。在本例中,咱們能夠把4個點光源的Render Mode都設爲Not Important,能夠獲得下圖的結果:
在這裏插入圖片描述 因爲咱們在Chapter-ForwardRendering中沒有在Base Pass中計算逐頂點和SH光源,所以場景中的4個點光源實際上不會對物體形成任何影響。一樣,若是咱們把平行光的Render Mode也設爲Not Important,物體就會僅顯示環境光照的結果。 那麼,咱們如何在前向渲染路徑的Base Pass中計算逐頂點和SH光呢?咱們可使用前面提到的內置變量和函數來計算這些光源的光照效果。

相關文章
相關標籤/搜索