您當前的位置:首頁 > 動漫

光柵化全面解析

作者:由 俊貝勒 發表于 動漫時間:2022-11-28

光柵化全面解析

光柵化全面解析

光柵化全面解析

0 兩種方法

圖形學中渲染過程基本上可以分解為兩個主要任務:

可見性和著色

。光柵化可以說是一種解決可見性問題的方法。可見性包括能夠分辨三維物體的哪些部分對攝像機是可見的。這些物體的某些部分可以被禁止,因為它們要麼在攝像機的可見區域之外,要麼被其他物體隱藏。

解決這個問題基本上可以透過兩種方式進行。你可以透過影象中的

每個畫素追蹤一條射線,找出相機與該射線相交的任何物體

(如果有的話)之間的距離。透過該畫素可見的物體就是相交距離最小的物體(一般用 t 表示)。這就是光線追蹤中使用的技術。請注意,在這種特殊情況下,你透過在影象中的所有畫素上迴圈,為每個畫素追蹤一條光線,然後找出這些光線是否與場景中的任何物體相交來建立影象。換句話說,該演算法需要兩個主要迴圈。外迴圈遍歷影象中的畫素,內迴圈遍歷場景中的物體。

在光線追蹤中,我們追蹤一條穿過影象中每個畫素中心的光線,然後測試這條光線是否與場景中的任何幾何體相交。如果找到相交,我們將畫素顏色設定為與光線相交的物件的顏色。因為一條射線可能與多個物件相交,所以我們需要跟蹤最近的相交距離。

for (each pixel in image) {

Ray R = computeRayPassingThroughPixel(x,y);

float tclosest = INFINITY;

Triangle triangleClosest = NULL;

for (each triangle in scene) {

float thit;

if (intersect(R, object, thit)) {

if (thit < closest) {

triangleClosest = triangle;

}

}

}

if (triangleClosest) {

imageAtPixel(x,y) = triangleColorAtHitPoint(triangle, tclosest);

}

}

請注意,在這個例子中,物件實際上被認為是由三角形組成的(而且只是三角形)。我們沒有迭代其他物件,而是把物件看作是一個三角形池子,然後迭代其他三角形。三角形經常被用作光線追蹤和光柵化的基本渲染基元(GPU 需要對幾何體進行三角化)。

光線追蹤

是解決

可見性問題

的第一個可能的方法。我們說這種技術是以影象為中心的,因為我們將光線從攝像機射入場景(我們從影象開始),而不是反過來,這是我們將在光柵化中使用的方法。

光柵化

採取的是相反的方法。為了解決可見性問題,它實際上是將

三角形 "投射 "到螢幕上

,換句話說,我們使用透視投影,將三角形的三維表示變成二維表示。這可以透過將構成三角形的頂點投射到螢幕上(使用我們剛才解釋的透視投影)來輕鬆實現。演算法的下一步是使用一些技術來填滿該二維三角形所覆蓋的影象的所有畫素。這兩個步驟如下圖所示。從技術角度來看,它們的執行非常簡單。投影步驟只需要進行透視分割,並將所得到的座標從影象空間重新對映到光柵空間。找出所產生的三角形覆蓋了影象中的哪些畫素,也非常簡單。

與光線追蹤方法相比,該演算法是什麼樣子的呢?首先,請注意,在光柵化中,我們不是先迭代影象中的所有畫素,而是在外迴圈中迭代場景中的所有三角形。然後,在內迴圈中,我們迭代影象中的所有畫素,並找出當前畫素是否 “包含 ”在當前三角形的 “投影影象 ”中。換句話說,這兩個演算法的內迴圈和外迴圈是對調的。

光柵化可以被粗略地分解為兩個步驟。我們首先使用透視投影法將構成三角形的三維頂點投射到螢幕上。然後,我們對影象中的所有畫素進行迴圈,測試它們是否位於所產生的 2D 三角形內。如果是的話,我們就用三角形的顏色來填充這個畫素。

// rasterization algorithm

for (each triangle in scene) {

// STEP 1: project vertices of the triangle using perspective projection

Vec2f v0 = perspectiveProject(triangle[i]。v0);

Vec2f v1 = perspectiveProject(triangle[i]。v1);

Vec2f v2 = perspectiveProject(triangle[i]。v2);

for (each pixel in image) {

// STEP 2: is this pixel contained in the projected image of the triangle?

if (pixelContainedIn2DTriangle(v0, v1, v2, x, y)) {

image(x,y) = triangle[i]。color;

}

}

}

下文將全面介紹光柵化的實現細節。【本文有較多公式,可新增

jinjun2050

獲取 pdf 版本】

1 光柵化簡介

渲染管道的最後一個主要階段稱為光柵化。光柵化是採用螢幕空間幾何圖形、片段著色器和該著色器的輸入並將幾何圖形實際繪製到低階二維 (2D) 顯示裝置的操作。 再一次,我們將專注於繪製三角形集,因為它們是三維 (3D) 圖形系統中最常見的圖元。事實上,在本文的大部分時間裡,我們將專注於繪製一個單獨的三角形。對於 幾乎所有現代顯示裝置,這種低階“繪圖”操作涉及為顯示裝置上的每個點或畫素分配顏色值。

在概念層面,光柵化的整個主題只是一個實現細節。之所以需要光柵化,是因為我們今天使用的顯示裝置是基於密集的矩形發光元件或畫素 pixels(術語 picture elements 圖片元素的縮寫)網格,每個畫素的顏色和強度在每一幀中都可以單獨調整。由於與基於映象管的電視工作方式有關的歷史原因,這些顯示器被稱 為光柵顯示器( raster displays)。

就其本質而言,與渲染管道其他階段相比,光柵化非常耗時。管道的其他階段通常需要按物件、按三角形或逐頂點計算,而光柵化本質上需要對每個畫素進行某種計算。

1,600 畫素寬 x1,200 畫素高的顯示器(屏 幕上大約有 200 萬畫素)非常流行。除此之外,光柵化實際上通常需要對每個畫素進行多次計算,我們意識到必須計算的畫素數量通常比給定幀中的三角形數量多 10 倍,20 或更多。

從歷史上看,在純粹的軟體 3D 管道中,多達 80%到 90%的渲染時間花在光柵化上是很常見的。這種級別的計算需求導致了一個事實,即光柵化是第一個透過專門的消費硬體加速的圖形化階段。事實上,到 2000 年代初,大多數 3D 電 腦遊戲開始需要某種形式的 3D 硬體。本文不會詳細介紹編寫軟體 3D 光柵化器所需 的方法和程式碼,因為大多數遊戲開發人員不再需要編寫它們。關於如何編寫一組光柵器的細節,請看 Hecker 在《Game Developer Magazine》中關於透視紋理對映的優秀系列文章 [76]。

儘管很少有遊戲開發者需要在現代遊戲中自己實現哪怕是光柵化管道的一個子集,但光柵化的話題仍然非常重要,即使在今天也是如此。光 柵化的基本概念引發了對整個渲染管道中一些最有趣和最微妙的數學和幾何問題的 討論。此外,對這些基本概念的理解可以讓遊戲開發人員更好地理解為什麼以及如何進行巧奪天工的渲染和效能瓶頸的出現,即使光柵化實現是在專用硬體中實現的。許多這些基本概念和低階細節幾乎可以在任何 3D 遊戲中產生視覺相關的結果。本文將重點介紹光柵化的一些基本概念,這些概念對於更深入地理解使用基於圖形處理單元 (GPU) 或計算機處理單元 (CPU) 的渲染系統的過程至關重要。

2 顯示和幀緩衝區

每件顯示裝置硬體,無論是計算機顯示器、電視還是其他類似裝置,都需要影象數 據源。對於計算機圖形系統,這種影象資料來源稱為幀緩衝區(之所以這麼稱呼,是 因為它是一個數據緩衝區,用於儲存幀的影象資訊,或螢幕的影象價值)。基本而言,幀緩衝區是 2D 數字影象:一塊記憶體,其中包含表示螢幕上每個點的顏色的數值。每個顏色值代表螢幕在給定點的顏色——一個畫素。每個畫素都有紅色、綠色 和藍色分量。放在一起,這個幀緩衝區代表要在螢幕上繪製的影象。每次需要更新 螢幕上的影象時,顯示硬體都會從記憶體中讀取這些顏色,通常每秒至少 30 次,通常每秒 60 次或更多次。

正如我們將看到的,幀緩衝區通常包含的不僅僅是每個畫素的單一顏色。雖然實際用於設定顯示器上每個點發出的光的顏色和強度的是最終的逐畫素顏色, 但其他逐畫素值在光柵化過程中內部使用。從某種意義上說,這些其他值類似於每 個頂點法線和每個三角形的材質顏色。雖然它們從不直接顯示,但它們對最終顏色的計算方式有重大影響。

3 概念化光柵化管道

光柵化整個幀所需的步驟如圖 1 所示。第一步是從幀緩衝區中清除任何以前的影象。這在某些情況下可以跳過;例如,如果已知場景幾何圖形覆蓋整個螢幕,則無需清除螢幕。在這種情況下,舊影象將被新影象完全覆蓋。但對於大多數應用程式 ,此步驟涉及使用渲染應用程式程式設計介面 (API) 將幀緩衝區中的所有畫素(在單個函式呼叫中)設定為固定顏色。

第二步是將幾何光柵化到幀緩衝區。我們將在本文的其餘部分詳細介紹這個階段,因為它是三個步驟中最複雜的步驟(到目前為止)。

第三步是將幀緩衝影象呈現 (present) 給物理顯示器。這個階段通常稱為交換或緩衝區交換(swapping or buffer swapping),因為從歷史上看,它經常涉及(並且在許多情況下仍然涉及)兩個緩衝區之間的切換——在顯示另一個時繪製到一個,然後在每一幀之後交換兩個緩衝區。這是為了避免在渲染期間出現閃爍或其他偽影(特別是為了避 免讓使用者看到部分渲染的幀)。然而,本文後面描述的其他技術將需要在呈現步驟(presentation step)中完成額外的工作。因此,我們將使用更通用的術語 present 來指代這一步。

3。1 光柵化階段

即使是一個簡單的光柵化管道也有幾個階段。應該注意的是,雖然這些階段往往存在於光柵化硬體實現中,但硬體幾乎從不遵循以下列表中概念階段的順序(甚至結構)。這個簡單的管道光柵化單個三角形如下:

確定三角形覆蓋的可見畫素。

計算每個畫素處可見三角形的顏色。

確定每個畫素的最終顏色並寫入幀緩衝區。

第一階段進一步分解為兩個獨立的步驟:

確定三角形覆蓋的畫素

確定在每個畫素處可見的三角形

本文的其餘部分將詳細討論每個流水線階段。

4 確定片段(Fragments):三角形覆蓋的畫素

4。1 片段

為了在渲染的光柵化階段取得進一步進展,我們必須將螢幕空間中的三角形(或更一般地,幾何體)分解成更直接匹配幀緩衝區中畫素的片段。這涉及確定畫素矩形或畫素中心點與三角形的交點。在光照和陰影中,我們使用術語片段來表示多邊形表面上給定點周圍的無限小表面積。片段著色器被描述為在這些微小的表面上進行求值。

在光柵化級別,片段具有更明確但相關的定義。它們是上述分解螢幕空間三角形以匹配畫素的過程的結果。這些片段可以被認為是螢幕空間中畫素大小的三角形碎片(pieces)。這些可以被視覺化為一個三角形,透過沿著畫素邊界切割成小塊(pieces)。許多這些片段(三角形的內部)將是正方形的,即畫素正方形的完整大小。我們稱這些畫素大小的片段為

完整片段

。然而,沿著三角形的邊緣,這些片段可能是放置在畫素正方形內部的被分為多個邊的多邊形,因此小於畫素。我們稱這些較小的片段為

部分片段

。在實踐中,這些片段可能實際上是在畫素中心拍攝的三角形的點樣本(類似於我們在光照和陰影中對片段的概念),但基本的想法是,片段代表了一個三角形的碎片(pieces),這些碎片影響到了一個給定的畫素。我們將把畫素看作是目的地或箱子,我們把覆蓋該畫素區域的所有碎片放入其中。因此,它不是一個一對一的對映。一個畫素可能包含來自不同(甚至是相同)物件的多個片段, 或者一個畫素可能不包含場景的當前檢視中的任何片段。

