Carved Marker

Per Pixel Lighting [Part 2]

要怎麼做到 per pixel lighting 的效果呢?基本上,這需要兩樣工具:第一樣是 DOT_PRODUCT3,另一樣是 cubic environment map。如果你不知道這些是什麼東西,也沒關係,因為後面會解釋到。

要做 per pixel lighting,首先複習一下 local lighting 的做法好了……基本上不管是用什麼形式的 local lighting,它都會有這樣的形式:

亮度 = ambient + diffuse + specular + emission

Ambient 是指環境光。因為在 local lighting 的模型中,我們只考慮到光源對物體的直接影響。所以,有很多東西是沒辦法處理到的。最常見的情形,就是物體背對光源的部分,在實際世界中,並不是全黑的。這是因為,光源對其它物體的散射光,也會照亮這些部分,只不過強度很弱而已。為了模擬這種情形,可以做一個很簡單的假設:散射光是由四面八方均勻射入。因此,它照射在物體上,其中一部分被物體吸收後,未被吸收的部分,會均勻向四面八方反射出去。因此,對觀察者來說,不論物體的位置在什麼地方,所得到的反射光強度都是一樣的。所以,結果就是:

ambient = [物體的 ambient color] × [光源的 ambient 強度]

Diffuse 是指物體在光源下的實際表現。除了極少數的物體,像是磨得很亮的金屬、玻璃等會完全反射入射光的物體外,大部分的物體,在大部分的情形下會散射入射光,所以叫 diffuse。為什麼物體會散射入射光呢?這是因為物體的表面往往是粗糙的,而粗糙的表面就會散射入射光。由於光是散射的,所以無論觀察者在哪裡,看到的散射光強度都一樣。不過,散射光的強度又是如何呢?在這裡,有一個很常用的模型稱為 Lambertian model。這個 model 認為物體散射光的強度,和入射光的關係是:

散射光強度 = [物體表面的法向量] ‧ [入射光的向量]

也就是兩個向量的內積。有一點很重要,就是這兩個向量都是 normalized,即正規化,所以長度都是 1。另外,如果物體的法向量是背對光源,那散射光的強度當然會是 0。所以,比較正確的寫法是:

散射光強度 = max(0, [物體表面的法向量] ‧ [入射光的向量])

再加上物體本身所吸收的一些入射光,就變成:

diffuse = [物體的 diffuse color] × [入射光的 diffuse 強度] × max(0, [物體表面的法向量] ‧ [入射光的向量])

再來是 specular。Specular 是當入射光對物體產生全反射的情形。如果觀察者的角度十分剛好,就會產生這種現象。隨便看周圍的一個物體,只要它不是非常粗糙,就可以看到它的某些部分是直接反射光源的顏色,而和它本身的顏色無關。這就是 specular 的部分。要怎麼做出 specular 的效果呢?基本上有很多種不同的模型,分別適用在不同的情形。不過,最常見的有兩種:Phong modelBlinn model。不過,因為它們有點複雜,所以在這裡就不多作說明了。基本上,只要記住一個重要的性質就可以:「specular 的顏色和物體的顏色無關」,和 「specular 的亮度和光源、物體、及觀察者的位置都有關係」。第一點很容易,因為它是全反射,當然就不會被物體吸收,所以不會被物體的顏色所影響。不過,為了處理某些特別的情形,通常我們還是會給物體一個 specular color,不過通常是白色。第二點則比較不是這麼明顯,不過,是否能看到全反射,會和入射光的角度及觀察者的角度有關,應該也不會太難理解。

最後是 emission。這是指會發光的物體,自己所產生的光。這個項目很無趣,因為它和入射光完全沒關係,就是一個「物體所產生的光」。所以,一般情形下是:

emission = [物體的 emission color]

比 ambient 更無趣,對吧! :)

綜觀上面的四個大項目,可以看出最複雜的部分就是 specular。Emission 通常很少用,因為一般很少會需要自體發光的物體,而且它也很簡單。Ambient 和 diffuse 則十分重要,但也不至於太複雜。之前我們所提到的 per vertex lighting,就是在三角面的各個頂點進行上面的計算。而我們要做的 per pixel lighting,當然就是要在每個像點上都進行這樣的計算了。

不過,這些光源的計算,其計算量是很大的。首先,需要計算出物體表面在該像點上的法向量。一般來說,都是由三角面的三個頂點的法向量,內插而得。不過,內插得到的法向量,還需要做 normalize 的動作才行。得到法向量後,還需要計算入射光的向量。對於 directional light(即只具有方向性的光源,例如非常遠的光源,像是太陽),這倒是不難,因為對任何地方,入射光的向量都一樣,而且光的強度也一樣。但是,對於 point light點光源)就不同了,要計算入射光的向量,就得把光源位置減去物體表面的位置(即該像點在空間中的位置),最後還需要 normalize。另外,point light 有時還會有 attenuation(即衰減),這也需要計算,才能得到入射光的強度。最後才是到實際計算反射光強度的部分,也就是上面提到的公式。其中 ambient 和 diffuse 還算好解決,但是 specular 就麻煩了。像是 Phong model 和 Blinn model 都包含相當複雜的運算。所以,這麼多運算,要對每個像點去做,似乎是不太可能的事。甚至,就算是對每個頂點去做,也都還嫌太多了。

不過,如果這是不可能做到的,就不會有這篇文章了。當然,要做到完整且精確的打光計算,確實是不太可能的。以 specular 來說,就很難做到精確,因為它的計算量實在是太大了。不過,如果我們可以接受一些誤差,讓一些影響不是這麼大的動作,只在頂點進行,有些地方使用內插或是查表的方式,這樣還是可以得到相當不錯的結果的。下面的圖就是把前一頁中的 per vertex lighting 使用較為不精確的 per pixel lighting 打光所得到的結果:

Per pixel lighting with no tessellation

這張圖是使用 6 個三角面。和前一頁的 vertex lighting 相比,同樣是使用 6 個三角面,但是即使是粗糙的 per pixel lighting 還是比 vertex lighting 更加精確得多。不過,因為上圖沒有加入 attenuation 的因子,所以它比 vertex lighting 的結果要亮一些。另外,應該也可以看出這個 per pixel lighting 並不是非常精確,因為它的結果並不如使用 600 個三角面的 vertex lighting(已經非常接近真正 per pixel lighting)的結果。

上面的例子中,是使用點光源。在後面我們會先用較為簡單的 directional light 開始,介紹如何利用 Direct3D 8 配合一般的 3D 加速硬體,來達到 per pixel lighting 的效果。

[Part 1] [Part 2] [Part 3] [Part 4] [Part 5] [Part 6] [Part 7]

12/15/2000, Ping-Che Chen


Sorry, Traditional Chinese only. This page is encoded in UTF-8.

Copyright© 2000 Ping-Che Chen