着色(Shading)
着色是一個很重要的環節,它負責計算出顏色(光柵化只是填充像素格,換句話說是負責轉移顏色到屏幕),着色計算要考慮的因素通常有:光照、紋理、着色頻率(着色單位)等。
當然,現在着色的概念已經擴張到不是單純的輸出顏色,有時還可能需要輸出別的屬性(例如頂點着色器vertex shader可以輸出修改后的頂點屬性)。
Blinn-Phong 光照模型
光的能量基本規律
朗伯余弦定律認為,當光源照射方向與表面法線夾角越小,物體表面接受的光的能量就會越多:

其次,能量也存在衰減現象,其能量和與光源距離的平方成反比:
能量時刻向外擴散,因此隨着距離的平方越大,球表面面積也越大,每單位面積能量強度便越小

現實世界的光是非常復雜,這些基本規律其實還遠遠不夠,而前人基於這些簡單原理,就提出了Blinn-Phong 光照模型。
Blinn-Phong 是一個經典的經驗光照模型(非現實物理),它使用三種類型的光疊加而成得到光照結果。
環境光(Ambient)
現實世界存在大量間接光照(即多次反射后最終經過目標物體並最終反射到眼睛的光),但是計算這些間接光照過於復雜,Blinn-Phong模型直接將這類間接光的總和表示為環境光 \(I_a\) ,並且每個着色材質以一個系數 \(k_a\) 來控制其接受環境光的程度。
\(L_a = k_aI_a\)

漫反射光(Diffuse)
漫反射光則是常見的一種物理現象(粗糙的表面會將入射光均勻反射),因此漫反射光只通過計算入射光線與着色表面法線的點乘(朗伯余弦定律),並用max函數避免夾角為負數(0意味着攝像機看不到着色點),然后乘上光的能量(能量衰減后的),最后同理每個着色材質以一個系數 \(k_d\) 來控制其接受漫反射光的程度。
\(L_d = k_d(I/r^2)max(0,n\cdot l)\)

高光(Specular)
高光則基於鏡面反射現象(當眼睛觀測方向與光反射方向相向時會看到最亮的顏色),也就是說眼睛觀測方向 \(v\) 與反射方向 \(R\) 夾角越接近,則接受的光能量也越多:

而Blinn-Phong假設,一個半程向量(觀測方向\(v\)和入射方向\(l\)的中間方向)與表面法線的角度\(\alpha\),近似等於觀測方向\(v\) 與反射方向 \(R\) 的夾角(因為V接近R等價於h接近n,而且偏差也可以通過系數調整)。使用半程向量計算夾角的好處是相比后者計算量大大減少。使用p次方是為了體現高光特性(亮者更亮,暗者更暗,從而突出亮光),最后同理每個着色材質以一個系數 \(k_s\) 來控制其接受高光的程度。
半程向量: \(h = bisector(v,l) = \frac{v+l}{||v+l||}\)
\(L_s = k_s(I/r^2)max(0,cos\alpha)^p= k_s(I/r^2)max(0,n\cdot h)^p\)

Blinn-Phong 光照總結
\(BlinnPhong\)(光照結果) = \(Ambient\)(環境光)+ \(Diffuse\)(漫反射光) + \(Specular\)(高光)
\(L = L_a + L_d + L_s = k_aI_a + k_d (I/r^2) max(0,n\cdot l) + k_s(I/r^2)max(0, n\cdot h)^p\)
這樣我們可以通過以下三個參數來表示一個着色物體的材質:
- \(k_a\) 調整一個着色材質的基礎光照影響程度
- \(k_d\) 調整一個着色材質的漫反射接受程度
- \(k_s\) 調整一個着色材質的高光接受程度

光源模型
除了光照模型,我們還應該提供光源模型以供模擬完整的光照場景;假設我們當前有1個物體,\(n\) 個光源,在對該物體每個像素點着色(主要指前向渲染)的時候,我們需要進行 \(O(n)\) 次光照計算(根據每個光源模型特性計算一次)。

環境光(Ambient Light)
影響因素:強度、顏色
這種類型的光源來自空間中的任何地方,並以相同的方式照亮所有物體,可以理解成所有物體的基礎光照顏色。
點光源(Point Light)
影響因素:強度、顏色、位置、衰減系數
點光源是從一個具體的點位置向外散發光線。
聚光燈(Spot Light)
影響因素:強度、顏色、位置、衰減系數、方向、夾角
聚光燈就是在點光源的基礎之上加上了方向和夾角的概念,使其只在某些方向向外散發光線
方向光(Directional Light)
影響因素:顏色、方向、強度
可以理解成無數個平行光照射過來(類似太陽照射地面上的物體),無論在哪個地方收到的入射光方向都是一樣的。
着色頻率(着色單位)
給物體着色的一個問題是:我們該給物體每個面進行一次着色還是每個點進行一次着色。容易知道,着色頻率越高,其效果更真實,但是代價也就越高。
基於面的着色(Flat shading)
對每個三角面進行一次着色計算,因此根據光照模型的着色得到的結果將會是同個三角面都是同一顏色:

計算三角形法線:簡單地將兩條邊叉乘得出三角形的法線,並且還需要進行標准化
\(N_t=(V_1-V_0)\times(V_2-V_1)\)
基於頂點的着色(Gouraud shading)
對每個頂點進行一次着色計算,然后三角形內部像素點則通過在三角形的重心坐標插值(Barycentric Interpolation)得到顏色:

計算頂點近似法線:簡單地將與頂點銜接的面法線平均起來。
\(N_v=\frac{\sum_iN_i}{||\sum_iN_i||}\)

基於像素的着色(Phong shading)
基於像素的着色是實際上最常用的着色頻率,它對每個像素點進行一次着色計算,得到更加真實的效果:

計算像素近似法線:三角形內部像素點通過在三角形的重心坐標插值得到向量,最后這個插值出來的向量還需要標准化:

在現在的渲染管線中,往往提供了可編程的vertex shader和pixel/fragment shader來讓開發者決定頂點着色器和像素着色器分別進行什么着色計算。由於現代GPU性能的強大,我們都習慣在pixel/fragment shader進行光照計算以獲得更好的視覺效果。