本文的其餘部分將使用這個更具體的片段定義。圖 2 顯示了一個覆蓋有畫素矩形邊界的三角形。圖 3 顯示相同的配置被分解成片段,包括完整的和部分的。圖中將片段稍微分開,以更好地展示部分片段的形狀。

4。2 深度複雜度

整個場景中的片段數量可以比螢幕上的畫素數量少得多,也可以多得多。如果幾何圖形僅覆蓋螢幕的一個子集,則可能有許多畫素不包含場景中的片段。另一方面,如果許多三角形在螢幕空間中相互重疊,那麼螢幕上的許多畫素可能包含多個片段。給定幀中場景中的片段數與螢幕上畫素數的比率稱為深度複雜度或過度繪製,因為該比率表示有多少全屏幾何體構成場景。通常,具有更高深度複雜度的場景光柵化成本更高。請注意,這是整個檢視的總體比率;即使幾何圖形僅覆蓋螢幕的一半,場景的深度複雜度也可能為 2。如果平均而言,被覆蓋的一半螢幕上的幾何圖形是四個三角形深度,那麼深度複雜度將是每個畫素兩個片段在整個螢幕上攤銷。

4。3 將三角形轉換為片段

三角形是凸的,無論它們如何透過射影變換進行投影(在某些情況下,三角形可能顯示為線或點,但它們仍然是凸物件)。這是一個非常有用的屬性,因為它意味著任何三角形與水平的畫素行相交(也稱為掃描線,由於歷史原因與基於 CRT 的電視顯示器)最多在一個連續的片段中。因此,對於與三角形相交的任何掃描線,

我們可以僅用最小 x 值和最大 x 值表示交點,稱為

跨度

(span)。因此,在光柵化期間三角形的表示由一組跨度組成,每條掃描線一個,三角形相交。此外,三角形的凸性還意味著與三角形相交的掃描線集合在 y 上是連續的;給定三角形有一個最小值和一個最大值 y,其中包含所有非空跨度。圖 4 顯示了三角形跨度集的示例。覆蓋在三角形上的暗帶代表將用於繪製三角形的相鄰片段的跨度。

三角形的 y~min~ 即最小 y 畫素座標就是三個三角形頂點的最小 y 值。類似地,三角形的最大 y 畫素座標 y~max~就是三個頂點的最大 y 值。因此,三個頂點之間的簡單 min/max 計算定義了必須為三角形生成的 (y~max~−y~min~+1) 的整個跨度範圍。

每個跨度的最左邊和最右邊的片段可能是部分片段,因為三角形的邊緣可能不會完全落在畫素邊界上。此外,出於同樣的原因,最頂部和最底部的跨度可能包含部分片段。三角形的剩餘片段將是完整的片段。

生成跨度本身只是涉及到將水平掃描線與三角形的邊緣相交。由於三角形的凸性,除非掃描線與一個頂點相交,該掃描線將與三角形的兩條邊恰好相交: 一個從三角形外跨入三角形,一個再次離開三角形。這兩個交點將定義跨度的最小和最大 x 值。

4。4 處理部分片段

完整的片段總是繼續到光柵化過程的下一個階段。然而,部分片段的命運取決於特定的渲染系統。在更高階的系統中,一個畫素處的所有部分片段都作為部分片段傳遞,最終畫素的可見性和顏色可能會受到所有這些部分的影響。然而,更簡單的光柵化系統不處理部分片段,並且必須在生成部分片段時決定是丟棄該片段還是將其提升為完整片段。解決此問題的常用方法是當且僅當它們包含畫素的中心點時才保留部分片段。這有時稱為幾何點取樣,因為整個片段是基於每個畫素內的單點樣本生成或不生成的。圖 5 顯示了與圖 3 相同的三角形,但部分片段被丟棄或提升為完整片段,具體取決於片段是否包含畫素的中心點。

當一個三角形的頂點或邊緣正好落在一個畫素中心時,這樣的圖形系統的行為是由與系統相關的填充慣例決定的。它確保如果兩個三角形共享一個頂點或一條邊,只有一個三角形會為畫素貢獻一個片段。這一點非常重要,因為如果沒有一個明確的填充約定,在三角形之間的共享邊緣可能會出現空洞(兩個三角形的部分碎片都被丟掉的畫素)或重複繪製的畫素(兩個三角形的部分碎片都被提升為完整的碎片)。沿著共享三角形邊緣的孔允許背景色透過本來是連續的、不透明的表面,使表面看起來有裂縫穿過。沿著共享邊緣的雙重繪製的畫素會導致更微妙的偽影,通常只有在使用透明或其他形式的混合時才會看到(見第 8。1 節)。關於實現點取樣填充約定的細節,見 Hecker 的《遊戲開發者雜誌》系列文章 [76]。

5 確定可見幾何

渲染幾何圖形的總體目標是確保最終渲染的影象令人信服地代表給定場景。在最高級別上,這意味著物體必須看起來被更近的物體正確遮擋,並且不能被更遠的物體遮擋。這個過程被稱為可見表面測定 (VSD),並且有許多非常不同的方法來完成它。這些方法都涉及在一個或另一個粒度級別上比較表面的深度,並以給定畫素處的最小深度物件(即最近的物件)是渲染到螢幕的物件的方式渲染它們。

歷史上,有許多不同的方法被用於 VSD。許多早期的演算法都是基於巧妙的排序技巧,包括在光柵化之前將幾何體從後往前排序。這是一個昂貴的命題,通常在 CPU 上每幀計算一次。到目前為止,今天最常用的方法是基於光柵化的方法:深度緩衝區。光柵化器是圖形管線中最早被加速的部分,有專門的硬體,這意味著基於光柵化器的可見表面確定系統可以實現高效能。深度緩衝器也被稱為 Z 緩衝區(z-buffer)。它實際上是更普遍的深度緩衝的一個具體的、特殊的案例。

5。1 深度緩衝

深度緩衝是基於可見性應以輸出為重點的概念。換句話說,由於畫素是我們渲染管道的最終目的地,可見性應該在每個畫素(或者更確切地說,每個片段)的基礎上計算。如果在每個畫素處看到的最終顏色是具有最小深度的片段的顏色(在繪製到該畫素的所有片段中),則場景將顯示為正確繪製。換句話說,在繪製到一個畫素的所有片段中,具有最小深度的片段應該“贏得”該畫素並選擇該畫素的顏色。出於討論的目的,我們假設點取樣幾何(即,沒有部分片段)。

由於常見的光柵化方法傾向於一次渲染一個三角形,一個給定的畫素可能會在一幀的過程中被來自不同三角形的片段重繪幾次。如果我們希望避免按深度對三角形進行排序(我們確實這樣做了),那麼應該獲得給定畫素的片段可能不是最後一個繪製到該畫素的片段。我們必須有某種方法來儲存當前最近片段在每個畫素處的深度以及該片段的顏色。

儲存了這些資訊後,我們可以在每次將片段繪製到畫素時計算一個簡單的測試。如果新片段的深度比該畫素當前儲存的深度值更接近,則新片段贏得該畫素。計算新片段的顏色,並將這個新片段顏色寫入畫素。片段的深度值替換該畫素的現有深度值。如果新片段的深度大於為畫素著色的當前片段,則忽略新片段的顏色和深度,因為片段表示當前畫素處最近的已知表面後面的表面。在這種情況下,我們知道新片段將在該畫素處被遮擋,因為我們已經在該畫素處看到了比最新片段更近的片段。圖 6 表示將片段從兩個三角形渲染到一個小的深度緩衝區。請注意更接近的三角形的片段如何總是贏得畫素(正確的結果),即使它是先繪製的。

因為該方法是按畫素計算的,因此是按片段計算的,因此每個三角形的深度是在每個片段的粒度上計算的,並且該值用於深度比較。由於這種更精細的子三角形粒度 ,深度緩衝區會自動處理無法使用逐三角形排序正確顯示的三角形配置。幾何圖形可以以任何順序傳遞到深度緩衝區。這種隨機順序可能有問題的情況是給定畫素的兩個片段具有相同的深度。在這種情況下,順序很重要,具體取決於用於排序深度的確切比較(即<或≤)。然而,這種情況對於幾乎任何可見表面方法都是有問題的。

深度緩衝區有幾個缺點,儘管其中大多數在現代 PC 或遊戲機上不再重要。深度緩衝方法的歷史缺陷之一隱含在方法的名稱中;它需要一個緩衝區或深度陣列值,每個畫素一個。這是一大塊記憶體,通常需要與幀緩衝區本身一樣多的記憶體。同樣,正如幀緩衝區必須在每幀之前清除為背景顏色一樣,深度緩衝區也必須清除為背景深度,通常是可表示的最大深度值。這些問題在 GPU 記憶體有限的手持和嵌入式 3D 系統上可能很嚴重。最後(仍然適用於 PC 和控制檯),深度緩衝區需要以下工作:

計算片段的深度值

在深度緩衝區中查詢現有畫素深度

這兩個值的比較

(僅適用於新的“獲勝者”片段)將新深度寫入深度緩衝區

在許多 GPU 上,深度緩衝區儲存在分層的壓縮資料結構中,允許使用大塊畫素進行快速拒絕測試。但是,對於基本實現,這是為每個片段計算的。對於大多數軟體光柵器,這個額外的逐片段的工作會使深度緩衝不適合持續使用。全軟體 3D 系統傾向於儘可能使用最佳化的幾何排序,為真正需要它的少數物件保留深度緩衝。例如,早期的第三人稱射擊遊戲渲染引擎將大量工作投入到環境的專門排序中,從而避免對它們進行任何深度緩衝測試。這留下了足夠的 CPU 週期來使用軟體深度緩衝渲染動畫角色、怪物和小物體(覆蓋的畫素比風景少得多)。

此外,深度緩衝區並不能解決高深度複雜度場景的潛在效能問題。我們仍然必須計算每個片段的深度並將其與緩衝區進行比較。但是,在某些情況下,它可以減少過度繪製的問題,因為沒有必要計算或寫入任何未透過深度測試的片段的顏色。事實上,一些應用程式會嘗試以大致從近到遠的順序渲染它們的深度緩衝場景(同時仍然避免在 CPU 上按三角形、按幀排序),這樣後面的幾何圖形可能會使深度緩衝失敗測試並且不需要顏色計算。

深度緩衝在硬體加速平臺上執行的 3D 應用程式中非常流行,因為它易於使用,需要很少的應用程式程式碼或主機 CPU 計算,並且可以以高效能生成高質量的影象。

5。1。1 計算每個片段的深度值

使用深度緩衝區計算片段可見性的第一步是計算當前片段的深度值。正如我們將看到的,

將工作得很好。然而,

工作良好而檢視空間值

不工作的原因是相當有趣的。

為了更好地理解深度值如何在螢幕空間中跨三角形變化的性質,我們必須能夠將螢幕上的點對映到投影到它的三角形中的點。這與拾取非常相似,我們將使用幾個概念。由於透視投影的非線性特性,我們會發現我們從螢幕空間畫素到給定檢視空間點的對映三角有點複雜。我們將通過幾個較小的階段來跟蹤此對映。對於本文的討論,我們將假設我們正在使用 OpenGL 樣式的矩陣,我們在檢視空間中向下看‑z 軸。

檢視空間中的三角形只是檢視空間中平面的凸子集。因此,我們可以透過平面的法向量

來定義檢視空間中三角形的平面,並且一個常數 d,使得平面上的點

滿足

回顧拾取,2D 歸一化裝置座標

中的一個點對映到檢視空間射線 t**r **使得

其中

是投影距離(從檢視空間原點到投影平面的距離)。投影到

處的畫素的任何視點空間必須與這條射線相交。通常,我們不能反轉投影變換,因為螢幕上的一個點對映到檢視空間中的一條射線。但是,透過知道三角形的平面,我們可以將三角形與視線相交,如下所示。檢視空間中落在三角形平面內的所有點 P 由公式 1 給出。此外,我們知道三角形上投影到

