Carved Marker

Z buffer 和 W buffer 簡介 [Part 2]

前面把 Z buffer 的原理做了一個大概的說明,聽起來 Z buffer 似乎是個很理想的技術。但是,實際上 Z buffer 有一個很大的問題,就是精確度的問題。

在前一頁後面所提到的,兩個非常接近的平面所出現的 Z fighting 情形,其實是相當少見,而且很容易避免的。當然,遇而還是會看到有一些遊戲會出現這種情形。不過,Z buffer 最嚴重的問題是在離觀察者較遠的部分。如果 Z buffer 的精確度不夠,而場景又很遠的的話,那遠處的東西就會出現一些非常奇怪的現象。下圖是一個例子:

Z aliasing No Z aliasing
Z aliasing無 Z aliasing

當然,上面的例子是比較極端的情形。實際上一般情形下並不會有這麼誇張的 Z aliasing 現象。不過,我相信大家多少都在一些場景較大的遊戲中,看過類似的情形。

為什麼會有這樣的現象呢?這就要從 Z buffer 的結構談起了。如果前一頁所說的,一般的顯示晶片,是把 Z 值限制在 0 ~ 1 的範圍,再用一個定點數去表示它。例如,一個 16 位元的 Z buffer,可能會用 0 ~ 65535(一個 16 位元數字可表示的範圍)來表示這個 0 ~ 1 之間的 Z 值。

如果 Z buffer 的分布在 eye space 中是線性的,也就是它的每個數字之間的間隔都相等的話,那這樣的精確度應該是蠻高的才對。因為,假設觀察者可以看到一公里遠的東西,那每個間隔就是約 1.5 公分。如果用更高精確度的數字來表示的話(像是 24 位元數字),那精確度還會更高。然而,Z buffer 在 eye space 中並不是線性的。它是在 projection space 中為線性。

如果你覺得這些聽起來像是外星話的話,現在就要來「翻譯」這些外星話。首先,先來看一張示意圖:

Hyperbolic Z

上圖是一個眼睛在透視投影的情形下,觀看場景中的一個紅色平面的情形。靠近眼睛的平面(上面有黃色點的)是代表投影平面,也就是 3D 繪圖中的螢幕。黃色的點紅色平面投影到螢幕上的 pixel,他們當然是等間距的。但是,注意看這些「等間距」的 pixel,他們所對映的 Z 值(也就是 Z 軸上的那些灰色的點),並不是等間距的。實際上,離眼睛愈遠的 pixel,其 Z 軸上的間距就愈大。

這其實透視投影的一個明顯的性質。因為在透視投影的情形下,愈遠的東西看起來愈小,所以,在螢幕上同樣的間距,在比較遠的地方,就會變得比較大。因此,雖然三角面是平面,但是它在每個 pixel 上的 Z 值卻不是線性的變化。因此,就無法用線性內插來計算三角形內部的 pixel 的 Z 值。但是,要正確計算出每個 pixel 上的 Z 值,會需要一個除法的動作,而除法是很討厭、很花時間的動作。

早期的顯示晶片無法花費一個除法器在 Z buffer 上面。所以,一個方法是在 Z buffer 中,不要存放 eye space 的 Z 值,而改成存放 projection space 的 Z 值。這樣一來,Z 值在 projection space 就會變成是線性的,就可以簡單地用線性內插來計算三角形內部的 pixel 的 Z 值了。這也是目前幾乎所有顯示晶片的 Z buffer 的設計。

不過,在 projection space 中的 Z 值,就像上面的圖所顯示的一樣,有一個很重要的特性:它所對映的 eye space 的 Z 值間隔,在愈遠的地方就愈大。所以,Z buffer 的精確度,如果以 eye space 來看的話(這樣看才有意義),就會變成不平均的分布。離觀察者愈近的地方,其精確度會比遠的地方更高。而這個精確度的變化,會取決於 Z near 平面和 Z far 平面的位置。Z near 平面離觀察者愈近,且 Z far 平面離觀察者愈遠,則精確度的變化就會愈大,也就是遠的地方的精確度會愈差。

在這一頁最前面的兩張圖中,其 Z far 平面的位置是一樣的,但是左圖的 Z near 平面的位置,比右圖的 Z near 平面的位置近了一千倍。所以,在左圖中就出現了嚴重的 Z aliasing 現象,但是右圖就沒有出現這種現象。

所以,要儘可能避免 Z aliasing 現象,就要儘可能把 Z near 平面拉遠,而把 Z far 平面拉近。但是,實際上很多情形下,是無法允許這樣的設計的。比如說,在一個場景中,玩家可能會看到 50 公分遠的桌子上的東西,而同時看到窗外在一公里外的一座大基地。因此,Z near 平面不能設得比 50 公分要遠,但是 Z far 平面又得到一公里遠。以 16 位元 Z buffer 來看,那最遠處的間隔(也就是一公里遠的地方)會達到 30 公尺,也就是如果兩個 pixel 的間距小於 30 公尺的話,Z buffer 將無法分辨出正確的順序!而它在 Z near 處(也就是 50 公分的地方)的精確度則高達 0.0000076 公尺。這顯示出精確度分布是如此的不平均和不適當。如果改用 24 位元 Z buffer 的話,那情形會有相當程度的改善,在一公里遠處的精確度會提高到約 12 公分。這也是為什麼 24 位元 Z buffer 很少會顯示出 Z aliasing 的情形。

