1. 程式人生 > >光照、著色與材質

光照、著色與材質

我保證這是我目前寫過的所有科普教程中最不科普、最底層、最不想被讀、最難理解、最晦澀的一篇。
但我也保證這是我目前寫過的所有科普教程中最專業、最珍貴、最耗時、最盡力,最精益求精的一篇。
以下正文。

做3D開發(尤其是建模相關)的話,“材質”一定是個會經常遇到的概念,對於這個總在用的東西,你究竟瞭解多少呢?——至少我在不久前還是個只用引數不知原理的人。因此,今日特來與大家分享下我的學習心得和研究理解。

厭倦或者害怕晦澀枯燥的圖形學教科書嗎?不用擔心,在白藍紫的文章中不會出現那些過於嚴謹的繞口詞語——我只提供科普級別的讀物而已。So,Let's go!

首先,讓我們理清思路,看看這三者間的關係。
材質是反映物體與光線互相作用,最終影響著色的屬性;因此要講什麼是材質,就先要說說在實時渲染中是如何進行著色的;而著色又是根據光照來計算的,因此要先梳理下光照。


【光照】
光照就是根據光學原理計算光強變化。
光學知識在初高中物理課上都講過,什麼直線傳播啊,反射折射吸收啊,這些都是排在課本最前面幾章的入門知識,比力學電學容易得多,如果你不幸忘記了,可以敲開鄰居的門,請教一下人家孩子——或者問谷歌吧。

對於計算機渲染而言,我們有一些額外的說明,比如什麼光線都是有顏色的,不考慮不可見光,光源分為點光源,體積光,平行光,聚光燈等等……有興趣的人可以——不用我說了吧?找一本以專業嚴謹和枯燥催眠而著稱的清華大學出版社的圖形學書籍啃吧!

【著色】
著色就是根據光線和材質確定物體顏色的步驟啦。
首先要說明的一點是,光照模型——也就是模擬光效的計算方法——有很多種,而我只是針對實時渲染而言——你不想遊戲跟3dsMax的渲染一樣,一幀等一小時吧?——因此,這裡以常用的畫素級別的PHONG模型為準(另一種常用的是頂點級別的GOURAUD模型,這兩種是目前可行而常用的模型,詳見最下面的連結)


材質對光線的影響有這些:

1、漫反射
眾所周知,這是由於表面粗糙不平所引起的,它的結果只與光源位置有關而與視點位置無關,即:
物體表面相對於光源的角度決定它對來自光源的光的反射。表面越接近垂直於光線,被反射的漫射光線就越多。

根據lambert餘弦定律可以這樣求出結果:
漫反射強度=材質的漫反射係數×光源強度×(法線方向向量·入射光方向向量)
例如:0.5(反射一半)×1(最強,白色)×cos0(入射光垂直於法線,夾角0)=0.5(半減,灰色)
*所有的“強度”“係數”都是和“顏色”是等價的,因為將RGB三個分量計算過後,合併的結果就是顏色而不再是強度了。即“漫反射顏色=材質的漫反射顏色×光源顏色×(法線方向向量·入射光方向向量)”(下同)



*注意,“×”是數乘,而“·”則是點乘,也就是說最後兩向量的點積表示的就是它們夾角的cos值。(下同)
*注意,所有向量都是單位向量,並且入射光向量由反射點指向平面外。(下同)


*圖示,L是入射光,N是發現,R是反射光,eye是視點

2、鏡面反射
理想鏡面是不存在的,對一般的光滑表面而言,反射光會集中在一個範圍內,它的結果與視點有關,即:
鏡面反射強度=材質鏡面反射係數×光源強度×(法線方向向量·入射光與視點夾角一半的方向向量)^光澤度
例如:0.5(反射一半)×1(最強,白色)×cos0(視點位於反射光方向上,夾角0)^1(加權1)=0.5(半減,灰色)
*“^光澤度”是“光澤度次方”,光澤度越大,高光的亮斑越小
*標準演算法應該是指向視點的光線和反射線的單位向量的點乘(即上圖中的α角),但為了節省計算,我們按blin模型用此代替。
*那個半向量=視點方向向量+入射光方向向量,如果按下圖的箭頭,那麼就是視點方向向量-入射光方向向量

*圖示,H為半向量,它與eye的夾角等於它與L的夾角,這個角只是近似(你可以很容易的發現,其實它少了一半),但對於實時渲染而言,這已經足夠了。

3、環境反射
環境光是一種經過多次反射平衡的光,它的強度是均勻的,並且分佈是一樣的,即:
環境光沒有位置或方向上的特徵,只有一個顏色亮度值,而且不會衰減,所以在所有方向和所有物體表面上投射的環境光的數量是恆定不變的。

