OpenGL中一種高效的線段反走樣技術


使人討厭的「走樣」

我在平常工做中經過傳統的OpenGL繪製函數繪製線段時,發現繪製出的線段邊緣充滿了「鋸齒」,而這種「鋸齒」在線段運動和旋轉時每每會更加明 顯(圖 1)。這種咱們不但願看到的「鋸齒」被成爲「走樣」,而消除這種「鋸齒」的過程就是咱們所說的「反走樣」。雖然OpenGL提供了諸如設置 GL_LINE_SMOOTH 屬性、多重採樣等線段反走樣的方法,但效果和質量受到不少方面的限制,並且不一樣的硬件廠商使用不一樣的反走樣算法,因此使得反走樣的結果在不一樣的GPU上有 着不一樣的效果。所以咱們須要一種更爲高效和通用的線段反走樣技術。 html

OpenGL中一種高效的線段反走樣技術

圖1 採用傳統OpenGL繪製方法繪製的線段 算法

爲何會「走樣」?

在介紹如何對線段反走樣以前,咱們必須瞭解爲何咱們繪製的線段會產生「走樣」。 緩存

咱們都知道,在數學的定義中一條線段是由兩個端點肯定的,而線段是沒有寬度和麪積的。但在計算機圖形領域中,爲了讓人的肉眼可以看到,必須給線段 必定的寬 度,因此咱們的線段一般是由兩個端點和一個寬度參數肯定的,而咱們計算機中圖形的寬度一般都是以像素爲單位的,所以咱們的線段寬度有多是1像素也有可能 是n像素。 函數

若是須要在白色的背景下繪製一條寬度爲1像素的黑色線段,從信號處理的觀點上來看,咱們能夠把這條線段看作一個值爲1的信號,而線段外部的區域信 號值爲0,若是不加任何處理,線段的邊界就是這樣一個不連續的階梯函數(圖2)。 由於幀緩存和顯示器所能容納的像素點是有限的,因此咱們須要對這個信號進行採樣。 spa

OpenGL中一種高效的線段反走樣技術

圖2 線段信號採樣示意圖 htm

咱們能夠看到:離散採樣(圖2 中用藍色虛線表示)的間隔不管多麼的小都沒法精確的表達它的不連續性,所以咱們不管怎麼提升分辨率,都沒法完全消除走樣。而根據耐奎斯特的信號採樣定理:要重構一個不走樣的信號,採樣率至少是信號最高頻率的兩倍。 get

即:C = B * log2 N ( bps ) 數學

所以,從理論上來講要繪製一條沒有走樣的線段,咱們必須擁有足夠大的信號頻率,也就是咱們須要無限放大咱們的屏幕分辨率才能完全消除走樣。 it

OpenGL中一種高效的線段反走樣技術

圖3 經過提升分辨率減輕走樣現象 效率

從圖3咱們能夠看出,雖然咱們提升了分辨率,可是走樣依然存在。所以,一味地提升分辨率是沒法完全解決掉線段走樣問題的,並且在時間、空間以及金錢有限的狀況下是不容許咱們這麼作的。

解決之道

計算機圖形學領域中普遍採用的一種方法是:限制信號的帶寬。也就是說既然沒法提升分辨率,咱們能夠將信號中沒法還原的高頻部分去掉以達到「反走 樣」的目的。這樣線段就再也不有明顯尖銳的邊界了,相反,線段的邊界處將變得模糊,這種將邊界模糊的過程咱們稱之爲「過濾」。咱們可讓信號經過一個低經過 濾器,來過濾掉信號中的高頻部分,以達到過濾的效果,這樣的過濾器有不少,能夠是簡單的線性過濾器也能夠是稍微複雜一點的盒狀過濾器或高斯過濾器。本文將 以高斯過濾器爲例,爲你們介紹整個過濾的過程。

OpenGL中一種高效的線段反走樣技術

圖4 低通高斯過濾器對2D信號的過濾效果

圖4演示了高斯過濾器對一個2D信號進行過濾操做的整個過程,首先圖4(a)表示未處理的線段信號,其中x和y軸表示線段所處平面座標系,z軸表 示圖像信號的強度,能夠認爲是RGBA顏色中的alpha值。其中左半部分z=1表示位於線段內部,右半部份z=0表示位於線段外部,這裏z=0和z=1 邊界處是不連續的。圖4(b)所表示的是一個高斯地同過濾器,將它與圖4(a)中的某一段信號作卷積後就獲得了圖4(c)中的效果。卷積在這裏等效於求出 過濾器與信號相交部分的體積,圖4(d)就是將全部信號與過濾器卷積後獲得的最終過濾效果。

OpenGL中一種高效的線段反走樣技術

圖5 通過半徑爲2的高斯過濾後的線段信號示意圖

從圖5能夠看到:通過過濾後的線段邊界將再也不是一段不連續的階梯函數,而是一段連續的平滑曲線。

預處理

至此,咱們彷佛已經解決了困擾咱們的反走樣問題,可是咱們須要注意到的是:在程序運行時咱們的GPU會逐像素地進行復雜的卷積計算,這種大規模的 計算對咱們來講無疑是一筆很大的開銷,會直接影響咱們程序運行的效率。所以,咱們須要將這部分的計算放在渲染以前進行,咱們稱之爲預處理。