不過,即使是 24 位元 Z buffer 也不見得是完全理想。以上面的例子來說,如果 Z near 平面移到 10 公分處,在遠處的精確度就會從 12 公分降低為 60 公分。有些人可能會覺得,在一公里遠的地方,又有誰能分辨 60 公分,或是 12 公分呢?但是,問題在於,當兩個大的平面的距離小於 60 公分時,因為 Z buffer 無法分辨出正確的順序,就可能會在這一框是某個平面被畫出來,而在下一框卻變成是另一個平面被畫出來。如果這兩個平面的顏色差別很大,就會產生閃爍的現象,任何人都會很容易就注意到的。

有些顯示晶片採取一些方法來解決這個問題。一個簡單的想法是在 Z buffer 中使用浮點數,而不使用定點數。經過適當的設計,浮點數可以在某個特定的數字附近,提供更大的精確度範圍(一般情形是在 0 附近)。而一般的 Z buffer 在 Z far 附近會需要更高的精確度,所以可以把 Z buffer 在 Z far 平面以 0 表示,而 Z near 平面以 1 表示。這樣就可以得到更高的精確度。不過,浮點數在計算上比較麻煩。特別是 Z buffer 的運算中,常需要做加法和比較的運算,這都會比定點數的運算要麻煩很多。

另外一個方法是用非線性的 Z buffer。例如,可以把 Z buffer 切成很多個小區間,而每一個小區間中都是一般的線性 Z buffer。但是,可以在遠方分配更多的小區間,讓它的精確度可以提高。這也是一種解決精確度問題的方式。

其實,要解決 Z buffer 精確度問題,最簡單的方法就是在 eye space 中做線性內插。但是,前面已經說過,在 eye space 中的線性,在 projection space 並不一定是線性,所以它會需要額外的除法器。不過,有一個方法可以避免使用除法器,而只需要「倒數器」,「倒數器」比完整的除法器要簡單一些。這個方法就是先以較高的精確度,在 projection space 中,對 Z 做線性內插。對每個內插得到的結果,再用倒數器算出其倒數,也就是所謂的 W 值。這個 W 值的精確度可以較低,因為它在 eye space 中的分布是平均的。最後,再把這個 W 值和 "W buffer" 裡面的值做比較,就可以得到正確的順序。這個方法,相信有些人已經猜到了,就是W buffering

當然,另外還有一些別的方法可以實作出 W buffer。不過,不管是用什麼方法實作 W buffer,其最重要的性質就是在 eye space 中為線性分布。因此,16 位元的 W buffer 在遠處的精確度是非常理想的。以前面的例子來說,即使是 24 位元 Z buffer,在一公里遠處的精確度也只能到 12 公分。但是 16 位元 W buffer 則是每個地方的精確度都是 1.5 公分。因此,在這個例子中,16 位元 W buffer 在遠處的表現,甚至比 24 位元 Z buffer 更好。

而且,W buffer 還有一個好處,就是其 W near 平面(相對於 Z buffer 中的 Z near 平面)的位置是不重要的。也就是說,W buffer 可以同時兼顧眼前的桌子,和數公里外的巨大基地。而用 Z buffer 的話,如果想要能正確顯示出數公里外的巨大基地,那可能就得犧牲眼前的桌子了。

不過,因為 W buffer 的精確度是平均分布,它在 Z near 處的精確度就明顯不如 Z buffer 了。雖然說 Z buffer 在 Z near 處的精確度是過於高了(像是 0.0000076 公尺),但是,W buffer 卻可能會過於低。比如說,1.5 公分的精確度對於遠處的物體是絕對足夠的,但是對於靠近觀察者的物體,則是明顯的不足。比如說,桌上可能有一本厚度小於 1.5 公分的書。這時,1.5 公分的精確度是完全不夠的。

這樣聽起來,好像 W buffer 也無法解決問題嘛!其實並不是這樣的。如果有 24 位元的 W buffer,同時可以看到十公里外的東西(這應該算是非常的遠了),那它的精確度還是有約 0.6 公厘左右。這樣的精確度一般來說是相當足夠的了。而且 W buffer 也很容易使用,不需要對程式有什麼重大的修改。

目前 W buffer 最大的問題就是支援度不夠。有些顯示晶片根本就不支援 W buffer,而有些則只支援 16 位元的 W buffer。不過,目前很多顯示晶片都已經開始支援 W buffer,所以將來應該會有更多遊戲使用吧!

[Part 1] [Part 2]

8/14/2001, Ping-Che Chen


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

Copyright© 2000, 2001 Ping-Che Chen