光線造就了物體的明暗,這章要講的就是如何處理與呈現光線明暗的問題。
向量
P 點為 (x1, y1, z1),Q 點為 (x2, y2, z2),從 P 到 Q 的向量 (vector) 為 ( x2 – x1, y2 – y1, z2 – z1 )。
向量的長度
假設向量 A 是 ( x, y, z ) 其長度 (Magnitude) 為 sqrt( x * x + y * y + z * z ),也就是三角形斜邊的公式,數學式寫做 | A |。
向量的內積
二個向量 A (x1, y1, z1) 和B (x2, y2, z2) 的內積 (dot product) 其定義為 x1 * y1 + x2 * y2, x3 + y3 ),數學式寫做 A • B。
向量間的角度
二個向量間的角度可以用下面的公式算出來 |A| |B| cosθ = A • B 。
向量的正常化
向量的正常化 (vector normalization) 也就是把向量的 x, y, z 各除以向量長度,使其方向性不變的前題下,將長度改為 1。你說為什麼要把向量正常化? 很多好處,像是算上面的 cosθ 時就不用再用開根號算 |A| 和 |B| 了。
向量的外積
光源和明暗的關係
以下圖為例,紅色的箭頭是平面的垂直向量、也就是 “法線” (normal)、可以用平面上任何三個點的向量外積算出來。藍色是從平面向量起點到光源的向量,可以用二點相減算出來。當二個向量的 θ 為 0 時光源最強。而 θ 為 90 度時光源最弱。換成 cosθ 就是 1 為最強,小於等於 0 為最弱。
一個由三角形構成的物件,計算法線時如何確保都是指向物件外部?
那假設我們做了一個 3D 物件,要怎麼確保計算出來的所有法線都是朝外的呢?
是的,我們算得出來。
首先,因為右手定則,我們知道 (向量AC) x (向量AB) 跟 (向量AC) X (向量AD) 算出來的法線是不同側的。
我們先計算(向量AC) x (向量AB),再比較已知的紅色法線。如果二條向量的夾角 θ = 0,那表示 △ ACD 的法線是 (向量AD) X (向量AC)。如果夾角 θ = 180度,那△ ACD 的法線是(向量AC) X (向量AD)。
在實務上,要確保一個物件上所有的三角形法線都是朝外,需要先用人工指定某一個三角形的法線方向,然後讓演算法將所有相鄰的三角形的法線算出來,再繼續遞迴算出整個物件的法線。
當然,這些事情不是 run time 計算的,是事前就算好了的 (像是交給 Blender 算好匯出成 JSON 格式之類的 (笑))。
拿實際的例子,假設有下面四個點為一個三角錐的四個頂點。
- 點 A ( 0, 0, 0 )
- 點 B ( 1, 0, 0 )
- 點 C ( 0, 1, 0 )
- 點 D ( 0, 0, 1 )
程式請參考 unittest.cpp 的 Sample_4。
Z Buffer
Flat Shading
Gourand Shading
有了三個頂點的法線值,就可以算出與光源的角度,就可以算出三個頂點的顏色。角度換算顏色的算法有簡單的和麻煩的,簡單是 RGB 各乘的 cosine 值就好。麻煩的要先從 RGB 轉 HSL,調整 L (Lightness) 值,再轉回 RGB。以下是簡單的。
if ( cos > 0 )
{
buf[anchor] = (int)(255 * cos);
buf[anchor + 1] = (int)(255 * cos);
buf[anchor + 2] = (int)(255 * cos);
}
最後再用內插法將三頂點的顏色漸層到整個三角型的每一個像素就可以了
Phong Shading
Phong 和 Gourand 的差別是 Gourand 是算出三角型的三個頂點的顏色,然後漸層到整個三角型。Phong 是算出三個頂點的法線向量,然後漸層法線向量到三角型內每一個像素。
跟 Gourand 比起來,Phong 需要大量的運算,對運算的要求更高,當然看起來比 Gourand 更為自然。Sample_5 是有些許偷懶的 Phong Shading 的實作,偷懶的地方是我漸層的是每個像素對光源的夾角 cosine 值,而且是用類似 Bresemham Line’s Algorithm 的方法用整數算出來的,畫面效果如下。
野生的旋轉的大理石猴子出現了! (笑)