的點對於某些 t 必須等於 tr。用向量 tr 代替公式 1 中的點

並求解 t,

從這個 t 值,我們可以計算出沿投影射線的點

,它是投影到

的三角形上的檢視空間的點。這相當於發現

但是,我們現在只對

感興趣,因為我們正在嘗試計算深度緩衝的 perfragment 值。公式 2 的

分量是

作為對已知結果的快速檢查,請注意,在具有恆定深度

的三角形的特殊情況下,我們可以替換

代入公式 3,計算結果為預期常數

正如公式 3 中所定義的,

是一個計算每個片段的代價高昂的值(在一般的非常數深度情況下),因為它是具有非常數分母的分數。

這將需要按片段劃分來計算

,這比我們想要的要昂貴。然而,深度緩衝只需要能夠相互比較深度值。如果我們比較

值,我們知道它們隨著深度的增加而減小(因為檢視方向是‑z),給出深度測試:

≥DepthBuffer→新片段可見

但是,如果我們計算並存儲 zv 的倒數(乘法逆),那麼類似的比較仍然以相同的方式工作。如果我們使用所有 zv 值的倒數,我們得到

≤DepthBuffer→新片段可見

>DepthBuffer→新片段不可見

如果我們對等式 3 進行倒數,我們可以看到每個片段的計算變得更簡單:

其中所有帶括號的項在三角形中都是恆定的。事實上,這形成了 ND 座標到

的仿射對映。由於我們知道存在從畫素座標 (xs,ys) 到 ND 座標 (xndc,yndc) 的仿射對映(affine mapping),我們可以將這些仿射對映組合成從螢幕空間畫素座標到

的單個仿射對映。結果,對於給定的投影三角形,

其中 f、g 和 h 是實數值並且是每個三角形的常數。我們將給定三角形的上述對映定義為

從推導中可以看出

(或任何仿射對映)的一個有趣性質

同樣地

換句話說,一旦我們計算出任何起始片段的 RecipZ 深度緩衝區值,我們可以透過簡單地新增 f 來計算跨度中下一個片段的深度緩衝區值。一旦我們計算了給定跨度的基本深度緩衝區值,當我們沿著掃描線前進,填充跨度時,我們需要做的就是將 f 新增到每個相鄰片段之間的當前深度(圖 7)。這使得深度值的每片段計算確實非常快。並且,一旦計算了第一個跨度的基礎 RecipZ,我們可以將 g 新增到前一個跨度的基礎深度以計算下一個跨度的基礎深度。這種技術被稱為前向差分,因為我們使用一個片段的值與下一個片段的值之間的差值(或增量)來逐步更新當前深度。此方法適用於任何存在來自螢幕空間的仿射對映的值。我們將這些值稱為

螢幕空間中的仿射或螢幕仿射

(affine in screen space, or screen affine。)。

事實上,我們可以使用我們在投影期間計算的

值作為替代對於 RecipZ。在檢視和投影中,我們計算了一個 zndc 值,它在近平面等於‑1,在遠平面等於 1,其形式為

這是 RecipZ 的仿射對映。結果,我們發現我們現有的值

是螢幕仿射的,適合用作深度緩衝區值。這是我們前面提到的深度緩衝的特殊情況,通常稱為 z 緩衝,因為它直接使用

5。5。2 數值精度和 z 緩衝

在實踐中,螢幕空間中的深度緩衝有一些數值精度限制,可能會導致視覺偽影。正如前面討論深度緩衝區時提到的,物件被繪製到深度緩衝系統的順序(至少在不透明物件的情況下)只有在兩個表面(兩個片段)的深度值是在給定畫素處相等。理論上,除非所討論的幾何物件真正共面,否則這不太可能發生。然而,由於計算機數字表示沒有無限的精度,不共面的表面可以對映到相同的深度值。這可能導致以錯誤的順序繪製物件。

如果我們的深度值被線性對映到檢視空間,那麼一個 16 位的定點數深度緩衝區將能夠正確分類表面深度相差約 160,000 的近平面和遠平面距離之差的任何物件。對於幾乎任何應用程式來說,這似乎都綽綽有餘。例如,對於 1 公里的視距,這將等於大約 1。5 釐米的解析度。移動到更高解析度的深度緩衝區會使這個值變得更小。

然而,在 z 緩衝的情況下,可表示的深度值不是均勻分佈的在檢視空間中。事實上,正如我們所見,儲存到緩衝區的深度值基本上是

,這絕對不是檢視空間 z 的均勻分佈。深度緩衝區值在檢視空間 z 上的圖表如圖 8 所示。這是檢視空間 z 到深度緩衝區值的雙曲線對映——注意深度值隨著 Z 向遠處平面的變化而變化很小。

對此使用定點值會導致距離精度非常低,因為 z 的大間隔對映到逆 z 的相同定點值。事實上,一個常見的估計是 z 緩衝區將其 90%的精度集中在最近的 10%的檢視空間 z 中。這意味著遠處物體的碎片經常相對於彼此被錯誤地分類。

一種在 3D 硬體中流行的處理精度問題的方法稱為 w‑buffer。w‑buffer 以高精度對深度(通常為 1/w)進行插值的螢幕仿射值,然後在每個畫素處計算插值的倒數以產生在檢視空間中線性的值(即 1/w)。然後將這個反轉值儲存在深度緩衝區中。透過量化(降低插值期間使用的額外精度)並在檢視空間中儲存一個線性值,可以在一定程度上避免 z 緩衝區的雙曲線性質。但是,如前所述,不再支援 w‑buffers。它們還存在一個問題,即每個圖元在螢幕空間中儲存的值是非線性的,這不適用於某些後處理演算法。

另一種解決方案使用浮點深度緩衝區,大多數平臺都提供這種緩衝區。結合它們,我們翻轉深度緩衝值,使得深度值在近平面對映到 1。0,在遠平面對映到 0。0,並且一個>或≥的比較用於深度測試 [89]。透過這樣做,浮點數的自然精度特性最終抵消了 z 緩衝區值的一些雙曲線特性。接近 0 的浮點值增加的動態範圍補償了遠距離 z 值範圍的損失,其作用類似於舊的 w 緩衝區。也就是說,浮點深度緩衝區可能存在其他問題,過度校正並使最靠近相機的場景區域精度太低。這在渲染場景中尤其明顯,因為最接近相機的幾何圖形對觀看者來說是最明顯的。

最後,避免這些問題的最簡單方法是透過將近平面儘可能遠地移動來最大化深度緩衝區的使用,從而不會浪費靠近近平面的精度。所有這些方法都有依賴於場景和應用程式的權衡。

5。2 實踐中的深度緩衝

在大多數圖形系統中使用深度緩衝需要在渲染程式碼中新增幾個點:

建立幀緩衝區時建立深度緩衝區

每幀清除深度緩衝區

啟用深度緩衝區測試和寫入

第一步是確保使用深度緩衝區建立渲染視窗或裝置。這因 API 不同而不同,Iv(IvGraphics 圖形 API)在所有情況下都會自動分配深度緩衝區。請求建立深度緩衝區後(在大多數情況下,只是請求深度緩衝區,取決於硬體支援),必須在每幀開始時清除緩衝區。深度緩衝區的清除通常使用與幀緩衝區清除相同的功能。Iv 使用 IvRenderer 函式 ClearBuffers,但帶有新引數 kDepthClear。雖然可以使用獨立於幀緩衝區來清除深度緩衝區

renderer->ClearBuffers(kDepthClear);

如果您在幀開始時清除兩個緩衝區,則在某些系統上透過一次呼叫清除它們可能會更快,這在 Iv 中如下完成:

renderer->ClearBuffers(kColorDepthClear);

要啟用或禁用深度測試,我們只需使用 IvRenderer 函式 SetDepthTest。要禁用測試,請透過 kDisableDepthTest。要啟用測試,請透過其他測試模式之一(例如 kLessDepthTest)。預設情況下,深度測試被禁用,因此應用程式應在渲染之前顯式啟用它。最常見的深度測試模式是 kLessDepthTest 和 kLessEqualDepthTest。如果其深度值小於或等於當前畫素深度,則後一種模式會導致使用新片段。

深度值的寫入也可以啟用或禁用,與深度測試無關。正如我們將在本文後面看到的那樣,在禁用深度緩衝區寫入的同時啟用深度測試會很有用。呼叫 IvRenderer 函式 SetDepthWrite 可以啟用或禁用寫入 z 緩衝區。

6 計算片段著色器輸入

光柵化管道的下一個階段是透過評估當前片段的當前活動片段著色器來計算片段的整體顏色(以及可能的其他著色器輸出值)。這反過來又要求在當前片段位置

應用程式設定的每物件統一值(uniform)

頂點著色器從源頂點生成或傳遞的逐頂點屬性

每個片段的間接值,通常來自紋理

請注意,給定片段可能存在許多來源。作為著色器輸入源生成的一部分,它們中的每一個都必須對每個片段進行獨立評估。在計算了每個片段的源值之後,必須透過執行片段著色器來生成最終的片段顏色。在片段著色器中組合每個片段頂點顏色值、每個頂點光照值和紋理顏色有各種方法。著色器生成最終的片段顏色,該顏色將傳遞到光柵化管道的最後階段,即混合(本文稍後將討論)。

接下來的幾節將討論如何從我們列出的源中計算每個片段的著色器源值。雖然有許多可能的方法可以使用,我們將專注於在螢幕空間中快速計算並且非常適合大多數光柵化軟體甚至一些光柵化硬體的以掃描線為中心的特性的方法。

6。1 統一值 Uniform Values

值與管道中的所有其他階段一樣,每個物件的值或顏色最容易光柵化。對於每個片段,恆定的統一值可以直接向下傳遞給著色器。不需要對每個片段進行評估或計算。因此,統一值對片段著色過程的效能影響最小。

6。2 每頂點屬性 Per-Vertex Attributes

正如我們之前討論過的,每個頂點的屬性是從最後一個頂點處理階段(在我們的例子中,從頂點著色器)傳遞給片段著色器的變數。這些值僅在每個三角形的三個頂點處定義,因此必須進行插值以確定三角形中每個片段中心的值。正如我們將看到的,在一般情況下,要正確計算三角形的每個片段,這可能是一項昂貴的操作。但是,我們將首先檢視等深三角形的特殊情況。這種情況下的對映在計算上一點也不昂貴,即使在渲染非恆定深度的三角形時(尤其是在軟體渲染器中),它也是一個誘人的近似值。

6。2。1 恆定深度插值 Constant Depth Interpolation

為了分析恆定深度的情況,我們將確定恆定深度三角形的對映本質,從畫素空間,透過 NDC 空間,到檢視空間,透過重心座標,最後到逐頂點源屬性。我們首先從從畫素空間對映到檢視空間的特殊情況開始。

整體投影方程(從檢視空間對映到 NDC 空間到螢幕空間畫素座標)的所有形式

其中 a,c 不等於零。如果我們假設一個三角形的頂點都在相同的深度(即,檢視空間

等於三角形中所有點的常數

),那麼一個點在三角形內部是

注意 a,c 不等於零意味著 a′,c′不等於零,所以我們可以重寫這些使得

因此,對於恆定深度

的三角形,

投影在

平面上形成從螢幕頂點到檢視空間頂點的仿射對映。

重心座標是檢視空間頂點的仿射對映。

頂點屬性定義了從重心座標到屬性值的仿射對映(例如,Gouraud 著色)。

如果我們組合這些仿射對映,我們最終會得到一個從螢幕空間畫素座標到屬性值的仿射對映。例如,我們可以將這個從畫素座標到顏色的仿射對映寫為

其中

都是顏色(每種顏色都可能為負數或大於 1。0)。有關將三個螢幕空間畫素位置和相應的三重頂點顏色對映到三種顏色

的公式的推導,請參見 Eberly[35] 的第 126 頁。從我們先前對螢幕空間中逆 z 屬性的推導,我們注意到顏色

是常數 z 三角形的螢幕仿射:

與 1/z 一樣,我們可以簡單地透過計算三角形中“基本片段”顏色的前向差異來計算常量三角形的每個頂點屬性的每個片段值。

6。2。2 透視矯正插值 Perspective-Correct Interpolation

當使用透視投影投影在相機空間中沒有恆定深度的三角形時,生成的對映不是螢幕仿射的。從我們對深度緩衝區值的討論中,我們可以看到給定檢視空間中的一般(不一定是恆定深度)三角形,從 NDC 空間到三角形上的檢視空間點的對映具有以下形式

這些是射影對映(projective mappings),而不是我們在恆定深度情況下的仿射對映(affine mappings)。這意味著從螢幕空間到線性插值的每個頂點屬性的整體對映也是投影的。為了在透視投影中正確插入三角形的頂點屬性,我們必須使用這種更復雜的投影對映。

大多數硬體渲染系統現在以透視矯正的方式插入所有每個頂點的屬性。然而,這並不總是通用的,而且對於在低功率平臺上執行的舊軟體渲染系統來說,它太昂貴了。如果被插值的每個頂點屬性是來自每個頂點光照的顏色,例如在 Gouraud 著色的情況下,則可以在精度和速度之間進行權衡。請記住,Gouraud 著色首先是一種近似方法,在“正確性”的基礎上使用投影對映的理由有所減少。此外,Gouraud 陰影顏色的插值往往非常平滑,以至於很難判斷插值是否透視正確。事實上,Heckbert 和 Moreton[75] 提到紐約理工學院的離線渲染器在幾年前就在透視中錯誤地插值了顏色,直到有人注意到!因此,軟體圖形系統通常避免了昂貴的、透視正確的 Gouraud 顏色投影插值,而僅使用仿射對映和前向差分。

也就是說,其他每個頂點的值,例如紋理座標,並不能容忍透視矯正插值中的問題。對紋理進行光柵化的過程首先是對每個頂點的紋理座標進行插值,以確定每個片段的正確值。實際上,在光柵化器中插值通常是紋理座標(紋理座標乘以紋理影象尺寸)。 這個過程類似於對其他每個頂點屬性進行插值。然而,由於紋理座標的使用實際上與片段著色器中的頂點顏色有些不同,我們無法使用前面描述的螢幕仿射近似。紋理座標需要正確的透視插值。紋理座標的間接性質意味著雖然紋理座標在三角形上平滑而微妙地變化,但生成的紋理顏色查詢不會。

紋理座標的問題與仿射和投影變換的屬性有關。仿射變換將平行線對映到平行線,而射影變換隻保證將直線對映到直線。任何曾經看過一條又長又直的道路的人都知道,形成道路邊緣的兩條線似乎在遠處相遇,即使它們是平行的。透視,作為一種投影對映,不保留平行線。

仿射插值和投影插值之間差異的經典示例是紋理座標是棋盤格,以透檢視繪製。圖 9 顯示了作為影象的方格紋理,以及應用環繞到由兩個三角形(兩個三角形以輪廓或線框顯示)形成的正方形的影象。當頂部在透檢視中傾斜時,請注意,如果使用投影對映(圖 10)對映紋理,則垂直線會按預期會聚到遠處。

如果使用仿射對映對紋理座標進行插值(圖 11),我們看到兩個不同的視覺偽影。首先,在每個三角形內,所有的平行線都保持平行,垂直線不會像我們預期的那樣會聚。此外,請注意沿正方形對角線(共享三角形邊緣)的線條中明顯的“扭結”。這有可能乍一看似乎是插值程式碼的一個 bug,但稍微分析一下,它實際上是仿射變換的一個基本屬性。仿射變換由三角形的三個點定義。結果,在定義了三角形的三個點及其紋理座標後,變換就沒有更多的自由度了。每個三角形獨立於其他三角形定義其變換,結果是應該是一組穿過正方形的線的彎曲。

然而,投影變換具有額外的自由度,表示為與每個頂點關聯的深度值。這些深度值改變了紋理座標在三角形上插值的方式,並允許對映紋理影象中的直線在螢幕上保持筆直,即使跨越三角形邊界。

幸運的是,這個解決方案相對簡單,但成本很高。正如我們從公式 4 中看到的,

可以使用螢幕空間位置的仿射對映來計算。由於紋理座標本身是仿射對映,我們可以將它們與

仿射對映組合起來,發現

是仿射對映。因此,這三個量(

)可以使用前向差分在三角形上進行插值。在每個片段中,最終 (

) 值可以透過將

取反得到 zv 來計算,然後將其乘以插值的

這種投射式對映的缺點是,它需要對每個片段進行正確的評估。

更新

的仿射前向差分操作

一個仿射前向差分操作來更新

一個仿射前向差分操作更新

恢復透視正確

的除法

5。

乘以

以恢復透視正確的

6。

乘以

以恢復透視正確的

1990 年代的許多 PC 遊戲和一些影片遊戲機使用較便宜(且不太正確)的真實透視紋理近似值。然而,如前所述,在現代硬體光柵化系統上,每個片段的透視矯正紋理被簡單地假設。此外,可程式設計片段著色器基本上可以允許將任何逐頂點屬性用作紋理座標這一事實進一步影響了硬體供應商以正確的視角插入所有頂點屬性。在實踐中,許多 GPU 並未針對所有頂點屬性遵循上述過程。相反,他們使用透視矯正插值計算一組重心座標,然後使用這些重心座標將每個屬性對映到正確的值。

6。3 每個片段的間接值

每個頂點屬性的插值只是每個片段值的一種可能來源。由於現代片段著色器的強大功能,紋理座標和其他值不需要直接來自每個頂點的屬性。作為涉及其他逐頂點屬性的計算的結果,可以從片段著色器本身中生成的一組座標評估紋理查詢。

在片段著色器中生成的紋理座標甚至可以是之前在同一個片段著色器中查詢紋理的結果。在這種技術中,第一個紋理中的紋理影象值不是顏色,而是紋理座標本身。這是一種非常強大的技術,稱為間接紋理。第一個紋理查詢形成一個表查詢或間接查詢,為第二個紋理查詢生成一個新的紋理座標。

間接紋理是更一般的紋理案例的示例,其中評估紋理樣本會生成除顏色之外的“值”。顯然,並非所有紋理查詢都用作顏色。然而,為了在下面的討論中易於理解,我們將假設紋理影象的值代表最常見的情況——顏色。

7 光柵化紋理 Rasterizing Textures

上一節描述瞭如何在片段著色器中插入通用的逐頂點屬性,如果這些屬性是我們所需要的,我們可以簡單地評估或執行片段著色器並計算片段的顏色。但是,如果我們有紋理查詢,這只是第一步。在計算或插值給定片段的紋理座標後,必須將紋理座標對映到紋理影象本身以產生顏色。

一些最早的著色語言要求紋理只能透過每個頂點的屬性來處理,並且在某些情況下,甚至在呼叫片段著色器之前就實際計算了紋理查詢。然而,如上所述,現代著色器允許在片段著色器本身中計算紋理座標,甚至可能作為紋理查詢的結果。此外,著色器中的條件和不同的迴圈迭代可能會導致某些片段的紋理查詢被跳過。因此,我們將紋理的光柵化視為片段著色器本身的一部分。

事實上,雖然在片段著色器內部完成的數學計算很有趣,但孤立片段著色器評估中最(數學)複雜的部分是紋理查詢的計算。正如我們將看到的,紋理查詢不僅僅是抓取並返回最近的紋素到片段中心。將紋理對映到幾何體,然後將幾何體對映到片段的廣泛對映需要一組更大的技術來避免明顯的視覺偽影。

7。1 紋理座標複習

在我們對光柵化紋理的討論中,我們將使用多種不同形式的座標。這包括應用程式級的、標準化的、與紋理無關的紋理座標(u、v),以及與紋理大小相關的紋理座標(

),它們都被認為是實數值。我們在紋理介紹中使用了這些座標。

紋理座標的最終形式是整數紋素座標,或紋素地址。這些代表對紋理影象陣列的直接索引。與其他兩種形式的座標不同,它們(顧名思義)是整數值。從紋素座標到整數紋素座標的對映不是通用的,並且取決於紋理過濾模式,這將在下面討論。

7。2 將座標對映到紋素 Mapping a Coordinate to a Texe

在對紋理進行光柵化時,我們會發現——由於透視投影的性質、幾何物件的形狀以及紋理座標的生成方式——片段很少直接和精確地對應於一對一對映中的紋素。任何支援紋理的光柵化器都需要處理各種紋素到片段的對映。在軟紋理初始討論中,我們注意到紋素座標通常包括精度(透過浮點數或定點數),它比似乎需要的每紋素值更細粒度。正如我們將看到的,在某些情況下,我們將在稱為紋理過濾的過程中使用這種所謂的子畫素精度來提高渲染影象的質量。

紋理過濾(以其多種形式)透過混合紋理畫素座標對映和結果紋理畫素值的組合,來執行從實值紋理畫素座標到最終紋理影象值或顏色的對映。我們將對紋理過濾的討論分為兩種主要情況:一種是單個紋素對映到一個具有多個片段大小(放大)的區域,另一種是多個紋素對映到單個片段(縮小)所覆蓋的區域,因為它們的處理方式完全不同。

7。2。1 放大紋理 Magnifying a Texture

我們最初的紋理討論指出,將這些子紋理精確座標對映到紋理影象顏色的一個常見方法是簡單地選擇包含片段中心點的紋理,並直接使用它的顏色。這種方法稱為最近鄰紋理,計算起來非常簡單。對於任何 (

) texel 座標,整數 texel 座標 (

) 是最近的整數 texel 中心,透過截斷計算:

計算完這個整數紋素座標後,我們只需使用函式 Image(),它將整數紋素座標對映到紋素值,以查詢紋素的值。返回的顏色被傳遞給當前片段的片段著色器。雖然這種方法計算簡單快速,但當紋理以單個紋素覆蓋超過 1 個畫素的方式對映時,它有一個明顯的缺點。在這種情況下,紋理被稱為放大,因為螢幕上的多個片段的四邊形塊完全被紋理中的單個紋理畫素覆蓋,如圖 12 所示。

使用最近鄰紋理,正方形中的所有 (

)texel 座標

將對映到整數紋理座標(iint,jint),從而產生一個恆定的片段著色器值。這是紋素空間中高度和寬度為 1 的正方形,以紋素中心為中心。這會產生明顯的恆定顏色正方形,這往往會引起人們對低解析度影象已對映到表面的事實的注意。請參閱圖 12 以獲取與片段著色器一起使用的最近鄰過濾紋理的示例,該片段著色器直接將紋理作為最終輸出顏色返回。在大多數情況下,這種塊狀結果不是所需的視覺印象。

問題在於最近鄰紋理表示紋理影象作為 (u,v) 的分段常數函式。生成的片段著色器屬性在三角形中的所有片段中保持不變,直到

發生變化。由於 floor 操作在整數值處是不連續的,這會導致由三角形表面上的紋理表示的函式中的尖銳邊緣。

紋素邊界不連續顏色問題的常見解決方案是將紋理影象值視為指定了不同型別的函式。我們不是從離散紋理影象值建立分段常數函式,而是建立

分段平滑顏色函式

。雖然有很多方法可以從一組離散值建立平滑函式,但光柵化硬體中最常見的方法是在二維中每個紋素中心的顏色之間進行

線性插值

。該方法首先計算小於 (

) 的最大紋素中心座標 (

),即紋素座標(即紋素座標的下限減去半紋素偏移量):

換句話說,(uint,vint) 定義了四個相鄰 texel 中心的正方形的最小角(紋理影象空間的左下角),它們“約束(buond)”了 texel 座標(圖 13)。找到這個正方形後,我們還可以計算一個小數紋理座標 0。0≤

<1。0,它定義了 4 紋理正方形內的紋理座標的位置。

我們使用 Image() 來查詢正方形四個角的紋素顏色。為了便於記號,我們為正方形四個角的紋理顏色定義了以下簡寫形式(圖 14):

然後,我們定義了一個平滑的插值的 4 個紋素圍繞紋素座標。我們將平滑對映定義為兩個階段。首先,我們基於分數 u 座標,沿著正方形的最小 v 邊在顏色之間線性插值:

並且類似地沿著最大 v 邊:

