實時渲染基礎(2)光柵化(Rasterization)


圖元(Primitive)


在進行渲染之前,我們需要輸入裝配圖元數據,因此需要先定義圖元結構,一般有:三角形、四邊形、多邊形、線段、點

最常用的圖元就是三角形了,選擇它的理由有很多:

  • 三角形是最基礎的幾何面,可以組成任意邊形
  • 在三維空間中,3個頂點組成的面一定是三角形,但4個頂點組成的面不一定是四邊形(例如一張紙沿對角線折疊)
  • 三角形一定是凸多邊形(凹多邊形很容易導致算法錯誤或者更復雜的算法實現)
  • 三角形可使用易於實現的三角插值

三角形遍歷(Triangle Traversal)


有了圖元數據后,經過MVP變換和視口變換(Viewport Transform)后會得到屏幕空間上的頂點數據(三角形頂點無論如何變換最后頂點組成的形狀仍然為三角形),而接下來就需要使用光柵化去將這種頂點數據生成用於顯示的像素數組。

基於屏幕像素的采樣

一個最簡單的想法就是將屏幕像素進行遍歷,測試其每個像素是否在三角形內部。可以預見的是,這種光柵化方式必然帶來極高的時間復雜度。

for (int x = 0; x < width; ++x)
for (int y = 0; y < height; ++y)
    image[x][y] = inside(Vector2(x+0.5,y+0.5),tri);
//判斷點在三角形內部,若在內部則返還1,若在外部則返還0
int inside(Vector2 p,Vector2 vertexs[3]){
    for(int i = 0; i < 3; ++i){
        //假設三角形的邊edge都是逆時針方向
        Vector2 edge = vertex[(i+1)%3]-vertex[i];
        Vector2 vec = vertex[i] - p;
        int result = cross(edge,vec);
        //若點在三角形內,得到的叉積值應均為正數
        if(sign(result) == 0) return 0;
        //注:無背面剔除時,得到叉積值均為正數或者均為負數時都應返還true
    }
    return 1;
}

基於包圍盒的采樣

一個更好的方法是,用包圍盒對三角形進行包圍(可使用AABB盒),然后無需對整個屏幕的像素進行循環遍歷,而只需對包圍盒范圍內的像素進行循環遍歷:

基於自增的采樣

還有一個做法:

  1. 從最下邊的一行(頂點的最小y值)開始,找到最左邊的格子(頂點的最小x值)作為起點
  2. 從左往右遍歷,一旦遇到0就停止遍歷。
  3. 回到起點,然后往上一行,開始從左往右尋找新起點,一旦遇到1(意味着找到新起點了)就停止遍歷
  4. 找到新起點后重復步驟2和3,直到找不到新起點(這意味着步驟3最終遍歷到了頂點最大x值)

雖然循環次數少了(可能適用於狹長的三角面),但由於實現復雜,且不容易實現並行化計算(每一行每一列都有依賴關系),因此實際效率並不會快多少。

抗鋸齒(Anti-Aliasing)


信號與采樣

光柵化的采樣會遇到一些走樣(Aliasing)問題:鋸齒現象、摩爾紋。

鋸齒:光柵化不可避免的問題就是會產生鋸齒,其原因在於屏幕像素的采樣是離散的,不可能精確表示三角形。

摩爾紋:摩爾紋是采樣時可能會出現的另一種不理想的怪異現象。

解釋走樣問題之前,首先我們來認識一下信號:信號本質上就是一個函數,輸入參數,輸出結果。對於任何函數f(x),都有兩種表現形式:時域、頻域。其中我們最常見的便是時域的形式,也是現實世界的直觀形式,可以理解成即\(f(x)\)本身,把x當成時間,那么時域便是分析函數值關於時間x的關系。

而傅里葉告訴我們,任意一個函數 \(f(x)\) 可以由若干個頻率不同、振幅不同的余弦函數相加而成。

當我們每隔一定間隔采樣 f(x),實際上就是每隔一定間隔,分別采樣這些組成信號的余弦波並相加起來。下圖可以看到,低頻余弦波的采樣結果與實際余弦函數差別並不大,但是隨着頻率越來越高,余弦波的采樣失真越來越嚴重了。

尤其最下面的高頻余弦波,本該是劇烈起伏的,采樣的結果卻是平緩起伏,與實際結果已經嚴重不一致

因此,我們可以得出結論:當采樣頻率過低(采樣間隔過小)時,信號中的高頻信息會失真,這也是走樣問題的來源。反走樣要做的就是盡可能讓高頻信息量減少(高頻余弦波的振幅減少)。

那么,對於目標采樣頻率(目標分辨率)的反走樣基本原理為:

  1. 先把原始圖片當成一種信號,即信號 \(f(x)\) 輸入一個二維位置x,輸出一個像素顏色值(那么自然而然,這張圖片也可以由若干個不同頻率的余弦波組成)

  2. 接下來,我們對時域函數(圖片信號)進行卷積操作(即模糊圖片),卷積操作實際上就是每個點取周圍(包括點本身)的平均值。

