1. 程式人生 > >遊戲紋理抖動的成因分析和解決方案

遊戲紋理抖動的成因分析和解決方案

一、問題

在相機隨主角移動的過程中,發現地圖的細節部分不停的閃爍。我們用草地圖片做背景,圖片濾波模式設定為point,然後讓攝像頭沿著X軸緩慢移動,可以看到在移動過程中,圖片的某些部分抖動得很厲害,這種程度的抖動對於一般遊戲來說都是不可以接受的,因此網上也有些解決方案,但大部分人對抖動的成因理解不夠,這篇文章將先分析抖動的成因,然後簡單介紹下抖動的解決方案。

二、成因

我總是喜歡把結論寫在前面,這樣沒有興趣的同學就可以直接略過後面的大部分分析:抖動的成因:

1. 螢幕實際上是一個取樣器,它對影象取樣

2. 而根據奈奎施特取樣定理,取樣頻率必須至少是影象頻率的兩倍,取樣後的影象才能完整的代表原影象。

   從這裡我們可以知道,螢幕解析度越低、圖片高頻訊號越高,越容易產生抖動現象,比如把上圖的影象代替成一個單色影象,顯然是不會發生抖動的。

OK,下面開始具體的分析了,我們把影象經過螢幕顯示的過程數學建模,影象經過螢幕顯示呈現到人們面前,實際上是分成了兩個步驟:

1. 取樣:影象處理器從影象中選取一個或多個點平均,得到一個RGBA值,取樣在shader中用tex2D函式實現,OpenGL有幾種取樣方式GL_NEAREST, GL_LINEAR和GL_LINEAR_MIPMAP_LINEAR,分別對應Unity的point, bilinear和trilinear三種取樣模式。

2. 濾波:取樣後的RGBA值通過像素髮光,這是一個低通濾波器,把一個沒有大小的RGBA值顯示為一個畫素大小的光點,可能大家對這步體驗不夠清楚,然而這確實有一個低通濾波的過程,不然人們只能在螢幕上看到一個個Dirac函式陣列。

為了敘述方便,我們把討論侷限於一維空間。我們將討論一個長度為1、高度(RGBA值)為1的線段的在一維螢幕上的顯示過程,以及線段移動時,頻域的變化。下圖展示了小方塊經過取樣和低通濾波後在螢幕成像的空域和頻域流程圖:

圖(1)

圖中,寬度為1,坐落在座標中心的小方塊經過寬度為0.1(取樣頻率為10Hz)的取樣函式取樣後,再經過一個低通濾波函式,最終呈現在螢幕上。從上圖中可以看到,在空域,小方塊最後的樣子和最初的樣子寬度變化了些,在頻域,小方塊的高頻分量被削弱了。

從上圖,我們也很容易理解奈奎施特取樣定理,被取樣後的小方塊在頻域是多個波峰疊加,波峰之間的距離就是一個取樣頻率,如果要求兩個波峰之間的頻譜不疊加,那麼其最高頻率的2倍必須不大於取樣頻率。

下面我們讓小方塊沿著x軸移動,看看取樣後的頻譜是個什麼樣子的。假設小方塊的空域函式是p(x),經過傅立葉變換,得到其頻域函式:

經過取樣頻率為10Hz的取樣函式取樣:

顯然,如果要讓小方塊在移動前和移動後,頻域大小保持不變,那麼可以令,從而:

頻域大小為:

也就是說,只要小方塊以整數倍取樣週期運動,那麼小方塊取樣頻域將保持不變,這也就是我們本文一開始的結論,扯了這麼多,無非是想說這個結論是有數學根據的。

三、在Unity中的應用

其實大家最關心的可能就是這個了:如何“消抖”,一個簡單的方法是降低影象的頻率(注意是頻率不是解析度),這個可以從兩個方面想辦法:

1.在影象資源的製作過程中使用各種濾波函式來模糊影象;

2.在匯入資源時設定濾波模式為bilinear或者trilinear。

然而這兩種方式有時並不能完全解決抖動現象,尤其是在影象縮小時,這時頻域將擴大,而無視濾波。

我們還得想其他辦法,根據第二節的結論,只要小方塊以整數倍取樣週期運動,其頻域大小就不變,那麼只要影象以整數倍畫素大小移動,就不會發生抖動。目前的一般做法有兩種:

1. Unity的sprites/Default 這個shader帶有一個Pixel snap選項,選上該選項,sprite在渲染時,其頂點位置將snap到整數倍畫素位置,從而保證整個sprite能以整數倍畫素大小移動。

圖(2)

2. 在指令碼中把位移snap到整數倍畫素大小。這種方式在攝像頭移動時很方便,只要給攝像頭加一個指令碼,就可以圓滿的解決這個問題。附件裡有這個指令碼,也很簡單。

四、總結

其實對於些螢幕解析度比較高,又不需要影象縮小,用bilinear濾波模式也就差不多了,這大概也是這個問題沒有多少人提及的原因吧。

抖動問題不限於螢幕取樣,在遊戲物件通過一系列變換過程中,如果其運算的截斷誤差(一個取樣過程)經過放大,能夠在螢幕上顯示出來,就會產生問題。