如圖6所示,在預處理過程當中,咱們將半徑爲R的過濾器和寬度爲W的線段進行卷積,所獲得的強度值根據過濾器的位置變化而變化。當過濾器恰好位於直 線上(圖6a)時,咱們獲得的強度值最大,由於此時過濾器與直線重疊部分最多,(在圖4所示座標系中)重疊部分的體積也就越大。相反的,當過濾器位於距離 直線w/2+R的位置(圖6b)時,卷積所得強度值最小,由於此時過濾器與直線沒有重疊。而在過濾器從距離爲0移動到w/2+R處的過程當中,強度值在慢慢 變小。

OpenGL中一種高效的線段反走樣技術

圖6 過濾器位置影響卷積值

有了這個關係,咱們就能夠根據到直線的距離提早爲像素計算出對應的強度值而創建一個距離與強度值對應的查找表,在渲染時只須要根據像素與線段的距 離從查找表中取出強度值便可,而無需進行即時的計算,大大提升了咱們渲染的速度。而同一平行線上的全部像素強度值是同樣的,這樣理論上來講咱們就只須要算 W/2個像素的強度值就能夠繪製出整條直線了,計算量也大大減小。

然而,咱們並不但願計算量會隨線段寬度變化,咱們但願咱們的渲染過程的效率是穩定的,所以,咱們須要一張固定寬度的查找表。經過實踐發現,一張 32個強度值的查找表已經足夠應付任意寬度的直線了(圖7),若是以爲這樣不夠精細,你還可使用64個強度值的查找表,由於對於GPU來講,處理一個 32或64元素的1D紋理實在是小菜一碟。

OpenGL中一種高效的線段反走樣技術

圖7 32個強度值的查找表

如圖8中的代碼片斷所示,生成這樣一個紋理只需按照設定的強度值數量利用過濾器計算出相應數量的強度值就能夠了。惟一須要注意的是這個紋理是關於直線中心對稱的,以及紋理參數中縮放過濾參數要設置爲GL_NEAREST。

OpenGL中一種高效的線段反走樣技術

圖8 生成一張64個強度值查找表的過程

運行時

預處理只須要在CPU中運行一次,而當咱們將過濾後的紋理完成後,咱們的預處理工做就算告一段落,接下來就能夠進行渲染了。渲染時,咱們須要進行 兩種計算,一種是在CPU中的線段相關參數的計算,另外一種計算GPU的着色器中進行的,主要是利用CPU提供的參數在頂點着色器和片斷着色器中計算出真正 的位置和顏色。

首先咱們須要獲得一條具備寬度的「線段」,既然線段是沒有寬度的,那咱們就利用矩形來生成這樣一條有寬度的「線段」,所生成矩形的寬度就是線段的寬度,而咱們須要計算的也就是矩形的4個頂點的座標。

OpenGL中一種高效的線段反走樣技術

圖9 將線段端點沿兩側法向量平移w/2距離後獲得矩形4個頂點

計算矩形頂點的座標看起來也不是一件很困難的事情,只須要將線段的兩個頂點向兩側分別平移w/2距離就能夠獲得(圖9),而線段的平移方向正好是xy平面上垂直於該線段的法向量方向,所以咱們只須要計算這個法向量便可。

OpenGL中一種高效的線段反走樣技術

圖10 頂點着色器

有了法向量後,頂點着色器中只需將頂點和法向量相乘,再乘上w/2就能夠獲得平移後的頂點位置,最後再與線段的模型視圖投影矩陣相乘,計算出最終的頂點位置(圖10)。

OpenGL中一種高效的線段反走樣技術

圖11 片斷着色器

片斷着色器只需對紋理進行一次採樣獲得強度值再與線段顏色進行一次疊加就好了,這樣就能獲得一條任意顏色的線段。

最終效果

通過這樣的一系列處理,咱們就能獲得一條邊緣再也不尖銳的「反走樣」線段。同時,咱們放大後觀察能夠發現:線段邊緣由於過濾的效果而變得模糊了(圖5)。

OpenGL中一種高效的線段反走樣技術

圖12 過濾後線段邊緣變得模糊

而且對它進行拉伸或者旋轉都不會產生新的走樣(圖13)。

OpenGL中一種高效的線段反走樣技術

圖13(a) 通過反走樣處理的線段拉伸效果圖

OpenGL中一種高效的線段反走樣技術

圖13(b) 反走樣後線段旋轉效果圖

經過圖13的對比咱們能夠清楚地看出,通過預過濾反走樣處理的線段相比普通線段和硬件反走樣處理的線段鋸齒感明顯要弱了許多。這種處理方式所需的 存儲空間代價僅僅是額外的兩個頂點和一個寬度64的一維紋理,而運行時處理上也只是增長了一次法向量的計算,能夠稱得上是簡單高效。

OpenGL中一種高效的線段反走樣技術

圖13(c) 各類反走樣線段效果: 從左至右依次爲 普通線段 默認硬件反走樣 盒狀過濾器 高斯過濾器

這種方法的優點可總結爲:

1.支持任意對稱的過濾器,除了咱們使用的高斯過濾器外還支持盒狀或立方等過濾器。

2.可忽略過濾器算法複雜度對運行效率的影響,由於過濾計算是在渲染以前預先完成的。3.不管渲染任何線段,運行時開銷固定不變。

最後,但願這個方法可以對你們處理2D線段抗鋸齒問題可以有所幫助。

相關文章
相關標籤/搜索