容易想象得出到,經過卷積后的時域函數 \(f(x)\) 會變得更加平緩(圖片變得更加模糊)。這樣分離出來的高頻余弦波振幅更加小,換句話說,相當於我們把高頻信息剁掉了。

  1. 最后,我們對這張卷積后的圖片進行目標頻率的采樣,就能得到走樣現象不明顯的目標分辨率圖片。

SSAA(超采樣抗鋸齒)

SSAA(Super Sampling Anti-Aliasing 超采樣抗鋸齒):先進行高頻率的采樣並着色到高分辨率的buffer上,然后目標分辨率下的每個目標像素顏色會綜合混合高分辨率buffer中對應的多個像素顏色去生成目標畫面。(相當於,先進行高頻率采樣,接着進行卷積操作,最后再進行目標頻率采樣得到目標分辨率圖片)

例如,在4xSSAA算法中,假設最終屏幕輸出的分辨率是800x600, 4x SSAA就會先光柵化到一個分辨率1600x1200的buffer上並逐個像素着色,然后再直接把這個放大4倍的buffer下采樣至800x600的畫面

SSAA 是一種最原始的抗鋸齒方法,雖然得到的圖片鋸齒感少了很多,代價是:例如4x SSAA,光柵化和着色的計算負荷都比原來多了4倍,buffer存儲空間的大小也比目標分辨率多了4倍。

MSAA(多重采樣抗鋸齒)

MSAA(MultiSampling Anti-Aliasing 多重采樣抗鋸齒) 也是一種超采樣的抗鋸齒方法,但是做法更加聰明:光柵化只在采樣時采樣更多點,最后根據每個像素格子采樣結果的覆蓋率來填入像素格子。(相當於,先進行卷積操作,最后再進行目標頻率采樣得到目標分辨率圖片)

實際應用的話,采樣點位置不一定是標准細分正方形的中心位置,也可以是不規則分布的位置。

可以看到,4x MSAA在光柵化計算負荷比原來多了4倍,但是到了像素着色(pixel shading)階段的時候,每個目標像素只着色1次(4x SSAA對應一個目標像素則需要計算4個高分辨率像素,即4次着色),而且也不需要更大的buffer存儲(4x SSAA需要4倍的buffer存儲)

MSAA的一個問題就是和延遲渲染(Deferred Shading)框架並不是那么兼容。因為用延遲渲染的時候場景都先被光柵化到GBuffer上去了,不直接做着色。

FXAA(快速近似抗鋸齒)

FXAA(Fast Approximate Anti-Aliasing 快速近似抗鋸齒) 是一種圖像后處理技術。它先直接采樣得到目標圖像,然后通過像素顏色檢測邊緣。這種方法使得顏色變化劇烈的像素會被認為是邊緣,精度可能不好,但是處理速度非常快。

它不屬於先前的信號反走樣思路,而是屬於一種讓畫面看上去更舒服的trick。

后處理技術的抗鋸齒方法一般沒倍數概念,這是因為圖像不存在放大。

TAA(幀間抗鋸齒)

TAA(Temporal Anti-Aliasing 幀間抗鋸齒) 是最常用的圖像后處理技術。

TAA 的核心思想是將采樣點從單幀分布到多個幀上(從時間的維度上去采樣),即對上一幀圖像對應的位置進行采樣,得到的像素以一定比例混入當前幀的圖像像素中,這樣當連續的多個幀的數據混合起來以后,就相當於對每個像素進行了多次采樣。

采樣時還需要進行抖動操作(即每幀采樣的位置有一定隨機偏移),這是避免畫面靜止時導致重復對相同的位置采樣從而導致整個圖像相當於采樣次數沒有增加,造成抗鋸齒失效。

其中,TAA 使用了運動矢量(motion vector)來確定前一幀在何處進行采樣,然后將與當前幀的像素進行混合。

所謂運動矢量,其實就是一個空間中的物體(更准確說是物體像素點)在上一幀的位置與下一幀的位置之差,這往往要借助G Buffer信息來找到,具體算法稍微復雜。

TAA 的缺點是,由於每一幀圖像的像素顏色實際上是根據以前的幀來進行混合的,因此容易產生畫面延遲感;而且當物體運動過快時,會出現物體的殘影現象。

TXAA

TXAA 是后來NVIDIA提供的抗鋸齒技術,實際上就是TAA+MSAA的組合。

通過引入額外的深度信息來支持在延遲渲染(Deferred Shading)上使用MSAA,TXAA綜合了MSAA的強大能力與類似於CG電影中所采用的復雜的高畫質過濾器。還可以抖動幀與幀之間的采樣位置來獲得更高畫質。

DLSS(深度學習超采樣)

DLSS(Deep Learning Super Sampling 深度學習超采樣技術) 則是NVIDIA在Turing架構的時候推出的基於深度學習方法的圖像后處理技術。