因此,在區域性光照明模型中(全域性光照明包含物件間的作用,需要光線追蹤等演算法。為了效率,實時渲染中可以忽略),我們用一個常量進行計算:
環境反射強度=材質環境反射係數×環境光強度
例如:0.5(反射一半)×1(最強,白色)=0.5(半減,灰色)

4、自發光
自發光是指物件自己發出的光,即:
自發光只增加自身顏色,而不影響場景內其他物件,因為它不是光源,自發光不參與光運算。

通常來說,我們用一個常量代表自發光強度,但事實上為了節約計算,我們通常會忽略它,而改用漫反射係數代替,或者說,將它的結果融入漫反射係數中,最終合成我們的老朋友——貼圖。


5、各向異性的反射、菲涅爾效應、焦散與色散、下表面散射
略,PHONG模型忽略它們,同時為了實時渲染效率我也忽略它們。想了解更多,請——這次換寂寞的百度吧。

如果你需要更多例子來理解這些引數對物體的影響,那麼你可以參考這個頁面:
http://www.anim8or.com/manual/10_materials.html
這裡有對比圖供你理解,不過也有一些非實時渲染才會用到的引數,其中引數的英文譯名可以參考本文隨後的中英對照表。

最後,讓我們來說重頭戲——PHONG模型。
PHONG模型就是漫反射、鏡面反射與環境反射的和,即:
畫素顏色=環境反射色+漫反射色+鏡面反射色
很簡單吧?但也許你會問,既然是和,那麼超過上限的怎麼辦?最簡單的當然是截斷不要,當作白色處理!但不管怎麼說,畢竟是三個顏色相加,超過上限的機率也太大了,這時候,你就需要分配權值再累加,比如說:
畫素顏色=環境反射色×0.3+漫反射色×0.4+鏡面反射色×0.3

不過呢,一般來說這個權值已經包含在材質的反射係數裡了,或者說,反射係數其實就是權值。

如果你理解了以上內容,那麼恭喜你,你的光學物理和向量數學過關了,所以——準備好把高考不屑一顧的光學知識上升到一個新的高度,讓我們見識下真正用於程式中的計算方法吧!

不過在此之前,為了便於理解和自學,還是讓我們先來了解一下這些之前被我隱藏掉的英文吧:
light=光
ambient=環境反射(光/色/強度/係數)=陰影色
diffuse=漫反射(光/色/強度/係數)=固有色
specular=鏡面反射(光/色/強度/係數)=高光色
emissive=自發光(光/色/強度/係數)=輻射色
shininess=rough=光澤度=鏡面反射加權係數n
ka=環境反射係數
kd=漫反射係數
ks=鏡面反射係數
N=法線方向單位向量
L=入射光方向單位向量
R=反射光方向單位向量
V=視點方向單位向量
H=半方向單位向量

通常而言,漫反射係數加上鏡面反射係數等於1,因為材質通常除了漫反射就是鏡面反射。另外,環境反射和漫反射的預設值常為:

ambient=0x333333(顏色)=0.2(係數)
diffuse=0xCCCCCC(顏色)=0.8(係數)

如果你已經暈了,那麼請從頭對照著圖示和範例再看一遍,如果還沒有暈,那麼做好暈的準備——來迎接挑戰吧!

這就是經典的openGL光照模型,很嚇人麼?
——不?那你一定學過計算機圖形學,或者是3D程式設計老鳥,或者是看了之前的內容256遍,再或者是在滿嘴跑火車。
——是?那你需要的是學好英語,或者對應之前講的理解,或者開始像德國boy那樣狂氣地毀鍵盤,再或者是……聽我講解?那麼好吧,讓我們對照著這個嚇人的上古魔咒一句一句地翻譯,以找出破解2012末日詛咒的法術。

首先要補充一個概念,如你所見,這裡計算的是頂點顏色(vertexcolor),原因是……嗯,這個一兩句話不好說明,但你應該知道,畫素的顏色是通過插值得到的,因此我們需要先計算出頂點的顏色。同時,為了光滑著色(避免一塊一塊邊界分明)又不失效率,我們可以使用GOURAUD明暗處理,即雙線性光強插值演算法。具體演算法大家可以自己谷歌——但我覺得,Stage3D已經為我們選好了插值的方法,所以我們需要的只是計算出每個頂點的法線——注意,是頂點法線而不是面法線!如果需要人工計算的話,頂點法向量可以用與其相鄰的所有多邊形法向量相加得出的均值來近似表示。

好了,讓我們開始翻譯這鬼畫符。

emission(material)指的是材質的自發光顏色,如你所見,這個值是作為常量直接被加的——但為了速度省略它更好。

