在標準的PBR環境中,物理燈光一直是一個不可或缺的組件,這直接決定瞭光照質量和材質效果,影響到最後的畫面表現。例如,Unity Legacy Pipeline中我們熟悉的Directional Light等燈光組件的默認強度為1,但這個1具體是多亮,在光照計算中的值是多少,就比較模糊瞭。在制作大型項目時甚至需要主觀的定義標準場景中的光照強度,這是非常不可取的,使得燈光組件的復用性很差,在材質計算時也無法得到理想的結果。
在Unity HDRP之後,終於引入瞭物理燈光作為光照計算單位,定義瞭光照強度、光照明度、光照效率,並可以直接引用真實世界中的物理值進行場景燈光設置,通用性很高,大大提到瞭Lighting Artist的效率。
本篇著重講解瞭什麼是光照單位,為什麼要使用光照單位的問題,以及在寒霜中的一些使用案例。希望各位能在看完之後對物理的光照單位有一個基礎認識。
一個優秀的光照pipeline必須遵循學界公認的一些原則:支持HDR高動態范圍和線性空間[Gd08]的色彩。Pipeline中所有的輸入輸出值必須是經過伽馬矯正的(如mip-mapping、blending、filtering等)。在寒霜中,因為硬件上的支持[1] ,我們選擇依賴於sRGB的轉換。
一個看上去很真實的場景是基於對光照的理解和藝術創作的:每個物體應該接收到自身所處環境的燈光信息,有正確的反射強度以及陰影。大多數引擎中提供的調整工具並沒有關聯,這使得美術人員在單獨調整其中一個光照工具時無法得到正確的結果,需要將其它光照參數一起調整,這就打破瞭可信度和空間參考。因此在寒霜中,設計所有光照物體的原則是,為美術人員提供一個物理準確的默認值,使得他們可以在此基礎上進一步調整。這樣一來讓美術人員調整出來的光照效果就很難出錯瞭。但這些都需要考慮到基本的性能限制和美術效果。
寒霜支持多種類型的光源:精確光源(Punctual light),光度光源(photometric lights),區域光(Area Light),自發光表面(Emissive Surface),還有基於圖像的光照(IBL),這裡的IBL包含遠距離的光照探針(Skybox)和近距離的光照探針(Light Probe),還有基於屏幕空間的反射(SSR)。在光照物件和材質正確交互的過程中保障瞭光照整體的一致性。下面是一些案例:
接下來的章節會對寒霜中所有的光照類型進行總結,包含如何在引擎中保證光照的統一性。我們並沒有解決所有遇到的問題,但無法解決的那部分也會進行具體的說明,比如3.3中出現的貼花問題。
為瞭讓美術人員更好理解,在寒霜中精確光源和面光的設置界面都是相同的,見圖15。我們把燈光的色相和強度區分開來。我們使用強度intensity一詞而不是學術界上的用語來表示每盞燈光發射出來的能量大小。為瞭區分光的色彩傾向,寒霜中的人造燈光使用色溫/校正後的色溫(CCT)來區分。色溫是在理想物體的黑色表面下產生的色相對比。而CCT則是在理想的黑色表面[2]下產生的最接近人造光源(如臺燈)的色彩傾向。色溫和CCT通產個可以用Kelvin(K)作為單位來表示。為瞭文章的簡潔性,在接下來我會把色溫和CCT統稱為色溫。表4展示瞭不同燈光類型對應的色溫和燈光強度。
圖15:寒霜引擎中的光照設置
光譜渲染器(使用光譜的參數而非RGB值來代表顏色的燈光參數)能夠直接使用被Black Body[3] 定義的輻照度光譜來表示強度。在寒霜中,我們選擇將色溫和強度參數區分開,增加可控性。從色溫[4]中獲取光照的色相是一個非常復雜的計算過程,在Charity的這篇論文中有詳細的解釋。此外,我們也提供瞭RGB顏色供美術人員進行選擇。
圖16:在燈泡包裝上描述的色溫和光照強度信息可以直接輸入進寒霜引擎中
光照強度的參數可以用來描述所有的燈光類型。有瞭色溫和強度,美術人員可以從制造商的網站中獲取相應的信息並直接倒入進寒霜引擎中。在圖16中我們可以看到光照設置包含瞭光照衰減的范圍和物理燈光的大小等。而燈光的物理大小和形狀(比如圓形,球形,長條形等)可以讓美術人員去決定這是一個面光源還是精確光源。圖17展示瞭寒霜引擎中支持的所有燈光類型:精確光源中隻有點光源和射燈,因此其它的光源都屬於區域光。兩種光源類型在性能上的表現有所區別,寒霜引擎在兩種燈光類型的過度上做瞭平滑的處理。
為瞭確保光照的統一性,在引擎中遵守光照強度的比率就十分重要,所以在引擎中需要一個通用的光照單位系統來保證各個光源間的統一性。光照強度的范圍跨度很大,如圖18,為瞭性能表現,引擎中需要對這個范圍進行一定的壓縮。場景中光照的多樣性來自於正確的光照平衡。圖19展示瞭室內外光源混合的結果。場景大范圍的曝光度會在光照流程的最後被歸一化為可顯示的像素值,詳細過程見5.1。
表4:不同光源的色溫和對應的顏色近似值。色溫范圍在2700K-3000K之間的為暖光,在3500K—4100K之間的為中性光,大於5000K的為冷光圖17:寒霜中支持的所以光源類型。根據光源的物理大小進行歸類。
通常燈光藝術傢會將引擎中的燈光比率主觀的定義為真實世界中的光源參考,如太陽。這樣的參考會有一個對應的燈光強度(通常是一個較小的值,在5-10之間),其他的燈光會根據這個參照強度值來調整對應的強度大小。然而大多數時候這些設定都是基於當前場景的燈光環境進行設置的(室外,室內等)使得燈光的設置無法在各個場景中通用。為瞭保證各個場景光照比率的統一性,我們在寒霜中引入瞭光照的物理單位。這允許我們:
圖18:真實世界中各種各樣的光源和對應的流明Lumen值
光照單位和光照測量有很大的關聯,可以被分為以下兩類:
光譜中人眼的可見光范圍
來源於radiometric和photometric的光線是非常接近的:因為photometry作為能被人眼感知的光線實際上也是radiometry的一部分。已經有相應的論文對這兩種光線進行解釋[Rei+08]。表5中列舉出瞭最常用的幾種輻照度/光度的參數。其中使用e作為輻照度的標記,v作為光度標記。
圖19:室內外的光線可以使用正確的光照單位進行描述
人眼的敏感性被CIE光度曲線V所表示,它遵循某種鐘形曲線,代表人眼吸收特定光線波長的效率,見圖20。如圖可見,敏感度的峰值在555nm,在人眼中標示為綠色。在這個波長范圍,敏感度的功能值為1,表示在這個范圍內擁有100%的效率。光度數量與輻射量相關,在可見光譜范圍(380nm--780nm)內具有以下積分:
edb36296a765ddf6f9eed0140b5f5d12
常量Km也被稱為光學輻射的最大發光功效(the maximum spectral luminous efficacy of radiation for photoscopic vision ) ,該數值是基於光照強度的SI測量單位candela[Wikb]的定義得到的。論文中對它的定義是:光照強度為1 candela的值表現為,在給定方向以固定頻率540THz(即555nm的波長)發出單色輻射,並在該方向上的輻照強度為每 Steradian 1/683瓦,所以Km = 683。在處理燈泡的光照強度時,通常使用輻射強度來表示,上述的公式可以已一種更簡單的方式來實現:1瓦處在555nm波長的綠色光,它的光照強度為683流明。圖20的光度曲線可以讓我們推斷出一個光源的發光功效[6] 。換句話說,也可以用來表示光源能夠產生多少可見光。用來計算發光功效的公式如下所示:
表5:輻照度和光度的參數表6:幾種光源的光照效率(百分比)
這個等式表示綠色的光線(555nm)有683lm/W的功效,這等同於100%的發光效率。通常情況下不同光源的的發光功效是不同的,這導致兩個擁有相同瓦數的燈光會發射不同強度的燈光,詳見表6。
遊戲作為不依據完整光譜的光線進行渲染的類型,輻照度和光度間的單位轉化就可以被大大簡化瞭。隻需要活的光源的輻照度和發光功效就可以將它從Watt轉化為Lumen瞭。
但當這個等式中的變量未知時,我們通常將光源當作100%發光功效的物體來處理,也就是說h(Eta)=683 ,該等式在計算其他屬性時也很常用。
在光譜渲染器中,光線需要被描述為輻照度單位。連續的光譜權重輻射(spectral weighted radiance)計算隻在輻射域(radiometric domain)產生正確結果。在這之後需要將這部分光譜輻照度轉化為像素值顯示,就需要通過光度權重的過程來計算瞭(如自動曝光和色調映射Tone Mapping)
圖20:人眼的感光曲線
Radiometry or Photometry?那我們選擇輻照度還是光度進行計算呢,像遊戲引擎這樣的非基於光譜的渲染流程,雖然發光功效信息在輻照度和光度單位間進行轉換時是必要的,但為美術人員提供兩種參數(光照強度和光照功效)就會使得這個流程復雜度提升,且不易理解瞭。因此我們通常傾向於使用一個假設值h(Eta)=683來進行計算。通過使用近似值的方式,每個量都是通過線性變換的方式轉化為新的量,因此對於每個物體的處理都是相同的。然而在引擎中使用真實世界中的光照強度對真實存在的燈光進行感性擬合是可取的。因為使用瞭100%發光功效的近似值,真實世界中的輻照度參考值就失去瞭意義, 這會導致在引擎中產生過亮的燈光。但幸運的是,因為大多數燈光產品都會在包裝上標註其光度值。4.9.1中也提到HDRI圖像通常也會提供對應的光度值,我們可以通過調節這些光源的光度值來改變他們的燈光強度。
在寒霜中,所有的光源都統一使用光度單位,這表明渲染管線會在渲染目標儲存對應的明度值。燈光強度可以通過相對(不同光源的光照比率)或是絕對(真實的光照單位)的方式參與計算。在實際使用中,我們發現直接使用光線強度的絕對值更加便捷,因為開發人員可以直接使用燈光制造商網站或是參考物上提供的強度信息。
Remark 標記:當前有許多儀器可以被用來測量各種光源。使用圖21的入射光度計可以在靠近光源時檢測到明度值。也可以使用射線測試儀[7] 進行測量,可以理解為帶有一個不透明罩子的光度計,在一個特定的方向上去進行測量。我們無法直接測量光源的光照強度,但我們可以根據儀器和光源的距離去計算對應的光照強度,luminousIntensity = sqrt(illuminance distance)
圖21: 左:入射光度計,註意看上方的白色小球。 右:射線測試儀,可以像望遠鏡進行測試。 一些儀器會把兩種測量方式結合在一起
Exposure Value:曝光度(EV)通常作為控制光照強度[Ree14]的參數用於攝影學中。傳統意義上的EV定義瞭特定鏡頭下相機的幾種參數(快門速度, 感光度,光圈)。通過相機上感光元件[8]的幫助,使得攝影傢能夠在相機自動檔的輔助下在不同光照環境中完成拍攝,詳見5.1。然而如今的EV被更多的用於光照單位中,以2為底的對數進行表示,我們成為f-stop,一個正向跨度對應瞭兩倍的亮度,而相反負向跨度對應一半的亮度。其中每個值之間的跨度幾乎都是線性變換的,意味著+0EV到+1EV和+1EV到+2EV的大小基本相同。由於EV一開始不是為光照單位所設計的,因此它的定義根據不同設備的校準常數大不相同
上述等式中的Lavg為場景的平均亮度值,S為ISO算法,K則是光線測量的校準常數。在ISO為 2720:1974情況下推薦的K常數范圍是10.6到13.4。在各種攝影和測量儀器中比較通用的K值為12.5[9] (佳能Canon,尼康Nikon,Sekonic)和14(Minolta,Kenko,賓得Pentax)[Wikg]。而12.5的K值在各類渲染器中是最常用的參數[Wikic]。詳見表7中EV和明度的對應關系[10] 。對於區域光和自發光中強度較高的光源來說使用EV是比較理想的方式。因為美術人員對EV單位更熟悉,我們決定將K為12.5的EV值作為這兩種光源類型的強度單位選項之一,在EV和明度間轉換的公式為:
表8展示瞭寒霜中不同光源支持的光照單位,它們各自的優點會在下一節中具體講到。
表7:K=12.5時EV100和明度的對應關系6f1274403c315d18bbffe5bb495c5152表8:寒霜中不同類型燈光對應的光照單位
寒霜引擎中隻支持兩種精確光源:點光源和聚光燈光源。為瞭保證精確光源的物理正確性,它必須遵守學術界認可的“平方反比定律(Inverse Square Law)[Wikf]”見圖22。每個平面覆蓋的射線數量隨著距離增加而遞減。但這一定律隻對點光源有效,並不包括:
1. 泛光源(Floodlights)和分佈狹窄的探照燈(Searchlights),因為其光束是高度準直的。
2. 區域光或是像菲涅爾透鏡的光源(light like Fresnel lens)
圖22:平方反比定律:箭頭表示從光源發射出的通量,每個單位的箭頭密度隨著距離增加而減少。
Luminous Power發光功率:將平方反比定律寫為等式可以得出:
I代表亮度(每個單位表面的光照強度),E[11] 代表照度(每個單位區域內的亮度多少)。該等式在光照計算中需要距離單位是同類的(如m/cm/mm)。照度E可以在距離為0的時候為無窮大,但在真實世界中因為光源一定有大小,所以這種情況並不存在。在實時渲染中通常會通過添加偏移值的方式來規避數值問題。一個較好的經驗法則是將精確光源考慮為一個個體積較小的光源,避免讓場景物體穿過。在寒霜中1個單位對應1米的大小,精確光源在其中的占比為1cm:
和4.5小節中提到的一樣,artist可以在引擎中調整光度單位中的流明值來控制精確光源的強度。在光照計算的過程中,光照功率始終轉化為光照強度。可以在光照的立體角上的光照強度進行積分,從而計算得到光照功率。
等式16的立體角(solid angle)和錐體的幾乎相同。該等式[12] 的結果會像圖23所展示的一樣,照明梯度隨著光線的集中而變得更加明亮。但這帶來的耦合性問題使得artist在調整燈光效果時更難把控[13]。因此我們 降低瞭射燈的光照衰減和強度的耦合度,使用遮罩的做法,讓射燈的反射平面吸收一部分光線。為瞭使得射燈中心的圓形區域轉換的更平滑,我們在寒霜引擎中定義瞭射燈的發光功率參數:
對於精確光源中光照功率和光照強度在表9中有詳細的總結。為瞭完整性,我們在表中加上瞭錐形燈(frustum light)[Wiki](如矩形金字塔的形狀)。在寒霜中錐形燈和線燈被劃分為區域光,條形燈沒有提供對應的算法,使用場合也比較有限,無法共享點光源和聚光燈的光照路徑。
Light Calculation光照計算:下文會描述到不同類型光源的算法:
Point light 點光源:
圖23: 在相同光照強度下,縮小聚光燈的光照范圍會使得照明范圍內的亮度提升表9:精確光源和錐形光中光照效率(lm)和光照強度(cd)的轉換公式總結。聚光燈中的outer變量表示為燈光展開的角度。錐形燈中的Theta a和b表示錐形燈展開的角度。
考慮到面法線朝向光源,因此計算出來的結果需要遵循平方反比定律
Spot light射燈:
光照結果遵循平方反比法則:
Measurement 測量:為瞭驗證數據的準確性,我們使用測光儀對不同的光源進行測量,詳見圖24。表10中的結果驗證瞭數據中的光照效率和預估的燈泡強度的光照準確性。可以看出光源增加一倍光照強度(lumen)後其光照明度為原來的兩倍。最接近準確的測量結果就是針對燈泡的測量,以突出我們能夠測量的最大值,但這個方法依賴於燈泡的大小。我們並沒有對射燈進行任何形式的測量,因為其復雜性,和我們對這個燈光類型的定義有所不同。相反點光源是寒霜中比較準確的測量單位,它們能準確反應真實世界的物理燈光強度,因此得到的結果十分可信。
Attenuation 光照衰減:平方反比定律的唯一問題在於它的結果永遠無法為0。為瞭性能表現,支持光照剔除算法,渲染器必須對光照范圍進行限制。在一個固定范圍內使光照明度接近0。其中一個解決辦法是所見光照衰減的范圍,避免其他功能受到影響[Kar13],對此我們在標準距離內對其進行線性插值。
圖24:光照測量環境搭建:在四周墻壁反照率都很低的暗房內進行測量,右邊的圖可以看出燈泡在下方會產生自陰影,但燈光強度就非完全各項同性的瞭。所以在測量時儀器會和燈絲平行。每次測量之前,我們會先等待燈泡預熱一段時間,另一個值得註意的是測量儀器對任何障礙物(人體)和反射物體(白色表面的物表10:真實世界燈泡測量和遊戲內強度的對比
為瞭保證得到的結果不變,我們將距離的計算調整如下:
雖然可以使用一些簡潔的辦法達到效果,但產生瞭一些不自然的結果。因此第二種方法就是通過對加入臨界值來偏移等式,並把結果映射到0-1的范圍內[Mad11]。
e5a35d5f38a9e07e66a8a0f41bb98a3a
最後的結果比較理想,但由於值域的問題會產生視覺上的失真。可以通過加入Window方法解決,並保證光照范圍處於0的狀態,見圖25。
圖25: 明度:圖一展示瞭不同的window方法在不同距離軸交叉時的不連續性。Window1在殺戮地帶中被使用到[Kar13]的window方法,Window2會有視覺失真的問題,window3是一個比較平滑的梯度方法。第二第三兩幅圖表分別展示瞭光照半徑衰減為10和40的window函數。可以得出隨著光照半徑增加,曲線和參考物的擬合
N是被開放出來調整光滑度的參數。我們在寒霜中采納瞭Karis提出的[Kar13]window1 中n=4的做法。它被廣泛引用於精確光源和區域光之中。但由於lerp標準是放射狀(sqrt(x)/sqrt(d))的,因此不適用於非球形的燈光,例如管狀燈和矩形燈。為瞭性能原因我們接受瞭這類的缺點,artist仍然能在必要的情況下調整這些燈光的光照范圍。
以上討論內容的代碼呈現如下:
1 float smoothDistanceAtt(float squaredDistance , float invSqrAttRadius)
2{
3 float factor = squaredDistance * invSqrAttRadius;
4 float smoothFactor = saturate(1.0f - factor * factor);
5 return smoothFactor * smoothFactor;
6}
7
8 float getDistanceAtt(float3 unormalizedLightVector , float invSqrAttRadius)
9{
10 float sqrDist = dot(unormalizedLightVector , unormalizedLightVector);
11 float attenuation = 1.0 / (max(sqrDist, 0.01*0.01));
12 attenuation *= smoothDistanceAtt(sqrDist , invSqrAttRadius);
13
14 return attenuation;
15 }
16
17 float getAngleAtt(float3 normalizedLightVector , float3 lightDir ,
18 float lightAngleScale , float lightAngleOffset)
19 {
20 // On the CPU
21 // float lightAngleScale = 1.0f / max(0.001f, (cosInner - cosOuter));
22 // float lightAngleOffset = -cosOuter * angleScale;
23
24 float cd = dot(lightDir , normalizedLightVector);
25 float attenuation = saturate(cd * lightAngleScale + lightAngleOffset);
26 // smooth the transition
27 attenuation *= attenuation;
28
29 return attenuation;
30 }
31
32 // Process punctual light
33 float3 unnormalizedLightVector = lightPos - worldPos;
34 float3 L = normalize(unnormalizedLightVector);
35 float att = 1;
36
37 att *= getDistanceAtt(unormalizedLightVector , lightInvSqrAttRadius);
38 att *= getAngleAtt(L, lightForward , lightAngleScale , lightAngleOffset);
39
40 // lightColor is the outgoing luminance of the light time the user light color
41 // i.e with point light and luminous power unit: lightColor = color * phi / (4 *PI)
42 float3 luminance = BSDF(...) * saturate(dot(N, L)) * lightColor * att;
下一篇