DLSS 1.0 的基本工作原理是:利用NVIDIA神經圖形框架NGX,在超級計算機中以極低的幀率和每像素64個樣本對數萬張高分辨率的精美圖像進行離線渲染,訓練出一個深度神經網絡。基於無數個小時的訓練所獲得的數據,網絡就可以將分辨率較低的圖像作為輸入,輸出一個高分辨率的精美圖,並在一定程度上避免了出現 TAA 等傳統方法的模糊、不清晰和透明問題。

DLSS 也不屬於先前介紹的信號反走樣思路,而是通過低分辨率畫面去”猜測“出高分辨率畫面,這種猜出來的信息已經不算是來源於正確的原始信號了。

DLSS 2.0 則額外依賴了G Buffer的信息(這點與TAA有一定相似),從而“猜測”更加有依據、精確,能夠輸出鋸齒現象更加輕微、畫面更加清晰的高分辨率畫面。

可見性/遮擋(Visibility/Occlusion)


前面我們知道如何將一個三角形光柵化到屏幕上,而接下來的問題是當場景中有大量三角形時,我們還需要將它們光柵化的結果綜合起來。

畫家算法(Painter's Algorithm)

畫家算法是最原始的算法,就是簡單地將三角形進行排序,根據z值(離攝像機的前后距離)的順序,將三角形逐個進行光柵化,並且當光柵化遇到沖突時(以前有三角形光柵化占據了這個像素)強制覆蓋之。

畫家算法的缺點:

  • 以三角形的z距離進行排序,需要一定開銷,時間復雜度為\(O(nlogn)\),n為三角形個數
  • 以三角形為單位可能會發生不准確的z值排序,如畫家算法無法渲染出下圖

深度測試(Z-Test)

Z-Test則是一種主流且被硬件支持的遮擋剔除技術,其原理是提供一個額外存儲最小z值的buffer(經過變換后的坐標z值越小,實際上就是越接近攝像機),這樣實際有兩個buffer:

  • frame buffer:負責存儲像素顏色,也就是存儲圖像用的
  • z-buffer(depth buffer):負責存儲深度值(z值)

Z-Test技術實際上就是:每次光柵化且像素着色(Pixel/Fragment Shader)后的每個像素z值先和z-buffer的對應像素深度進行比較,若更小(意味着離攝像機更近),則像素顏色寫入frame buffer對應位置且z值覆蓋寫入z-buffer對應位置;若更大,則拋棄(discard)像素。

for (each triangle T)
for (each sample (x,y,z) in T){
    if (z < zbuffer[x,y]){      // closest sample so far
        framebuffer[x,y] = rgb; // update color
        zbuffer[x,y] = z;       // update depth
    }
    else{;} // do nothing, this sample is occluded
}

總結Z-Test有如下特點:

  • Z-buffer的時間復雜度是\(O(n)\),n為三角形個數
  • 絕大部分GPU硬件都實現了Z-Test算法,可以較快執行

Early-Z

Z-Test的一個缺點是,當花費了較多的像素着色計算(Pixel/Fragment Shader 負責光照和紋理采樣等大量計算)后得到的像素點有可能被拋棄,這就造成了計算性能的浪費。而 Early-Z 的原理就是把Z-Test的操作提前到光柵化之后和像素着色之前,這樣就避免了本就該被拋棄的像素進行額外的像素着色計算。

有一些情況是不適用於Early-Z技術的:像素着色計算可能會修改z深度值、Alpha Test等。這時候用Early-Z技術是不准確的,必須老老實實用Z-test,即等到光柵化和着色后才決定是否拋棄像素。

然而Early-Z性能是不穩定的,最壞情況就是從遠到近渲染每個三角形,這樣對每個三角形的所有像素都要進行像素着色計算(代價和Z-test一樣),而最好情況就是從近到遠渲染,這樣就不會出現overdraw。

Z-prepass 改進:使用額外一個pass(理解成跑多一次管線流程),每個三角形光柵化后僅寫入深度而不做任何像素着色計算(不輸出任何顏色),這樣所有三角形通過第一個pass后,我們可以得到記錄最小z值的屏幕深度圖(實際就是z-buffer);而第二個pass則作為正常渲染流程,只是每個三角形光柵化后需要關閉深度寫入並通過比較深度(比較是否相等)來決定是否進行該像素的着色計算,這樣就能保證屏幕每個像素只可能對應有最多一次像素着色計算。

總結Early-Z有如下特點:

  • Early-Z相比Z-Test更節省掉可能的像素着色計算,也是被大部分GPU硬件所實現的硬件技術,可以較快執行
  • Z-prepass 則是配合Early-Z的軟件技術,解決了Early-Z性能不穩定的問題

總結


確定好三角形圖元后,把所有三角形圖元的頂點進行MVP變換和視口變換得到屏幕空間上的三角形頂點。

之后對每個三角形(每3個頂點)將進行光柵化,再進行像素着色。抗鋸齒、z深度相關技術則有可能用在光柵化之中,也有可能是像素着色之后,因此就不多做分類。

此外,對於深度相關遮擋剔除技術還有Z-culling技術(用於TBR架構的移動端GPU)、hi-Z技術(用於延遲渲染技術),可以看參考[4]。

參考



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM