Carved Marker

Texture-based fog

Fog 是一種自然現象,在大部分地方並不算常見,但是在現在的 3D 遊戲中,則用得很多。

為什麼 3D 遊戲要使用 fog 的效果?其實主要的原因,是因為受限於硬體的能力,3D 遊戲並不能把遊戲中的整個場景畫出來,只能畫到一定的距離。在這個距離之外,就是 back clip plane 之後,就是一片空白。為了彌補這個空白,很多遊戲就使用 fog 的效果來掩蓋。

Fog 是怎麼做的呢?最簡單的 fog 效果,是假設 fog 是充滿整個空間中,而且十分均勻。所以,對觀察者來說,他所看到的 fog 的濃度,就和他與目標物的距離是一個簡單的線性關係。不過,「距離」本身並不是很容易計算的,因為要計算距離,通常需要用到平方根的計算,這樣的計算量可能太大了。所以,還可以再退一步:假設觀察者是在無限遠的地方(也就是 infinite viewer),這樣距離就是目標物在 eye space 的 Z 值了。

有了 fog 的濃度,再來就是要算出實際看到的顏色了。觀察者之所以會看到 fog,是因為 fog 會吸收一部分的光線,且散射出自己的顏色。所以,fog 愈濃,就會吸收掉愈多目標物的顏色,且散射出愈多自己的顏色。所以,可以大略用下面的式子來表示:

C = Cm (1 - Af) + Cf Af

在上式中,Cm 是目標物原本的顏色,Cf是 fog 的顏色,而 Af 則是 fog 的濃度。

要產生 fog,最簡單的方法就是 vertex fog。所謂的 vertex fog,就是把 3D 物體轉換到 eye space 之後,對每個頂點(即 vertex),以其 Z 值來決定 fog 的濃度,並以類似上面的式子來修改該頂點的顏色。這也是目前大部分產生 fog 的方法。

不過,有時我們會希望有比較複雜的 fog。例如,fog 的濃度不一定和目標物的距離成線性關係。這時,使用 vertex fog 的話,如果 3D 模型的 vertex 不是很多,就會出現不精確的情形。而且,非線性的計算量可能也會比較大。

另一個方法,就是用 fog table。所謂的 fog table,就是建立一個表,這個表存放 fog 的濃度和距離的關係。在畫物體時,對每個 pixel 都以其 Z 值去查 fog table 得到 fog 的濃度,再進行 fog 的計算。這個做法很快,因為使用 Z buffer 時,每個 pixel 本來就需要算出其 Z 值(或 W 值,但這又是不同的故事了…)。而查表也是很快的動作。最重要的是,這個方法是對每個 pixel 來做,而不是對 vertex 來做,所以不論 3D 模型是否有很多 vertex,都不會影響結果。

不過,在 OpenGL 中,並沒有規定要支援 fog table。OpenGL 並沒有指定一定要用什麼方式來達到 fog 的效果,而有些硬體並不支援 fog table,所以可能就是用 vertex fog。Direct3D 則有特別支援 fog table。

不論硬體有沒有支援 fog table,它總會支援 texture。而 OpenGL 支援在 eye space 下的 texture coordinate generation,所以我們可以用一個 1D 的 texture 來模擬 fog table 的效果。這就是所謂的 texture fog 了。

用 texture 來模擬 fog table 有很多好處。一來,幾乎所有的顯示硬體都支援 perspective corrected texture(在透視空間中正確做 interpolation,這需要 hyperbolic interpolator,但若不支援則 texture 一定會有明顯的錯誤),但是很少有硬體支援 perspective corrected color,所以 vertex fog 的效果常常會非常不正確。二來,texture coordinate generation 函式可以有很多變化,不一定要以 Z 來做 fog 的濃度。這樣可以做出一些有趣的效果,例如 volumetric fog 的效果。

當然,用 texture 來做 fog 也不是沒有缺點的。最明顯的缺點,當然就是「成本太高」。一般來說,我們希望 fog 是「免費」的。但是在絕大部分的硬體上,多用一個 texture 幾乎不可能是免費的。而且,如果場景本身就需要多重貼圖(例如,要使用 lightmap 等效果),那再加上一個 texture 成本通常更高,對大部分硬體,甚至還需要一個額外的 pass,這需要大量的頻寬。