最後,我們使用分數 v 座標在這兩個值之間進行線性插值

有關這兩個步驟的圖形表示,請參見圖 15。將這些代入一個單一的直接公式,我們得到

這被稱為雙線性紋理過濾(bilinear texture filtering),因為插值涉及到二維的線性插值,從四個相鄰紋理影象值生成平滑函式。它在硬體 3D 圖形系統中非常流行。我們先沿 u 插值,然後沿 v 插值的事實並不影響結果(除了潛在的精度問題)。快速替換表明,無論哪種方法,結果都是相同的。但是,請注意這不是仿射對映。二維仿射對映是由三個不同的點唯一定義的。我們的雙線性紋理對映的第四個源點可能與其他三個點定義的對映不匹配。

使用雙線性過濾,整個紋理域的顏色是連續的。一個最近鄰過濾和雙線性過濾之間視覺差異的示例如圖 16 所示。雖然雙線性濾波可以透過減少視覺塊狀來極大地提高放大紋理的影象質量,但它不會為紋理新增新的細節。如果將紋理放大很多(即 1 紋素對映到許多畫素),由於缺乏細節,影象看起來會很模糊。圖 16 所示的紋理被高度放大,導致左圖 (a) 中出現明顯的塊狀,右圖 (b) 中出現模糊。

7。2。2 實踐中的紋理放大

IvAPI 使用 IvTexture 函式 SetMagFiltering 來控制紋理放大率。Iv 支援雙線性過濾和最近鄰選擇。它們分別設定如下:

IvTexture* texture;

// 。。。