ambient(lightmodel)指的是全域性環境光顏色,而ambient(material)則是材質的環境反射係數,與上面講的一樣,它們相乘得出環境色的最終值。但請注意倒數第三行,這裡也有個ambient(light)和ambient(material),這一對兒表示的是針對各個光源的環境色,主要用於計算物體間的影響(比如說,把手靠近國旗就會被它染紅),但通常為了速度也會省略(畢竟很玄的效果與很炫的幀頻相比,還是後者更有吸引力)。因此,我們只計算第一行的全域性環境光就好了——當然你也可以省略計算,直接用一個常量代替它,視覺上並不會相差多少,但效率上卻會有所體現。

之後這個肥大的漢堡包就是各個光源的結果總和。如你所見,左邊的西格瑪(∑)代表求和,這相當於是一個迴圈(for i=0;i<n-1;i++),按照右邊的公式計算出各個光源的結果,並將其累加——毫無疑問,只有一個光源是最快的,這就是當年pv3d為什麼只能為一個物件賦予一個光源的原因!而事實也表明,我們通常不會需要那麼多的動態光源——比起耗時的計算,我們更願意將結果烘焙進模型的頂點顏色中!

來看這個漢堡的第一層:一個分式乘以點光源效果(spotlight_effect),再將其結果乘到後面的三層——PHONG模型——上面。這是做什麼用的呢?是光線衰減。
顯而易見,對同一個光源而言,離得越近的物體越亮。這個點光源效果就是點光源強度的加強係數——忽略(視為1)也未嘗不可——而前面的分式則是距離比率。
這個分數表示的是“光的衰減比例為傳播距離平方的倒數”這一光學定律,即1/(d^2)。但如你所見,這裡的分母卻分明是(d+k)^2的展開式,為什麼除了距離d還多了一個係數k呢?很簡單,因為你不會希望d<1(特別是無限趨近於0)時,點光源的亮度變成無限大。因此,我們補上一個係數來確保分式的結果不會大於1——也就是光源原本的亮度。
需要注意的是,這個衰減僅對點光源有效,因為平行光是無視距離的,它的分式永遠是1,即不衰減,而環境光在第一行就算過了,至於其它種類的光(比如聚光燈),在實時渲染中則很少見(速度第一,影響速度的統統忽略!)。

最後,讓我們來看PHONG模型。
左邊的那個中括號“[”表示後面這三行的和,也就是三行累加的結果。
第一行之前說過了,究竟是去是留請根據需求自便,略過不再提。
第二行是漫反射色,第三行則是鏡面反射色,與之前的公式完全一致,也不必多說,不過:
1、其中diffuse(light)漫反射光和specular(light)鏡面反射光可以用一個光源顏色表示以節約記憶體——也更合理。
2、diffuse(material)材質漫反射係數和specular(material)材質鏡漫反射係數通常相加得1(0xFFFFFF),因此你可以只記錄一個引數,或者直接用通用的0.8(0xCCCCCC)與0.2(0x333333)常量來代替以加速計算。
3、向量乘法與之前所說的一樣,其中n是法向量,L是光線入射向量,s是半向量,shinine是光澤度。需要注意的是,這裡對其結果做了一個與0取較大值的計算,這是為了避免處於背面還被光線照亮(即便顏色是負的)的緣故。

【材質】
最後,來看材質。
材質是記錄物體表面對光線影響的引數集合,如果你已經理解了上面的一堆公式,那麼,材質所包含的引數也就顯而易見了。
雖然由於渲染引擎的不同(實時與非實時)和光照模型的不同(非實時光照模型會更細膩——也更耗時),材質的引數會有多有少——這一點開啟3dsMax的材質編輯器就可以發現——但對於實時渲染而言,總有那麼幾個引數是常見的,那就是:
ambientColor——環境色,材質對環境光顏色RGB各分量的反射係數
diffuseColor——漫反射色,材質對光源顏色RGB各分量的漫反射係數
specularColor——鏡面反射色,材質對光源顏色RGB各分量的鏡面反射係數
emissiveColor——自發光色,材質自身的顏色
shininess——光澤度,高光光斑的大小範圍
transparency——透明度,材質的透明狀態,與真透明不同,這裡的透明與厚度沒有關係
*對於其他叫法,請參考之前的中英文對照表

That's all for today~
希望對大家(特別是渴望寫shader或引擎的人)有所幫助。

最後,附贈另一種著色貼圖的公式:
最終顏色=漫反射貼圖(正常紋理)×光線貼圖×混合因子+反射貼圖×(1-混合因子)

相關參考:
四模型:http://hi.baidu.com/xiaoshumuzi/blog/item/7796d7dd4dcd55e577c638ef.html
材質庫:http://anim8ormaterials.bravehost.com/
著色器:http://3dke.blogbus.com/logs/63342436.html
對比圖:http://www.anim8or.com/manual/10_materials.html