反走样
Gamma 的問題
在 3D 繪圖中,通常有一個很重要的假設,就是線性的反應曲線。所謂的線性反應曲線,就是說,如果把亮度值 1.0 和亮度值 0.5 相比的話,1.0 在螢幕上看起來,應該剛好是 0.5 的兩倍亮。這是一個非常重要的假設,因為只有在這個假設之下,才能很容易地進行亮度值的運算。舉例來說,如果兩個光源照在同一點上,我們會直接把兩個光源產生的亮度值相加,得到最後的亮度。但是,這只有在線性反應的情形下,才是正確的。
不幸的是,一般的螢幕,很少是線性反應的。大部份的螢幕的反應曲線是接近一個指數的曲線。這個指數曲線的指數部份,我們就把它稱為 gamma 值。當 gamma 值為 1.0 時,就是線性反應。一般常見的 CRT 螢幕,其 gamma 值則通常在 2.0 到 2.6 之間。這時,就需要一個修正(gamma correction)讓螢幕的顯示看起來和線性反應相同。
不過,在很多情形下,不正確的 gamma 設定,並不會帶來太大的困擾(除了畫面看起來會太暗或太亮之外)。不過,在有 FSAA 時,gamma 值的設定就顯得特別重要了。因為如果 gamma 值太大,FSAA 的效果就會不足,而使得結果看起來還是有鋸齒狀。而如果 gamma 值太小(小於 1.0),那 FSAA 的效果就會太過,而使畫面顯得比較粗。這個效應非常明顯,所以要得到理想的 FSAA 效果,一定要設定正確的 gamma 值。
那要如何找出螢幕的 gamma 值呢?這並不是個非常容易的事。不過,如果只想要找出「大概」的 gamma 值,那可以參考下面的圖(此圖由 PNG 發展小組提供):
這個圖的使用方式,是先把螢幕的 contrast(通常以半黑半白的圓形表示)調到最高,並把亮度調低,直到黑色部分成為真正的黑色(不能是深灰色)。然後和螢幕保持一定距離,看看對哪一個數字的區塊中,左右兩半的亮度最接近。要注意的是,很多因素都會影響到 gamma 值,包括螢幕的亮度設定(brightness)、四周的照明等等。同時,這個方式只能找出一個大概的 gamma 值,並不是很精確。
目前大部份的顯示晶片都提供 gamma 值設定的功能。所以,在找出螢幕的 gamma 值後,可以設定顯示晶片的 gamma 修正功能。經過 gamma 修正後,上圖中亮度最像的應該會是 1.0 的區塊。以下是 NVIDIA 的 Detonator 系列 driver 中,設定 gamma 值的畫面:
在 FSAA 簡介文章中,比較了 RGSS 和 OGSS 這兩種取樣方式。不過,事實上,取樣的方式還有很多種。在這裡我們就簡單介紹一些適合硬體實作的取樣方式。
為了展示取樣方式的不同,我們用一張簡單的圖片為例。這張圖是由 sin(x * x + y * y) 產生的。下圖是沒有進行任何 anti-aliasing 的例子:
上圖完全沒有任何 anti-aliasing 動作,所以可以看到明顯的鋸齒狀。另外,在右下角則有圓形條紋。這都是不應該出現的。
注意:本頁中所有的 FSAA 圖片,都應該在 gamma 值為 1.0 的條件下觀看。如果無法調整 gamma 值,這裡提供了 gamma 2.2 的圖片。
過去在 3D 繪圖系統中,因為系統設計上的限制,所以通常都是使用 OGSS。為了避免 OGSS 的一些不良性質,通常使用 64 倍的取樣(8×8)。以下對上圖做 64 倍 OGSS 的結果:
![]() |
![]() |
64X OGSS | 16X OGSS |
可以看到 64 倍 OGSS 提供相當不錯的效果。不幸的是,64 倍 OGSS 實在是太昂貴了(理論上它需要 64 倍的計算量)。要在即時繪製的系統上,提供 64 倍的 OGSS 恐怕是相當困難的。而且,即使是利用其它技術來減少計算量(像是只在邊緣部份進行超取樣),如果不是使用 deferred renderer,它還需要 64 倍的 frame buffer 空間。這在目前恐怕也是十分困難。
如果使用 16 倍(4X4)的 OGSS,當然就比較容易。但是,16 倍的 OGSS 並不能提供像 64 倍 OGSS 這麼好的品質。它看起來比較粗糙,而且右下角還是可以看到圓形失真條紋。
為了改善這個問題,可以使用非格狀的取樣點,像是 RGSS。非格狀的取樣點的好處,在 FSAA 簡介中也有簡單介紹過。下圖就是使用 16 倍非格狀取樣的效果:
![]() |
![]() |
16X jittered subsampling | 16X OGSS |
可以看出 16 倍非格狀取樣的效果,和 16X OGSS 差不多,但是鋸齒狀稍微少一些,而且圓形條紋也減少了。不過,不幸的是,圓形條紋並沒有完全消失。
為什麼會有這種圓形條紋呢?這是因為人的眼睛會傾向把亮度或顏色類似的東西,當成同一個東西,即使它們是分開的。所以,雖然這些線是分開的,但是它們之間如果有亮度類似的部份,就會被人眼認定成是同一個東西。這就是人眼對於失真現象敏感的原因之一。
要完全消除這種現象,就要試圖去除取樣點的規則性。前面所用的取樣方式,對每個 pixel 來說,其取樣點的位置都是固定的。如果對每個 pixel 都採用不同的取樣點,那就可以消除這種規則性,也就是由「雜訊」取代「失真」。因為人眼對隨機雜訊較不敏感,所以這是可行的。下圖就是兩個例子:
![]() |
![]() |
16X random jittered subsampling | 16X pseudo Poisson disc subsampling |
可以看到,圓形失真條紋已經是完全看不到了。而且鋸齒狀的現象,也比 16X OGSS 或 16X 非格狀取樣要稍微減少了一些。
在上圖中,左圖是以格狀的取樣點為基礎,對每個取樣點做一個隨機的移動(移動範圍不超過取樣點包含的範圍)。因此,每個 pixel 的取樣點位置都會不同。不過,因為沒有對取樣點的位置有什麼限制,所以有時取樣點可能會太靠近、或是離得太遠。所以,它產生的「雜訊」就會比較多。而右圖則是對取樣點位置做了限制,也就是兩個取樣點之間的最短距離不能低於某個數字(在這裡是約 0.7)。這樣可以避免取樣點會太靠近或太遠的情形,因而減少了一些「雜訊」。
結合各取樣點的結果
在前面討論了各種不同的取樣方式,不過,在拿到很多個取樣點的資料後,要怎麼計算出結果呢?
到目前為止,我們所用的方法,都是所謂的 box filter。所謂的 box filter 就是在一個區域內(不一定是方形的區域,不過在這裡也都是方形的),把這個區域內的數值加總,再求出平均值。在離散的情形下,就是所謂的 unweighted average,也就是把每個取樣點的顏色資料加起來,直接求平均值,就得到這個 pixel 的顏色。
Box filter 的好處是簡單,計算也不複雜,但是它並不能帶來最好的效果。它的主要缺點是在動態影像時表現出來,另外,它常常會讓畫面變得過於模糊,這也可以從訊號理論的角度來分析。不過,我們最好先別提訊號理論,而先從動態影像的角度來看這個問題。
動態影像的問題
我們之前在討論 FSAA 時,都是針對靜態影像進行討論。但是,其實 anti-aliasing 有一個很重要的用處,就是消除動態影像所帶來的失真現象。首先,我們先考慮完全沒有 anti-aliasing 的情形,並考慮一個很小的三角面,以慢速移動時,會發生什麼事:
上圖顯示一個小的三角形,在像點組成的格子點中,慢速移動時產生的現象。藍色的點是位於三角形內部的像點,也是在畫這個三角形時,會被畫出來的像點。可以看到,當三角形在移動時,像點的變化相當不規則。這會讓人覺得這個三角形在閃爍。而且,雖然三角形的面積並沒有改變,但是它所蓋住的像點數目卻都不太一樣,有時蓋住了八個像點,有時卻蓋住九個。這實在不是一個好現象。
事實上,這種形式的失真,對人眼是最敏感的。它的影響甚至要大過邊緣的鋸齒狀所帶來的影響。也許有人會說,畫面上很少會出現這麼小的三角形吧!但是,在 3D 繪圖中,不管物體原來有多大,如果它的距離遠,那就會因為透視投影的關係,而變得很小。如果觀察者不動,那它的移動速度對觀察者來說,也會變慢。所以,這種情形其實是十分常見的。
如果我們不是直接取樣,而是把一個像點中,三角面所佔的面積,來做為這個像點的顏色的話,應該就可以改善這個情形。當然,實際上要算出三角面在一個像點中所佔的面積並不容易,因為三角面可能會一部份會被別的三角面蓋住。這會讓情形變得很複雜。所以,比較簡單的方法,就是像前面所提到的,使用 super-sampling 的方式。也就是在一個像點中,取很多個取樣點,再把這些取樣點的顏色平均。結果大致上會如下圖:
可以看到,利用了 box filter 後,像點顏色的變化比較不會那麼突兀。但是,box filter 還是有一個問題。當一個三角面的某一部份都是在像點內部移動,而且所佔的面積都不變的時候,那個像點的顏色也不會改變。上圖中的第一列就是個例子。也就是說,只要三角面只在像點的範圍內部移動,像點的顏色就不會改變。但是,當它開始離開像點範圍時,包括這個像點,和它正在進入的像點,顏色就會開始改變。如果三角面移動的速度不快,那就會變成在大部份時間中,像點的顏色都不變(因為三角面都在這個像點的範圍內),然後突然開始變化(因為三角面開始移動到另一像點)。這樣還是會有一些「閃爍」的感覺。
如果我們現在改用另一個方法,也就是說,不要用一般的平均,而是加上一個權重,也就是 weighted average。給愈靠近像點中心的取樣點更高的權重,而在邊緣的取樣點則給比較小的權重,那即使三角面只在像點內部移動,也會有顏色的變化,那就不會有閃爍的感覺了吧!但是,不幸的是,這樣還是會有問題。當三角面從像點的中心移到邊緣時(但是還沒有超出像點的範圍),三角面的面積並沒有改變。但是,因為邊緣的權重較小,像點的顏色會改變。這樣會很奇怪。事實上,在三角面很小的時候,這樣反而會加強閃爍的現象。
解決這個問題的方法,是使用一個 overlapped filter。我們在前面所用到的 filter,都只是對像點範圍內的取樣點進行計算。但是,我們現在把 filter 的範圍擴大,讓它把周圍像點的一些取樣點也加進來進行計算。如下圖所示:
也許有人會有疑問,使用 overlapped filter 難道不會讓畫面變得太過模糊嗎?不過,要記住,我們使用的是 weighted average,愈靠近 filter 邊緣的取樣點,比重愈低。常用的權重包括 cone filter 和 Gaussian filter,cone filter 較為簡單,但 Gaussian filter 的效果比較好。如果取樣點的位置是固定的,那權重也會是固定的,所以權重可以事先計算,因此使用 Gaussian filter 和 cone filter 的複雜度不會有什麼差別。不過,如果使用隨機的取樣點的話,那 Gaussian filter 就會複雜很多了。
這裡提供一個細長的三角形,在空間中水平移動的例子,以 MPEG-1 格式儲存。有興趣的話,可以看看各種不同的取樣點,和 Gaussian filter 的效果。