{

// Nearest-neighbor

texture->SetMagFiltering(kNearestTexMagFilter);

// Bilinear interpolationc

texture->SetMagFiltering(kBilerpTexMagFilter);

// 。。。

7。2。3 紋理縮小

到目前為止,在我們討論光柵化的過程中,我們主要透過中心來指代片段——位於正方形片段中心的無窮小點(現在繼續假設只有完整的片段)。但是,片段具有非零區域。片段區域和代表它的點樣本之間的這種差異在紋理的常見情況下變得非常明顯。

舉個例子,想象一個物體離相機很遠。場景中的物體通常都具有高細節的紋理。這樣做是為了避免模糊(比如我們在圖 16b 中看到的模糊),當一個物體靠近相機時,它應用了一個低解析度的紋理。當相同的物體和紋理被移動到遠處時(這是動態場景中的常見情況),由於物體的視角縮放,同樣的,詳細的紋理將被對映到螢幕上越來越小的區域。這被稱為紋理縮小,因為它是放大的反比。這導致相同的物件和紋理覆蓋的碎片越來越少。

在一個極端(但實際上很常見)的情況下,整個高細節紋理可能是以這樣一種方式對映,它只對映到幾個片段。圖 17 提供了這樣一個例子;在這種情況下,請注意,如果物件稍微移動(甚至小於一個畫素),覆蓋片段中心點的確切紋素可能會發生巨大變化。事實上,這樣的點取樣在紋理中幾乎是隨機的,並且會導致用於片段的紋理的點取樣顏色隨著物件在螢幕上以微小的亞畫素量移動而在幀與幀之間劇烈變化。隨著時間的推移,這可能會導致閃爍,這是動畫渲染影象中令人分心的偽影。

問題在於,紋理中的大多數紋素對片段幾乎都有相同的“要求”,因為它們都投影在片段的矩形區域內。片段紋理樣本的整體顏色應代表其內部的所有紋素。一種思考方法是將投影平面上完整片段的正方形對映到三角形平面上,得到一個(可能是傾斜的)四邊形,如圖 18 所示。為了公平地評估該片段的紋理顏色,我們需要根據每個紋素覆蓋的四邊形的相對面積,計算該四邊形中所有紋素顏色的加權平均值。給定紋素覆蓋的片段越多,該紋素的顏色對片段紋理樣本的最終顏色的貢獻就越大。

雖然精確的面積加權平均方法會給出正確的片段顏色和將避免點取樣出現的問題,實際上這不是最適合實時光柵化的演算法。根據紋理的對映方式,一個片段可以覆蓋幾乎無限數量的紋素。在每個片段的基礎上查詢和求和這些紋素將需要潛在的無限量的每個片段計算,這甚至遠遠超出了硬體光柵化系統的能力。需要一種更快(最好是恆定時間)的方法來逼近這種紋素平均演算法。對於大多數現代圖形系統,一種稱為 mipmapping 的方法可以滿足這些要求。

7。3 Mipmapping

Mipmapping[157] 是一種紋理過濾方法,可避免計算大量紋素的平均值。它透過預先計算和儲存每個紋理的附加資訊來實現這一點,這比標準紋理需要一些額外的記憶體。這是每個紋理樣本的恆定時間操作,並且每個紋理需要固定數量的額外儲存(實際上,它會將必須儲存的紋素數量增加大約三分之一)。Mipmapping 是硬體和軟體光柵化器中流行的過濾演算法,並且在概念上相對簡單。

要理解 mipmapping 背後的基本概念,請想象一個 2×2 紋素的紋理。如果我們看一下整個紋理對映到單個片段的情況,我們可以用 1×1 紋理(單一顏色)替換 2×2 紋理。一種合適的顏色是 2×2 紋理中 4 個紋素的平均值。我們可以直接使用這個新紋理。如果我們在應用程式載入時預先計算 1×1 紋素的紋理,我們可以根據需要簡單地在兩個紋理之間進行選擇(圖 19)。

當給定的片段以這樣一種方式對映,它只覆蓋原始 2 × 2 紋素紋理中的 4 個紋素之一,我們簡單地使用放大方法和原始 2 × 2 紋理來確定顏色。如果片段覆蓋整個紋理,我們將直接使用 1×1 紋理,再次對其應用放大演算法(儘管使用 1×1 紋理,這只是單個紋素顏色)。1×1 紋理充分代表了單個紋素中 2×2 紋理的整體顏色,但它不包括原始 2×2 紋素紋理的細節。這兩個紋理版本都具有另一個版本沒有的有用特性。

Mipmapping 採用這種方法,並將其推廣到任何具有二維冪次的紋理。出於討論的目的,我們假設紋理是正方形的(演算法不需要這樣做,我們稍後

將在實踐中對 mipmapping 的討論中看到)。

生成 mipmap 級別的一種方法是首先取初始紋理影象 Image0(縮寫 I0) 的維數為

=

=

,並透過將四個相鄰紋素的每個方格平均為一個紋素來生成一個新版本的紋理。

這將生成大小為 Image1 的紋理影象

如下:

此時 0 ≤ i, j <

。Image1 中的每個紋素都代表了 Image0 中對應的 4 個紋素塊的整體顏色(圖 20)。請注意,如果我們對兩個版本的紋理使用相同的原始紋理座標,則影象 1 只是顯示為影象 0 的模糊版本(具有影象 0 的一半細節)。如果影象 0 中大約四個相鄰紋素的塊覆蓋了一個片段,那麼我們可以在紋理時簡單地使用影象 1。但是更極端的縮小情況呢?該演算法可以遞迴地繼續。對於每個尺寸大於 1 的影象 Imagei,我們可以定義 Imagei+1,其尺寸是 Imagei 的一半,並將 Imagei 的平均紋素定義為 Imagei+1。這會生成一整套原始紋理的 L+1 個版本,其中 Imagei 的尺寸等於

這形成了一個影象金字塔,每一個都是金字塔中前一個影象的一半尺寸(幷包含四分之一的紋素)。圖 21 提供了這樣一個金字塔的例子。我們在載入時為場景中的每個紋理計算這個金字塔一次或作為離線預處理,並將每個整個金字塔儲存在記憶體中。

這種計算 mipmap 影象的簡單方法稱為盒子過濾(正如我們將一個 2×2“盒子”的紋素平均為一個紋素)。盒子過濾不會產生非常高質量的 mipmap,因為它往往會使影象過於模糊,同時仍會產生偽影。其他更復雜的方法更常用於將每個 mipmap 級別過濾到下一個較低級別。一個很好的例子是 Lanczos 過濾器。參見 Turkowski[148] 或 Wohlberg[159] 瞭解其他影象過濾方法的詳細資訊。在生成每個級別時還必須小心,以確保對線性顏色進行計算;如果原始紋理顏色為 sRGB,則轉換為線性,進行任何計算,然後轉換回 sRGB 以儲存 mipmap 值。

7。3。1 使用 Mipmap 對片段進行紋理處理

使用 mipmap 對片段進行紋理化的最簡單、通用的演算法可以總結如下:

透過確定片段角落處的紋理座標,確定螢幕空間中的片段映射回紋理空間中的四邊形。

將片段正方形對映到紋理空間中的四邊形後,選擇最接近將四邊形精確對映到單個紋素的任何 mipmap 級別。

使用所需的放大演算法,使用在上一步中選擇的“最佳匹配”mipmap 級別對片段進行紋理處理。

確定最佳匹配 mipmap 級別的常用方法有很多,並且有多種方法可以將此 mipmap 級別過濾為最終的片段紋理值。我們希望避免必須將片段的角顯式映射回紋理空間,因為這計算起來很昂貴。我們可以利用其他光柵化階段已經計算的資訊。正如我們在 5。1 和 6。2 節中看到的,在光柵化中通常計算給定片段中心的片段著色器輸入值(例如,紋理座標)與給定片段右側和下方片段的值之間的差異,以用於前向差分。我們沒有明確表示,這些差異可以表示為導數。下面的清單旨在為這四個偏導數中的每一個分配直觀的值。對於不熟悉∂的人來說,它是偏導數的符號,是多元微積分的基本概念。∂運算子表示當您更改輸入分量之一時,向量值函式的輸出的一個分量會發生多少變化。

如果一個片段對映到大約 1 個 texel,那麼

換句話說,即使紋理被旋轉,如果片段與對映到它的紋素大小大致相同,那麼單個片段上紋理座標的整體變化長度約為 1 紋素。請注意,所有這四個差異都是獨立的。這些部分取決於 utexel 和 vtexel,而這又取決於紋理大小。事實上,對於這些差異中的每一個,從 Image i 移動 Imagei+1 都會導致差異減半。正如我們將看到的,在計算 mipmapping 值時,這是一個有用的屬性。

Heckbert[74] 中描述了一個用於將這些差異轉化為畫素‑紋素大小比度量的通用公式,該公式定義了一個映射回紋理空間的畫素半徑的公式。請注意,這實際上是兩個半徑中的最大值,utexel 中的畫素半徑和 vtexel 中的畫素半徑:

我們可以看到(透過替換∂)每次我們從 Imagei 移動到 Imagei+1 時,這個值都會減半(因為所有∂值都會減半)。因此,為了找到將 1 個紋素對映到完整片段的 mipmap 級別,我們必須計算 L 使得

其中 size 是使用 Image0 的紋素座標計算的。求解 L,

L 的這個值就是我們應該使用的 mipmap 級別的索引。請注意,如果我們插入對應於精確的一對一紋理到螢幕對映的部分,我們得到 size=1,這導致 L

我們得到 size=1,這導致 L=0,這與預期的原始紋理影象相對應。這為我們提供了一種封閉形式的方法,可以將現有的部分(用於在掃描線上插入紋

理座標)轉換為特定的 mipmap 級別 L。最後的公式是

請注意,L 的值是實數,而不是整數(稍後我們將討論將此值對映到離散 mipmap 金字塔的方法)。前面的功能只有一種可能用於計算 mipmap 級別 L 的選項。圖形系統使用這個值的許多簡化和近似值(它本身就是一個近似值)甚至其他函式來確定正確的 mipmap 級別。事實上,某些硬體裝置使用的 L 的特定近似值是如此不同,以至於一些有經驗的 3D 硬體使用者實際上可以透過檢視渲染的 mipmap 影象來識別特定的顯示硬體。其他 3D 硬體允許開發人員(甚至終端使用者)對使用的 L 值進行偏差,因為一些使用者更喜歡“清晰”的影象(將 L 偏向負方向,選擇更大、更詳細的 mipmap 級別和每個片段),而其他人更喜歡“平滑”影象(將 L 偏向正方向,趨向於不太詳細的 mipmap 級別和每個片段的紋素更少)。有關 mipmap 級別選擇的一種情況的詳細推導,請參閱 Eberly[35] 的第 106 頁。

用於降低 mipmap 的每個片段開銷的另一種方法是選擇一個 L 值,因此在每幀中每個三角形的都有單個 mipmap 級別,並使用該 mipmap 級別光柵化整個三角形。雖然這種方法不需要對 L 進行任何每個片段的計算,但它可能會導致嚴重的視覺偽影,尤其是在三角形的邊緣,其中 mipmap 級別可能會急劇變化。支援 mipmapping 的軟體光柵化器經常使用這種方法,稱為逐三角形 mipmapping (

per‑triangle mipmapping

)。

請注意,就其本質而言,mipmapping 傾向於在遠處的物體上使用較小的紋理。這意味著 mipmapping 實際上可以提高軟體光柵化器的效能,因為較小的 mipmap 級別比完整細節紋理更可能適合處理器的快取。在大多數 GPU 上也是如此,因為小型片上紋理快取儲存器 (small, on-chip texture cache memories) 用於儲存最近訪問的紋理影象區域。由於 GPU 和軟體光柵化器在某種程度上受到讀取紋理的記憶體頻寬的限制,因此將紋理保留在快取中可以顯著降低這些頻寬需求。此外,如果點取樣與未對映的紋理一起使用,則相鄰畫素可能需要讀取紋理中相距較遠的部分。透過紋理的這些大的每畫素步幅可能會導致可怕的快取行為,並可能嚴重阻礙非 mipmapped 光柵化器的效能。這些由快取未命中引起的處理器管道“停止(stalls)”或等待使得計算 mipmapping 資訊的成本(至少在每個三角形的基礎上)是值得的,與視覺質量的顯著提高無關。

7。3。2 紋理過濾和 Mipmap Texture Filtering and Mipmaps

上述方法的工作原理是,給定片段將有一個單一的“最佳”mipmap 級別。然而,由於每個 mipmap 級別是每個維度中下一個 mipmap 級別大小的兩倍,因此最接近的 mipmap 級別可能不是精確的片段到紋理對映。線性 mipmap 過濾不是選擇給定的 mipmap 級別作為最佳,而是使用類似於(雙)線性紋理過濾的方法。基本上,mipmap 過濾使用實值 L 來查詢限制給定片段與紋素比率的相鄰 mipmap 級別對和

(地板和天花板符號)。剩餘的小數部分 (L 到

) 用於在兩個 mipmap 級別中找到的紋理顏色之間進行混合。

放在一起,現在有兩個獨立的過濾軸,每個軸都有兩種可能的過濾模式,導致四種可能的 mipmap 過濾模式,如表 1 所示。在這些方法中,最流行的是線性雙線性,也稱為三線性插值濾波或 trilerp,因為它是雙線性插值的精確 3D 模擬。它是這些 mipmap 過濾操作中最昂貴的,需要每個片段查詢 8 個紋素,以及 7 個線性插值(兩個 mipmap 級別中的每一個級別三個,另外一個用於在級別之間進行插值),但它也產生了最平滑的結果。在 mipmap 級別之間進行過濾也會增加使用的紋理記憶體頻寬量,因為每個樣本都必須訪問兩個 mipmap 級別。因此,多級 mipmap 過濾通常會抵消前面提到的 mipmap 在硬體圖形裝置上的效能優勢。

最後一種更新形式的 mipmap 過濾稱為各向異性過濾。到目前為止討論的 mipmap 過濾方法隱含地假設畫素在對映到紋理空間時會產生一個與某個圓非常接近的四邊形——換句話說,紋理空間中的四邊形基本上是正方形的情況。在實踐中,通常情況並非如此。對於極端透視下的多邊形,一個完整的片段通常會對映到紋理空間中一個很長、很薄的四邊形。標準的各向同性過濾模式可能看起來太模糊(根據四邊形的長軸選擇了 mipmap 級別)或太尖銳(根據四邊形的短軸選擇了 mipmap 級別)。各向異性紋理過濾在對 mipmap 進行取樣時會考慮紋理空間四邊形的縱橫比,並且能夠過濾 mipmap 中的非正方形區域以生成準確表示傾斜多邊形紋理的結果。

7。3。3 實踐中的 Mipmapping

的預設 CreateTexture 介面僅分配基本級別的紋理資料,而不分配其他 mipmap 級別。要使用 mipmaps 建立紋理,我們使用 CreateMipmappedTexture,如下所示:

IvResourceManager* manager;

// image data

const int numLevels = 5;

void* data[numLevels];

// 。。。

{

IvTexture* texture = manager->CreateMipmappedTexture(kRGBA32TexFmt,

width, height,

data, numLevels, kImmutableUsage);

}

請注意,我們現在傳入了一個影象資料陣列——每個陣列條目都是一個 mipmap 級別。我們還必須指定級別數。如果使用動態或預設使用建立 mipmapped 紋理,我們還可以使用 IvTexture 函式 BeginLoadData 和 EndLoadData。但是,在 mipmap 的情況下,我們使用這些函式的引數 unsignedintlevel(以前預設為 0),它指定 mipmap 級別。最高解析度影象的 mipmap 級別為 0。每個後續級別編號(1、2、3、。。。)表示 mipmap 金字塔影象,其尺寸為前一級別的一半。一些 API 要求指定一個“完整的”金字塔(一直到 1×1 紋素)才能使 mipmapping 正常工作。在實踐中,為所有 mipmap 紋理提供完整的金字塔是一個好主意。完整金字塔中的 mipmap 層數等於

請注意,mipmap 級別的數量基於紋理的較大尺寸。一旦一個維度下降到 1 紋素,它就會保持在 1 紋素,而更大的維度繼續減小。因此,對於 32×8‑紋素紋理,mipmap 級別如表 2 所示。

請注意,陣列中提供的 mipmap 級別影象的紋素傳遞給 CreateMipmappedTexture 或 BeginLoadData 返回的陣列中的設定必須由應用程式計算。Iv 只是接受這些影象作為 mipmap 級別並直接使用它們。一旦指定了紋理的所有 mipmap 級別,就可以透過將紋理取樣器附加為著色器統一變數,將紋理用於 mipmap 渲染。指定整個金字塔的示例如下:

IvTexture* texture;

// 。。。

{

for (unsigned int level = 0; level < texture->GetLevels(); level++) {

unsigned int width = texture->GetWidth(level);

unsigned int height = texture->GetHeight(level);

IvTexColorRGBA* texels

= (IvTexColorRGBA*)texture->BeginLoadData(level);

for (unsigned int y = 0; y < height; y++) {

for (unsigned int x = 0; x < width; x++) {

IvTexColorRGBA& texel = texels[x+y* width];

// Set the texel color, based on

// filtering the previous level。。。

}

}

texture->EndLoadData(level);

}

為了設定縮小過濾器,使用了 IvTexture 函式 SetMinFiltering。Iv 支援非 mipmapped 模式(雙線性過濾和最近鄰選擇)和所有四種 mipmapped 模式。質量最好的 mipmapped 模式(如前所述)是三線性過濾,使用

IvTexture* texture;

// 。。。

texture->SetMinFiltering(kBilerpMipmapLerpTexMinFilter);

// 。。。

8 從片段到畫素 From Fragments to Pixels

到目前為止,本章已經討論了生成片段,計算片段著色器的每個片段源值,以及評估片段著色器(紋理查詢)的更復雜方面的一些細節。然而,本章的前幾節概述了所有這些每片段工作的真正目標:在場景的渲染檢視中生成畫素的最終顏色。回想一下,畫素是構成矩形網格螢幕(或幀緩衝區)的目標值。畫素是“箱”,我們在其中放置對該畫素區域有影響的的表面碎片。片段代表這些畫素大小的表面碎片。最後,我們必須將所有落入給定畫素的箱子中的片段轉換為該畫素的單一顏色和深度。到目前為止,我們在本章中做了兩個重要的簡化假設:

所有片段都是不透明的;也就是說,近處的片段會掩蓋更遠的片段。

所有片段都是完整的;也就是說,一個片段覆蓋了整個畫素。

綜上所述,這兩個假設導致了一個重要的整體簡化:給定畫素處最近的片段完全決定了該畫素的顏色。在這樣的系統中,我們需要做的就是在一個畫素處找到最近的片段,對該片段進行著色,然後將結果寫入幀緩衝區。在討論可見表面確定和紋理時,這是一個有用的簡化假設。但是,它限制了表示某些常見型別的表面材料的能力。它還可能導致螢幕上物件邊緣出現鋸齒狀的視覺偽影。因此,現代圖形系統中的兩個附加功能消除了這些簡化假設:畫素混合允許片段部分透明,抗鋸齒處理包含多個部分片段的畫素。我們將透過對兩者討論來結束本章。

8。1 畫素混合 Pixel Blending

畫素混合是一個以片段為單位的非幾何函式,它的輸入是當前片段的著色顏色(我們稱之為 Csrc),片段的 alpha 值(它是片段顏色的一個適當的組成部分,但為了方便,我們將其稱為 Asrc),幀緩衝區中畫素的當前顏色(Cdst),以及有時幀緩衝區中該畫素的現有 alpha 值(Adst)。 這些輸入,加上一對混合函式 Fsrc 和 Fdst,定義了將被寫入幀緩衝器中的畫素的結果顏色(以及可能的 alpha 值),即 CP。請注意,CP 一旦寫入,在以後涉及同一畫素的混合操作中就會變成 Cdst。 混合的一般形式是

其中⊕可以表示+、−、min() 或 max()。我們還可以有第二對隻影響 A 的函式。然而,在遊戲中的大多數情況下,我們使用上面的公式並將⊕設定為+。

源和目標的 alpha 值通常被解釋為不透明度(我們將在下面討論 alpha 混合時瞭解原因)。但是,alpha 也可以解釋為顏色對畫素的部分覆蓋——在這種情況下,alpha 是顏色覆蓋的畫素的百分比。一般來說,這種解釋在遊戲中很少使用,除非可能在介面(遊戲面板)中。它在使用畫素混合進行 2D 合成或影象分層(也稱為 alpha 合成)時更常使用。我們將在 8。2 節中更詳細地討論畫素覆蓋。有關 alpha 作為覆蓋率的更多資訊,請參閱 [123]。

畫素混合的最簡單形式是完全禁用混合(“源替換”模式),其中片段替換現有畫素。這相當於

畫素混合通常以其最常見的特殊情況的名稱來指代:alpha 混合。Alpha 混合涉及使用源 Alpha 值 Asrc 作為新片段的不透明度,以在 Csrc 和 Cdst 之間進行線性插值:

Alpha 混合需要 Cdst 作為運算元。因為 Cdst 是畫素顏色(通常儲存在幀緩衝區中),所以 alpha 混合可以(取決於硬體)要求從幀緩衝區中讀取每個混合片段的畫素顏色。這種增加的記憶體頻寬意味著 alpha 混合會影響某些系統的效能(以類似於深度緩衝的方式)。此外,阿爾法混合還有其他幾個特性,使其在實踐中的使用有些挑戰。

Alpha 混合旨在計算新的畫素顏色,基於新的片段顏色表示可能半透明的表面,其不透明度由 Asrc 給出。

Alpha 混合僅使用片段 Alpha 值,而不是目標畫素的 Alpha 值。假設現有畫素顏色代表比當前片段更遠的畫素處的整個現有場景,半透明片段放置在該畫素的前面。對於下面的討論,我們將 alpha 混合表示為

多個 alpha 混合操作的結果取決於順序。每個 alpha 混合操作都假定 Cdst 表示比新片段更遠的所有物件的最終顏色。如果我們將兩個可能半透明的片段 (C1,A1) 和 (C2,A2) 混合到背景顏色 C0 上作為兩個混合的序列,我們可以很快看到,一般來說,改變順序混合改變結果。例如,如果我們比較兩個可能的混合順序,設定 A1=1。0,並展開函式,我們得到

這兩個方面幾乎從來都不是平等的。兩種混合順序通常會產生不同的結果。在大多數情況下,兩個表面與背景顏色的 alpha 混合取決於順序。

8。1。1 畫素混合和深度緩衝 Pixel Blending and Depth Buffering

在實踐中,alpha 混合的這種順序依賴性使深度緩衝複雜化。深度緩衝區基於這樣的假設,即給定深度的片段將完全遮蓋任何深度更大的片段,這僅適用於不透明物件。在存在 alpha 混合的情況下,我們必須以非常特定的順序計算畫素顏色。我們可以對所有三角形進行深度排序,但如上所述,這很昂貴,並且對許多資料集存在嚴重的正確性問題。相反,一種選擇是假設對於大多數場景,半透明三角形的數量遠小於不透明三角形的數量。給定一組三角形,嘗試正確計算混合畫素顏色的一種方法如下:

將場景中的不透明三角形收集到一個列表 O 中。

將場景中的半透明三角形收集到另一個列表 T 中。

使用深度緩衝正常渲染 O 中的三角形。

將 T 中的三角形按深度排序為從遠到近的順序。

使用深度緩衝,透過混合渲染排序列表 T。

這似乎可以解決問題。但是,在大多數情況下,每個三角形的深度排序仍然是一項昂貴的操作,必須在主機 CPU 上完成。此外,每個三角形排序無法解決所有差異,因為存在無法正確排序的三角形的常見配置。已經提出了其他方法來避免這兩個問題。一種這樣的方法是在每個物件級別進行深度排序以避免粗略的無序混合,然後使用更復雜的方法,例如深度剝離 [44],它使用高階可程式設計著色和物件的多次渲染來“剝離”更近的表面(使用深度緩衝區)並生成深度排序的顏色。雖然相當複雜,但該方法完全在 GPU 上執行,並專注於讓最接近的圖層正確,根據理論是越來越深的透明度獲得遞減的回報(因為它們對最終顏色的貢獻越來越少)。

在某些特定應用的情況下,可以避免畫素混合三角形的深度排序或深度剝離。另外兩種常見的畫素混合模式是可交換的,因此是順序無關的。這兩種混合模式稱為 add 和 modulate。add 混合產生“發光”物體的效果,定義如下:

modulate 混合實現顏色過濾。它被定義為

請注意,這些效果都不涉及源顏色或目標顏色的 alpha 分量。add 和 modulate 混合模式仍然需要先繪製不透明物件,然後是混合物件,但都不需要將混合物件分類為深度排序。因此,這些混合模式在粒子系統效果中非常流行,其中使用了數千個微小的混合三角形來模擬後期的煙霧、蒸汽、灰塵或水。其他更復雜的與順序無關的透明度解決方案是可能的;一個例子見 [107]。

請注意,如果深度緩衝用於未排序的混合物件,則必須在禁用深度緩衝區寫入的情況下繪製混合物件,否則兩個混合物件的任何無序(從前到後)渲染將導致更多遠處的物體沒有被繪製。從某種意義上說,混合物件不存在於深度緩衝區中,因為它們不會遮擋其他物件。

8。1。2 預乘 Alpha Premultiplied Alpha

在上面的討論中,我們假設我們的顏色與直接的 RGB 值和關聯的 alpha 值一起儲存。RGB 值代表我們的基色,alpha 代表它的透明度或覆蓋率;例如,(1,0,0,12) 表示半透明紅色。然而,更好的混合格式是我們將基本 RGB 值乘以 alpha 值 A,或者

這稱為預乘 alpha,現在我們的 RGB 值代表對最終結果的貢獻。這裡的假設是我們的 alpha 值位於 [0,1] 範圍內。如果為每個通道使用 8 位值,則需要在乘法後除以 255。在使用 sRGB 時,請務必將線性到 sRGB 的轉換應用於預乘顏色——不要將其應用於基色,然後乘以 alpha。使用這個公式,阿爾法混合變成

src 是 AsrcCsrc 這似乎並沒有給我們帶來太多好處,除了可能節省了一個乘法。但預乘 alpha 具有許多顯著優勢。首先,考慮我們使用帶有雙線性取樣的紋理的情況。假設我們在透明 (A=0) 綠色紋素旁邊有一個純色 (A=1) 紅色紋素。使用標準顏色,如果我們在它們之間進行中間處理,我們得到

儘管我們從純紅色插值到完全透明的顏色,但不知何故我們最終得到了黃色的半透明顏色。如果我們改用預乘 alpha,透明色的顏色值全部變為 0,所以我們有

這是我們想要的半透明紅色的預乘 alpha 版本。

即使我們不使用完全透明的顏色,我們仍然會得到奇怪的結果,比如說

這又比我們預期的要黃得多。使用預乘的 alpha 顏色,我們得到:

並適當降低了第二種顏色的貢獻。因此,預乘 alpha 的第一個也是最重要的優點是它可以從紋理取樣中得到正確的結果。

第二個優點是它允許我們將簡單的混合方程擴充套件到更大的混合操作集,稱為 Porter‑Duff 混合模式 [123]。這些不常用於渲染 3D 世界,但它們在混合 2D 元素中非常常見,因此瞭解如何為遊戲內 UI 複製這些效果對於某些效果很有用。一個例子是“Src In”運算子,它將目的地的任何貢獻替換為源的比例分數,或者:

這最終成為:

這是標準混合模式和純色無法實現的。

第三個優點允許我們建立顏色值大於 1 的透明值,這對於建立照明效果很有用。例如,我們可以使用 (1,1,1,12) 的預乘 alpha 顏色,它具有 (2,2,2,12) 的直接顏色等價物。最終結果將對場景產生兩倍的貢獻,從而產生髮射效果。Forsyth[49] 在粒子效果中提供了一個很好的用途。通常我們希望粒子以新增劑(即火花)開始,然後變成 alpha 混合(菸灰和煙霧)。透過使用預乘 Alpha,我們可以建立一個具有子區域的紋理,該子區域代表不同粒子型別的顏色,並將其與單一混合模式一起使用。火花粒子可以使用具有非零 RGB 值或 (R,G,B,0) 的零 alpha 顏色。煙霧粒子可以使用標準的預乘 alpha 顏色,或 (RA,GA,BA,A)。透過將這些與預乘 alpha 混合方程一起使用,火花區域被新增到場景中,煙霧區域將被 alpha 混合。對於單個粒子的生命週期,我們需要做的就是將其紋理座標從紋理的不同區域對映到地圖,其可見表示將從火花慢慢變為煙霧。並且所有粒子都將與單個紋理和單個繪製呼叫正確合成,而無需在 add 和 alpha 混合模式之間切換。

最後,當使用純色時,混合圖層不是關聯的,因此為了獲得混合圖層 A‑D 的正確結果,您必須將 A 與 B 混合,然後將結果與 C 混合,然後將結果與 D 混合。但是,有有時您可能想先混合 B 和 C,例如某種基於螢幕的後處理效果。預乘 alpha 允許您這樣做並稍後新增貢獻 A 和 D。再次注意,為混合操作設定的順序必須相同——您不能將 C 與 B 混合並期望得到與將 B 與 C 混合的相同結果。

預乘 Alpha 的唯一缺點是,當使用 8 位或更小的顏色通道時,最終會失去精度。如果您計劃在某些可以放大它們的操作中僅使用 RGB 顏色,這只是一個問題。否則,為了獲得最佳結果,強烈建議使用預乘 alpha。

8。1。3 在實踐中混合

儘管在 Iv 支援的模式之外還有許多選項,但在大多數圖形系統中都可以非常簡單地啟用和控制混合。透過 IvRenderer 函式 SetBlendFunc 設定混合模式,該函式在單個函式呼叫中設定 Fsrc、Fdst 和運算子⊕。要使用經典的 alpha 混合(沒有預乘 alpha),函式呼叫是

renderer->SetBlendFunc(kSrcAlphaBlendFunc, kOneMinusSrcAlphaBlendFunc,

kAddBlendOp);

使用呼叫設定 Add 模式

renderer->SetBlendFunc(kOneBlendFunc, kOneBlendFunc, kAddBlendOp);

Modulate 混合可以透過呼叫使用

renderer->SetBlendFunc(kZeroBlendFunc, kSrcColorBlendFunc, kAddBlendOp);

還有更多的混合功能和操作可用;有關更多詳細資訊,請參閱原始碼。

回想一下,在渲染混合物件時禁用 z 緩衝區寫入通常很有用。這是透過深度緩衝遮蔽實現的,前面在深度緩衝部分中進行了描述。

8。2 抗鋸齒 Antialiasing

我們之前提出的另一個簡化光柵化假設,即部分片段被忽略或“提升”為完整片段的想法,引發了它自己的一系列問題。將所有片段轉換為全有或全無情況的想法是允許我們假設單個片段將“贏得”一個畫素並確定其顏色。我們使用這個假設將每個片段的計算減少到單點樣本。

如果我們將畫素視為純點樣本,沒有區域,這是合理的。然而,在我們對片段的初步討論和對 mipmapped 紋理的詳細討論中,我們發現情況並非如此;每個畫素代表螢幕上一個具有非零面積的矩形區域。因此,在畫素的矩形區域內可能會看到多個(部分)片段。圖 22 提供了這種多片段畫素的示例。

使用討論的點取樣方法,我們將選擇單個片段的顏色來表示畫素的整個區域。但是,如圖 23 所示,這個畫素中心點樣本可能無法代表整個畫素的顏色。在圖中,我們看到畫素的大部分割槽域是深灰色的,只有中心的一個很小的正方形是亮白色的。結果,選擇亮白色的畫素顏色並不能準確地表示整個畫素矩形的顏色。我們對矩形顏色的感知與矩形中每種顏色的相對面積有關,這是單點取樣方法無法表示的。

圖 24 使這一點更加明顯。在這種情況下,我們看到兩個怪異畫素的例子(每個 9 畫素 3×3 網格中的中心畫素)。在兩種中心畫素配置中(圖左側的頂部和底部),絕大多數表面區域都是深灰色。在這兩種情況下,中心畫素都包含一個小的白色片段。在這兩種情況下,白色片段的大小相同,但在兩種情況下,它們相對於中心畫素的位置略有不同。在第一個(頂部)示例中,白色片段恰好包含畫素中心,而在底部示例中,白色片段不包含畫素中心。右列顯示在每種情況下將分配給中心畫素的顏色。儘管它們的幾何配置幾乎相同,但為這兩個畫素分配了非常不同的顏色。這證明了一個事實,即對畫素顏色進行單點取樣可能會導致一些隨意的結果。事實上,如果我們想象白色碎片隨著時間的推移在螢幕上移動,當白色碎片穿過每個畫素的中心時,整行畫素會在白色和灰色之間閃爍。

可以為圖中的 2 個畫素確定更準確的顏色。如果圖形系統使用畫素矩形內每個片段的相對面積來加權畫素的顏色,結果會好得多。在圖 25 中,我們可以看到白色片段覆蓋了大約 10%的畫素區域,留下了其餘 90%為深灰色。透過相對區域加權顏色,我們得到一個畫素顏色

請注意,此計算與白色片段在畫素內的位置無關;只有碎片的大小和顏色很重要。這種基於區域的方法避免了我們看到的點取樣錯誤。該系統可以擴充套件到給定畫素內的任意數量的不同顏色的片段。給定一個面積為 a 的畫素和一組 n 個不相交的片段,每個片段在畫素內都有一個面積 ai 和顏色 Ci,則該畫素的最終顏色為

其中 Fi 是給定片段覆蓋的畫素的分數,或片段的覆蓋範圍。這種方法稱為區域抽樣。事實上,這確實是一個更一般的定積分的特例。如果我們想象我們有一個螢幕空間函式,它表示螢幕上每個位置的顏色(與畫素或畫素中心無關)C(x,y),那麼畫素的顏色定義為區域 l≤x≤r,t≤y≤b(畫素的左、右、上、下螢幕座標),使用這種區域取樣方法,等價於

這是像素面積上顏色的積分,除以畫素的總面積。公式 5 的求和版本是這個更一般的積分的簡化,假設畫素完全由分段恆定顏色的區域組成,即覆蓋畫素的片段。

作為對該方法的驗證,我們將假設畫素完全被一個顏色為 C(x,y)=CT 的完整片段覆蓋,給出下式,這是我們在這種情況下所期望的顏色。

雖然區域取樣確實避免了完全丟失或過分強調任何單個樣本,但它不是唯一使用的方法,也不是代表顯示裝置現實的最佳方法(物理畫素的強度實際上在畫素矩形內可能不是恆定的))。公式 5 中顯示的區域取樣隱含地對畫素的所有區域進行平均加權,使畫素中心的權重等於邊緣的權重。因此,它通常被稱為未加權區域取樣。另一方面,加權區域取樣添加了一個加權函式,可以根據需要對畫素的任何區域中顏色的重要性進行偏向。如果我們簡化等式 5 原始畫素邊界和相關函式,使得畫素的邊界為 0≤x,y≤1,則等式 5 變為

將等式 5 簡化為等式 7,我們定義了一個加權函式 W(x,y),它允許根據需要對畫素區域進行加權:

在這種情況下,分母被設計為根據加權面積進行歸一化。對等式 6 的類似替換表明,畫素上的恆定顏色對映到給定顏色。還要注意(與未加權區域取樣不同)畫素內圖元的位置現在很重要。從公式 8 可以看出,未加權區域抽樣只是加權區域抽樣的一種特殊情況。對於未加權的區域取樣,W(x,y)=1,給出:

Hughes 等人對加權區域取樣、其背後的理論以及許多常見的加權函式進行了全面討論。[82]。對於那些希望獲得更多深度的人,Glassner[54] 和 Wohlberg[159] 詳細介紹了廣泛的取樣理論。

8。2。1 超取樣抗鋸齒 Supersampled Antialiasing

到目前為止討論的方法顯示了計算基於區域的畫素顏色的理論方法。這些方法要求每個片段計算畫素覆蓋值。計算三角形的分析(精確)畫素覆蓋值可能既複雜又昂貴。在實踐中,純基於區域的方法不會直接導致簡單、快速的硬體抗鋸齒實現。

概念上最簡單、最流行的抗鋸齒方法稱為過取樣、超級取樣或超取樣抗鋸齒 (SSAA)。在 SSAA 中,基於區域的取樣透過在每個畫素多個點對場景進行點取樣來近似。在 SSAA 中,片段不是在每個畫素級別生成,而是在每個樣本級別生成。從某種意義上說,SSAA 在概念上只不過是將整個場景渲染為更大的(更高解析度)幀緩衝區,然後將高解析度幀緩衝區中的畫素塊過濾到最終幀緩衝區的解析度。例如,超取樣幀緩衝區的寬度和高度可能是螢幕上最終目標幀緩衝區的 N 倍。在這種情況下,超取樣幀緩衝區中的每個 N×N 畫素塊將被過濾為螢幕幀緩衝區中的單個畫素。

超級樣本透過加權(或在某些案例未加權)平均值。這些取樣模式的加權區域版本使用的位置和權重因製造商而異;示例位置的常見示例如圖 26 所示。請注意,每個畫素的超樣本數量從少至 2 到多至 16 不等。M 樣本 SSAA 將畫素表示為 M 元素分段常數函式。部分片段將僅覆蓋畫素中的一些點樣本,因此在結果畫素中的權重會降低。

一些 N×N 樣本網格也有旋轉版本。這樣做的原因是水平和垂直線的出現頻率很高,並且也與畫素佈局本身相關。透過以正確的角度旋轉樣本,所有 N2 個樣本都位於不同的水平和垂直位置。因此,透過畫素從左到右或從上到下緩慢移動的水平或垂直邊緣將分別與每個樣本相交,因此將具有以 1/

增量變化的覆蓋率值。使用螢幕對齊的 N×N 樣本模式,相同的移動水平和垂直邊緣會同時與整行或整列樣本相交,從而導致覆蓋值以 1/N 的增量變化。旋轉模式可以更好地利用可用樣本的數量。

M-sample SSAA 每個畫素生成 M 倍(如前所述,一般為 2-16 倍)的片段。每個這樣的(較小的)片段都有自己的顏色,透過評估每個頂點的屬性、紋理值和片段著色器本身,每幀的頻率比正常渲染高 M 倍。每個樣本的完整渲染管道非常強大,因為每個樣本都真實地代表了該樣本的幾何顏色。它也非常昂貴,需要每個樣本呼叫整個光柵化管道,從而將光柵化開銷增加 2‑16 倍。即使是功能強大的 3D 硬體系統,這也太昂貴了。

8。2。2 多重取樣抗鋸齒 Multisampled Antialiasing

超級取樣抗鋸齒最昂貴的方面是為每個樣本建立單獨的片段以及每個樣本產生的紋理和片段著色。另一種抗鋸齒形式認識到,3D 渲染中鋸齒最可能的原因是物件邊緣的部分片段,其中畫素將包含來自不同物件的多個部分片段,通常具有非常不同的顏色。多重取樣抗鋸齒 (MSAA) 試圖在不像 SSAA 那樣提高渲染成本的情況下解決這個問題。MSAA 的工作方式與正常渲染類似,因為它以最終畫素大小生成片段(包括部分片段)。它

只對每個片段評估一次片段著色器

,因此與 SSAA 相比,片段著色器呼叫的數量顯著減少。

MSAA 新增的資訊是每個樣本的片段覆蓋率。當一個片段被渲染時,它的顏色會被評估一次,然後為片段覆蓋的每個可見樣本儲存相同的顏色。樣本中的現有顏色(來自較早的片段)可能會被新片段的顏色替換。但這是在每個樣本級別完成的。在幀結束時,仍然需要“解析”來從多個樣本中計算畫素的最終顏色。然而,每個樣本、每個片段僅計算一個覆蓋值(一個簡單的幾何運算)和可能的一個深度值。計算片段顏色的昂貴步驟仍然為每個片段完成一次。與 SSAA 相比,這大大降低了 MSAA 的費用。

MSAA 有兩個微妙之處值得一提。首先,由於 MSAA 是

基於覆蓋

的,因此不會對完整片段計算抗鋸齒。渲染完整的片段,就好像沒有使用抗鋸齒一樣。另一方面,SSAA 透過在每個畫素中多次呼叫片段的著色器來消除每個畫素的鋸齒。一個關鍵的觀察結果是,在單取樣完整片段中最有可能導致混疊(aliasing)的專案可能是紋理(因為它是片段中的最高頻率值)。紋理已經應用了一種抗鋸齒形式:mipmapping。因此,在大多數情況下,這對 MSAA 來說不是問題。

另一個問題是選擇畫素中的位置來評估部分片段上的著色器的問題。通常,我們在畫素中心評估片段著色器。但是,部分片段甚至可能無法覆蓋畫素中心。如果我們在畫素中心對片段著色器進行取樣,我們實際上會將頂點屬性外推到預期值之外。這在紋理中尤其明顯,因為我們將在三角形中可能未對映的位置讀取紋理。這會導致明顯的視覺偽影。大多數 3DMSAA 硬體中的解決方案是選擇片段覆蓋的樣本的重心。由於片段是凸的,因此重心將始終落在片段內部。這確實給系統增加了一些複雜性,但是不包括畫素中心的片段的可能配置數量是有限的。凸性和中心樣本未被觸及的事實意味著可能存在一組非常有限的覆蓋樣本配置。甚至可以在構建硬體之前預先計算出一組可能的位置。但是,重心取樣必須按逐個屬性請求。否則,硬體將預設使用畫素中心取樣。

8。2。3 實踐中的抗鋸齒

對於大多數渲染 API,使用 MSAA 最重要的一步是建立與該技術相容的渲染幀緩衝區。深度緩衝需要幀緩衝旁邊的附加緩衝區來儲存深度值,而 MSAA 需要特殊的幀緩衝格式,其中包括每個畫素內每個樣本的附加顏色、深度和覆蓋值。不同的渲染 API 甚至相同 API 上的不同渲染硬體通常有不同的方法來顯式請求 MSAA 相容的幀緩衝區。一些呈現 API 允許應用程式以畫素格式指定樣本的數量和事件佈局,而另一些則僅使用單個標誌來啟用單個(未指定)級別的 MSAA。

最後,在向螢幕呈現 MSAA 幀緩衝區時,某些呈現 API 可能需要特殊標誌或限制。例如,有時必須使用特殊模式將 MSAA 幀緩衝區呈現給螢幕,該模式在呈現後將幀緩衝區的內容標記為無效。這考慮到幀緩衝區必須在呈現過程中從其每畫素多樣本格式“解析”為每畫素單一顏色的事實,從而在此過程中破壞多樣本資訊。

9 總結

光柵化為我們提供了整個管道中一些最低級別但數學上最有趣的概念。我們已經討論了數學概念(如投影變換)和渲染方法(如透視正確紋理)之間的聯絡。此外,我們在討論深度緩衝區時解決了數學精度問題。最後,點取樣與區域取樣的概念出現了兩次,與 mipmapping 和抗鋸齒有關。無論是在硬體、軟體還是兩者的混合中實現,整個圖形管道最終都被設計為只為光柵化器提供資料,這使得光柵化器成為最重要但最不為人知的渲染技術之一。

由於在各種平臺上都可以使用高質量、低成本的 3D 硬體,因此必須實現自己的光柵化器的讀者比例現在已經微乎其微了。然而,即使對於那些永遠不需要編寫光柵化器的人來說,瞭解光柵化器的功能也很重要。例如,即使是對深度緩衝系統的基本實際理解也可以幫助程式設計師構建一個場景,以避免在可見表面確定期間出現視覺偽影。瞭解光柵化器的內部工作原理可以幫助 3D 程式設計師快速除錯幾何管道中的問題。最後,這些知識可以指導程式設計師更好地最佳化他們的幾何管道,為他們的光柵化器“提供”高效能資料集。

參考文獻

[35] David H。 Eberly。 3D Game Engine Design。 Morgan Kaufmann Publishers, San Francisco, 2001。

[44] Cass Everitt。 Interactive order-independent transparency。 Technical report, NVIDIA, 2001。

[49] Tom Forsyth。 Premultiplied alpha。

http://

home。comcast。net/

∼tom_forsyth/blog。wiki。html。

[54] Andrew S。 Glassner。 Principles of Digital Image Synthesis。 Morgan Kaufmann Publishers, San Francisco, 1994。

[74] Paul Heckbert。 Texture mapping polygons in perspective。 Technical report, New Institute of Technology, 1983。

[75] Paul Heckbert and Henry Moreton。 Interpolation for polygon texture mapping and shading。 In David Rogers and Rae Earnshaw, editors, State of the Art in Computer Graphics: Visualization and Modeling, pages 101–111。 Springer-Verlag, Berlin,1991。

[76] Chris Hecker。 Under the hood/behind the screen: Perspective texture mapping(series)。 Game Developer Magazine, 1995–1996。

[82] John F。 Hughes, Andries van Dam, Morgan McGuire, David F。 Sklar, James D。 Foley, Steven K。 Feiner, and Kurt Akeley。 Computer Graphics: Principles and Practice。 Addison-Wesley, Reading, MA, 3rd edition, 2013。

[89] Brano Kamen。 Maximizing depth buffer range and precision。

http://

outerra。blogspot。com/20

12/11/maximizing-depth-buffer-range-and。html

[107] Morgan McGuire and Louis Bavoil。 Weighted blended order-independent transparency。 Journal of Computer Graphics Techniques (JCGT), 2(2):122 -141,2013。

[123] Thomas Porter and Tom Duff。 Compositing digital images。 In Proceedings of the 11th Annual Conference on Computer Graphics and Interactive Techniques (SIGGRAPH ‘84), pages 253–259, 1984。

[148] Ken Turkowski。 Filters for common resampling tasks。 In Andrew S。 Glassner, editor,Graphics Gems, pages 147–165。 Academic Press Professional, San Diego, 1990。

[157] Lance Williams。 Pyramidal parametrics。 In Computer Graphics (SIGGRAPH ’83 Proceedings), 1983。

[159] George Wohlberg。 Digital Image Warping。 IEEE Computer Society Press, Los Alamitos, CA, 1990。

本文由 mdnice 多平臺釋出

標簽: 片段  畫素  紋理  三角形  深度