我們現在就來看看,要怎麼用 texture 來做出 fog 的效果。首先,要先決定 fog 的濃度和距離的關係。然後建立一個 256x1 的 1D texture,也就是把 texture 當成 fog table。通常 fog 的顏色都是固定的,和距離無關,所以我們可以建立一個 luminance texture(也就是只有灰階沒有顏色的貼圖)。OpenGL 的 BLEND 這個 texture environment 剛好可以符合我們的需要。而 fog 的顏色則用 texture envionrment color 來指定。還有,貼圖的 wrap 方式要設成 CLAMP,這樣才會是正常的效果。

如果想要建立奇怪的效果,也可以試試讓不同的濃度的 fog 有不同的顏色。不過,這樣可能真的會很奇怪。

再來就是指定適當的 texture coordinate generation 函式了。這裡需要用 eye space 的 texture coordinate generation,在 1D texture 的情形下,可以把物體在 eye space 中的座標做一個線性組合。假設要做一般的 fog,也就是 fog 的濃度只隨著 Z 值而變化,且 fog 由 Z = -10 的位置開始出現,至到 Z = -210 時達到飽和,這樣很容易就可以算出需要的式子是:

S = -0.005 Z - 0.1

當然,這些值的決定和場景的大小有密切的關係,就和一般的 fog 一樣。這樣設定會產生類似下圖的結果:

Z based texture fog

要注意的一點是:OpenGL 在設定 eye space 的 texture coordinate generation 時,會乘上 M-1M 就是目前的 MODELVIEW 矩陣。因為我們不希望 fog 和物體的移動或觀察者的位置產生任何關聯,所以在設定 texture coordinate generation 函式時,要先把 MODELVIEW 矩陣設為單位矩陣。

看起來還不錯,但是和一般的 fog 也沒什麼不同。不過,比較有趣的東西的做法也是很類似的。

有些遊戲(例如 Quake 3 Arena)的某些場景中,有 volumetric fog 的效果。也就是說,它的 fog 是出現在距地面一定高度的地方。這樣的 fog 只要簡單修改一下 texture coordinate generation 函式,把它改成以 Y 為主而非以 Z 為主就可以了。例如,如果希望 fog 在 Y = 5 開始出現,而在 Y = -11 時飽合,那需要的式子是:

S = -0.0625 Y + 0.25

這樣的設定,在前面的場景中,會產生類似下圖的結果:

Y based texture fog

要注意的是,這種 fog 和前面的 fog 不同,它在 eye space 中會隨著觀察者而改變。所以要在 MODELVIEW 矩陣設定至觀察者位置後,才設定 texture coordinate generation 函式。

不過,這種 fog 還有一個問題。如果它太高,離觀察者很近,那離觀察者較近的部分,應該會比較清楚才對(也就是 fog 應該不會這麼濃)。但是,這樣就沒辦法用 1D 的貼圖來決定了,因為這不是線性關係。不過,如果不考慮 X,它還是可以用 2D 的貼圖來做(想要做出真正的效果需要 3D 貼圖)。

下圖是一個用 2D 貼圖做出來的結果:

YZ based texture fog

可以看出,距離較近的部分,fog 比較不會那麼濃。

以現在的 3D 硬體來說,拿貼圖來做 fog 可能有點浪費。不過,在某些情形下,還是很有用的。例如,模擬飛行的遊戲中,最常出現 volumetric fog 的情形,特別是低空飛行的情形,例如起飛/降落、或是直升機飛行。這可以產生很不錯的效果。

這裡可以下載示範程式的執行檔和原始碼。這個的示範程式是直接用 Win32 API 和 WGL 所寫,而不是用 GLUT 程式庫,所以不需要 glut32.dll 檔案。

操作方式:用方向鍵轉向和移動,用 [A] 和 [Z] 改變水平視角。按 [F] 鍵可以切換不同的 fog,而 [C] 鍵可以切換 fog 的顏色(只有第一種和第二種 fog 可以切換顏色)。

注意:這個程式需要支援 ARB_multitexture 這個 OpenGL extension 的硬體才能執行。

6/15/2000, Ping-Che Chen


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

Copyright© 2000 Ping-Che Chen