圖形學中渲染過程基本上可以分解為兩個主要任務:可見性和著色。光柵化可以說是一種解決可見性問題的方法??梢娦园軌蚍直嫒S物體的哪些部分對攝像機是可見的。這些物體的某些部分可以被禁止,因為它們要么在攝像機的可見區域之外,要么被其他物體隱藏。
解決這個問題基本上可以通過兩種方式進行。你可以通過圖像中的每個像素追蹤一條射線,找出相機與該射線相交的任何物體(如果有的話)之間的距離。通過該像素可見的物體就是相交距離最小的物體(一般用 t 表示)。這就是光線追蹤中使用的技術。請注意,在這種特殊情況下,你通過在圖像中的所有像素上循環,為每個像素追蹤一條光線,然后找出這些光線是否與場景中的任何物體相交來創建圖像。換句話說,該算法需要兩個主要循環。外循環遍歷圖像中的像素,內循環遍歷場景中的物體。
在光線追蹤中,我們追蹤一條穿過圖像中每個像素中心的光線,然后測試這條光線是否與場景中的任何幾何體相交。如果找到相交,我們將像素顏色設置為與光線相交的對象的顏色。因為一條射線可能與多個對象相交,所以我們需要跟蹤最近的相交距離。
for?(each?pixel?in?image)?{?請注意,在這個例子中,對象實際上被認為是由三角形組成的(而且只是三角形)。我們沒有迭代其他對象,而是把對象看作是一個三角形池子,然后迭代其他三角形。三角形經常被用作光線追蹤和光柵化的基本渲染基元(GPU 需要對幾何體進行三角化)。
光線追蹤是解決可見性問題的第一個可能的方法。我們說這種技術是以圖像為中心的,因為我們將光線從攝像機射入場景(我們從圖像開始),而不是反過來,這是我們將在光柵化中使用的方法。
光柵化采取的是相反的方法。為了解決可見性問題,它實際上是將三角形 "投射 "到屏幕上,換句話說,我們使用透視投影,將三角形的三維表示變成二維表示。這可以通過將構成三角形的頂點投射到屏幕上(使用我們剛才解釋的透視投影)來輕松實現。算法的下一步是使用一些技術來填滿該二維三角形所覆蓋的圖像的所有像素。這兩個步驟如下圖所示。從技術角度來看,它們的執行非常簡單。投影步驟只需要進行透視分割,并將所得到的坐標從圖像空間重新映射到光柵空間。找出所產生的三角形覆蓋了圖像中的哪些像素,也非常簡單。
與光線追蹤方法相比,該算法是什么樣子的呢?首先,請注意,在光柵化中,我們不是先迭代圖像中的所有像素,而是在外循環中迭代場景中的所有三角形。然后,在內循環中,我們迭代圖像中的所有像素,并找出當前像素是否 "包含 "在當前三角形的 "投影圖像 "中。換句話說,這兩個算法的內循環和外循環是對調的。
光柵化可以被粗略地分解為兩個步驟。我們首先使用透視投影法將構成三角形的三維頂點投射到屏幕上。然后,我們對圖像中的所有像素進行循環,測試它們是否位于所產生的 2D 三角形內。如果是的話,我們就用三角形的顏色來填充這個像素。
//?rasterization?algorithm下文將全面介紹光柵化的實現細節?!颈疚挠休^多公式,可添加 jinjun2050 獲取 pdf 版本】
渲染管道的最后一個主要階段稱為光柵化。光柵化是采用屏幕空間幾何圖形、片段著色器和該著色器的輸入并將幾何圖形實際繪制到低級二維 (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) 的渲染系統的過程至關重要。
每件顯示設備硬件,無論是計算機顯示器、電視還是其他類似設備,都需要圖像數 據源。對于計算機圖形系統,這種圖像數據源稱為幀緩沖區(之所以這么稱呼,是 因為它是一個數據緩沖區,用于保存幀的圖像信息,或屏幕的圖像價值)?;径?#xff0c;幀緩沖區是 2D 數字圖像:一塊內存,其中包含表示屏幕上每個點的顏色的數值。每個顏色值代表屏幕在給定點的顏色——一個像素。每個像素都有紅色、綠色 和藍色分量。放在一起,這個幀緩沖區代表要在屏幕上繪制的圖像。每次需要更新 屏幕上的圖像時,顯示硬件都會從內存中讀取這些顏色,通常每秒至少 30 次,通常每秒 60 次或更多次。
正如我們將看到的,幀緩沖區通常包含的不僅僅是每個像素的單一顏色。雖然實際用于設置顯示器上每個點發出的光的顏色和強度的是最終的逐像素顏色, 但其他逐像素值在光柵化過程中內部使用。從某種意義上說,這些其他值類似于每 個頂點法線和每個三角形的材質顏色。雖然它們從不直接顯示,但它們對最終顏色的計算方式有重大影響。
光柵化整個幀所需的步驟如圖 1 所示。第一步是從幀緩沖區中清除任何以前的圖像。這在某些情況下可以跳過;例如,如果已知場景幾何圖形覆蓋整個屏幕,則無需清除屏幕。在這種情況下,舊圖像將被新圖像完全覆蓋。但對于大多數應用程序 ,此步驟涉及使用渲染應用程序編程接口 (API) 將幀緩沖區中的所有像素(在單個函數調用中)設置為固定顏色。
第二步是將幾何光柵化到幀緩沖區。我們將在本文的其余部分詳細介紹這個階段,因為它是三個步驟中最復雜的步驟(到目前為止)。
第三步是將幀緩沖圖像呈現 (present) 給物理顯示器。這個階段通常稱為交換或緩沖區交換(swapping or buffer swapping),因為從歷史上看,它經常涉及(并且在許多情況下仍然涉及)兩個緩沖區之間的切換——在顯示另一個時繪制到一個,然后在每一幀之后交換兩個緩沖區。這是為了避免在渲染期間出現閃爍或其他偽影(特別是為了避 免讓用戶看到部分渲染的幀)。然而,本文后面描述的其他技術將需要在呈現步驟(presentation step)中完成額外的工作。因此,我們將使用更通用的術語 present 來指代這一步。
即使是一個簡單的光柵化管道也有幾個階段。應該注意的是,雖然這些階段往往存在于光柵化硬件實現中,但硬件幾乎從不遵循以下列表中概念階段的順序(甚至結構)。這個簡單的管道光柵化單個三角形如下:
第一階段進一步分解為兩個獨立的步驟:
本文的其余部分將詳細討論每個流水線階段。
為了在渲染的光柵化階段取得進一步進展,我們必須將屏幕空間中的三角形(或更一般地,幾何體)分解成更直接匹配幀緩沖區中像素的片段。這涉及確定像素矩形或像素中心點與三角形的交點。在光照和陰影中,我們使用術語片段來表示多邊形表面上給定點周圍的無限小表面積。片段著色器被描述為在這些微小的表面上進行求值。
在光柵化級別,片段具有更明確但相關的定義。它們是上述分解屏幕空間三角形以匹配像素的過程的結果。這些片段可以被認為是屏幕空間中像素大小的三角形碎片(pieces)。這些可以被可視化為一個三角形,通過沿著像素邊界切割成小塊(pieces)。許多這些片段(三角形的內部)將是正方形的,即像素正方形的完整大小。我們稱這些像素大小的片段為完整片段。然而,沿著三角形的邊緣,這些片段可能是放置在像素正方形內部的被分為多個邊的多邊形,因此小于像素。我們稱這些較小的片段為部分片段。在實踐中,這些片段可能實際上是在像素中心拍攝的三角形的點樣本(類似于我們在光照和陰影中對片段的概念),但基本的想法是,片段代表了一個三角形的碎片(pieces),這些碎片影響到了一個給定的像素。我們將把像素看作是目的地或箱子,我們把覆蓋該像素區域的所有碎片放入其中。因此,它不是一個一對一的映射。一個像素可能包含來自不同(甚至是相同)對象的多個片段, 或者一個像素可能不包含場景的當前視圖中的任何片段。
本文的其余部分將使用這個更具體的片段定義。圖 2 顯示了一個覆蓋有像素矩形邊界的三角形。圖 3 顯示相同的配置被分解成片段,包括完整的和部分的。圖中將片段稍微分開,以更好地展示部分片段的形狀。
整個場景中的片段數量可以比屏幕上的像素數量少得多,也可以多得多。如果幾何圖形僅覆蓋屏幕的一個子集,則可能有許多像素不包含場景中的片段。另一方面,如果許多三角形在屏幕空間中相互重疊,那么屏幕上的許多像素可能包含多個片段。給定幀中場景中的片段數與屏幕上像素數的比率稱為深度復雜度或過度繪制,因為該比率表示有多少全屏幾何體構成場景。通常,具有更高深度復雜度的場景光柵化成本更高。請注意,這是整個視圖的總體比率;即使幾何圖形僅覆蓋屏幕的一半,場景的深度復雜度也可能為 2。如果平均而言,被覆蓋的一半屏幕上的幾何圖形是四個三角形深度,那么深度復雜度將是每個像素兩個片段在整個屏幕上攤銷。
三角形是凸的,無論它們如何通過射影變換進行投影(在某些情況下,三角形可能顯示為線或點,但它們仍然是凸對象)。這是一個非常有用的屬性,因為它意味著任何三角形與水平的像素行相交(也稱為掃描線,由于歷史原因與基于 CRT 的電視顯示器)最多在一個連續的片段中。因此,對于與三角形相交的任何掃描線, 我們可以僅用最小 x 值和最大 x 值表示交點,稱為跨度(span)。因此,在光柵化期間三角形的表示由一組跨度組成,每條掃描線一個,三角形相交。此外,三角形的凸性還意味著與三角形相交的掃描線集合在 y 上是連續的;給定三角形有一個最小值和一個最大值 y,其中包含所有非空跨度。圖 4 顯示了三角形跨度集的示例。覆蓋在三角形上的暗帶代表將用于繪制三角形的相鄰片段的跨度。
三角形的 y~min~ 即最小 y 像素坐標就是三個三角形頂點的最小 y 值。類似地,三角形的最大 y 像素坐標 y~max~就是三個頂點的最大 y 值。因此,三個頂點之間的簡單 min/max 計算定義了必須為三角形生成的 (y~max~?y~min~+1) 的整個跨度范圍。
每個跨度的最左邊和最右邊的片段可能是部分片段,因為三角形的邊緣可能不會完全落在像素邊界上。此外,出于同樣的原因,最頂部和最底部的跨度可能包含部分片段。三角形的剩余片段將是完整的片段。
生成跨度本身只是涉及到將水平掃描線與三角形的邊緣相交。由于三角形的凸性,除非掃描線與一個頂點相交,該掃描線將與三角形的兩條邊恰好相交: 一個從三角形外跨入三角形,一個再次離開三角形。這兩個交點將定義跨度的最小和最大 x 值。
完整的片段總是繼續到光柵化過程的下一個階段。然而,部分片段的命運取決于特定的渲染系統。在更高級的系統中,一個像素處的所有部分片段都作為部分片段傳遞,最終像素的可見性和顏色可能會受到所有這些部分的影響。然而,更簡單的光柵化系統不處理部分片段,并且必須在生成部分片段時決定是丟棄該片段還是將其提升為完整片段。解決此問題的常用方法是當且僅當它們包含像素的中心點時才保留部分片段。這有時稱為幾何點采樣,因為整個片段是基于每個像素內的單點樣本生成或不生成的。圖 5 顯示了與圖 3 相同的三角形,但部分片段被丟棄或提升為完整片段,具體取決于片段是否包含像素的中心點。
當一個三角形的頂點或邊緣正好落在一個像素中心時,這樣的圖形系統的行為是由與系統相關的填充慣例決定的。它確保如果兩個三角形共享一個頂點或一條邊,只有一個三角形會為像素貢獻一個片段。這一點非常重要,因為如果沒有一個明確的填充約定,在三角形之間的共享邊緣可能會出現空洞(兩個三角形的部分碎片都被丟掉的像素)或重復繪制的像素(兩個三角形的部分碎片都被提升為完整的碎片)。沿著共享三角形邊緣的孔允許背景色透過本來是連續的、不透明的表面,使表面看起來有裂縫穿過。沿著共享邊緣的雙重繪制的像素會導致更微妙的偽影,通常只有在使用透明或其他形式的混合時才會看到(見第 8.1 節)。關于實現點取樣填充約定的細節,見 Hecker 的《游戲開發者雜志》系列文章 [76]。
渲染幾何圖形的總體目標是確保最終渲染的圖像令人信服地代表給定場景。在最高級別上,這意味著物體必須看起來被更近的物體正確遮擋,并且不能被更遠的物體遮擋。這個過程被稱為可見表面測定 (VSD),并且有許多非常不同的方法來完成它。這些方法都涉及在一個或另一個粒度級別上比較表面的深度,并以給定像素處的最小深度對象(即最近的對象)是渲染到屏幕的對象的方式渲染它們。
歷史上,有許多不同的方法被用于 VSD。許多早期的算法都是基于巧妙的排序技巧,包括在光柵化之前將幾何體從后往前排序。這是一個昂貴的命題,通常在 CPU 上每幀計算一次。到目前為止,今天最常用的方法是基于光柵化的方法:深度緩沖區。光柵化器是圖形管線中最早被加速的部分,有專門的硬件,這意味著基于光柵化器的可見表面確定系統可以實現高性能。深度緩沖器也被稱為 Z 緩沖區(z-buffer)。它實際上是更普遍的深度緩沖的一個具體的、特殊的案例。
深度緩沖是基于可見性應以輸出為重點的概念。換句話說,由于像素是我們渲染管道的最終目的地,可見性應該在每個像素(或者更確切地說,每個片段)的基礎上計算。如果在每個像素處看到的最終顏色是具有最小深度的片段的顏色(在繪制到該像素的所有片段中),則場景將顯示為正確繪制。換句話說,在繪制到一個像素的所有片段中,具有最小深度的片段應該“贏得”該像素并選擇該像素的顏色。出于討論的目的,我們假設點采樣幾何(即,沒有部分片段)。
由于常見的光柵化方法傾向于一次渲染一個三角形,一個給定的像素可能會在一幀的過程中被來自不同三角形的片段重繪幾次。如果我們希望避免按深度對三角形進行排序(我們確實這樣做了),那么應該獲得給定像素的片段可能不是最后一個繪制到該像素的片段。我們必須有某種方法來存儲當前最近片段在每個像素處的深度以及該片段的顏色。
存儲了這些信息后,我們可以在每次將片段繪制到像素時計算一個簡單的測試。如果新片段的深度比該像素當前存儲的深度值更接近,則新片段贏得該像素。計算新片段的顏色,并將這個新片段顏色寫入像素。片段的深度值替換該像素的現有深度值。如果新片段的深度大于為像素著色的當前片段,則忽略新片段的顏色和深度,因為片段表示當前像素處最近的已知表面后面的表面。在這種情況下,我們知道新片段將在該像素處被遮擋,因為我們已經在該像素處看到了比最新片段更近的片段。圖 6 表示將片段從兩個三角形渲染到一個小的深度緩沖區。請注意更接近的三角形的片段如何總是贏得像素(正確的結果),即使它是先繪制的。
因為該方法是按像素計算的,因此是按片段計算的,因此每個三角形的深度是在每個片段的粒度上計算的,并且該值用于深度比較。由于這種更精細的子三角形粒度 ,深度緩沖區會自動處理無法使用逐三角形排序正確顯示的三角形配置。幾何圖形可以以任何順序傳遞到深度緩沖區。這種隨機順序可能有問題的情況是給定像素的兩個片段具有相同的深度。在這種情況下,順序很重要,具體取決于用于排序深度的確切比較(即<或≤)。然而,這種情況對于幾乎任何可見表面方法都是有問題的。
深度緩沖區有幾個缺點,盡管其中大多數在現代 PC 或游戲機上不再重要。深度緩沖方法的歷史缺陷之一隱含在方法的名稱中;它需要一個緩沖區或深度數組值,每個像素一個。這是一大塊內存,通常需要與幀緩沖區本身一樣多的內存。同樣,正如幀緩沖區必須在每幀之前清除為背景顏色一樣,深度緩沖區也必須清除為背景深度,通常是可表示的最大深度值。這些問題在 GPU 內存有限的手持和嵌入式 3D 系統上可能很嚴重。最后(仍然適用于 PC 和控制臺),深度緩沖區需要以下工作:
在許多 GPU 上,深度緩沖區存儲在分層的壓縮數據結構中,允許使用大塊像素進行快速拒絕測試。但是,對于基本實現,這是為每個片段計算的。對于大多數軟件光柵器,這個額外的逐片段的工作會使深度緩沖不適合持續使用。全軟件 3D 系統傾向于盡可能使用優化的幾何排序,為真正需要它的少數對象保留深度緩沖。例如,早期的第三人稱射擊游戲渲染引擎將大量工作投入到環境的專門排序中,從而避免對它們進行任何深度緩沖測試。這留下了足夠的 CPU 周期來使用軟件深度緩沖渲染動畫角色、怪物和小物體(覆蓋的像素比風景少得多)。
此外,深度緩沖區并不能解決高深度復雜度場景的潛在性能問題。我們仍然必須計算每個片段的深度并將其與緩沖區進行比較。但是,在某些情況下,它可以減少過度繪制的問題,因為沒有必要計算或寫入任何未通過深度測試的片段的顏色。事實上,一些應用程序會嘗試以大致從近到遠的順序渲染它們的深度緩沖場景(同時仍然避免在 CPU 上按三角形、按幀排序),這樣后面的幾何圖形可能會使深度緩沖失敗測試并且不需要顏色計算。
深度緩沖在硬件加速平臺上運行的 3D 應用程序中非常流行,因為它易于使用,需要很少的應用程序代碼或主機 CPU 計算,并且可以以高性能生成高質量的圖像。
使用深度緩沖區計算片段可見性的第一步是計算當前片段的深度值。正如我們將看到的, 將工作得很好。然而, 工作良好而視圖空間值 不工作的原因是相當有趣的。
為了更好地理解深度值如何在屏幕空間中跨三角形變化的性質,我們必須能夠將屏幕上的點映射到投影到它的三角形中的點。這與拾取非常相似,我們將使用幾個概念。由于透視投影的非線性特性,我們會發現我們從屏幕空間像素到給定視圖空間點的映射三角有點復雜。我們將通過幾個較小的階段來跟蹤此映射。對于本文的討論,我們將假設我們正在使用 OpenGL 樣式的矩陣,我們在視圖空間中向下看?z 軸。
視圖空間中的三角形只是視圖空間中平面的凸子集。因此,我們可以通過平面的法向量 來定義視圖空間中三角形的平面,并且一個常數 d,使得平面上的點 滿足
回顧拾取,2D 歸一化設備坐標 中的一個點映射到視圖空間射線 t**r **使得
其中 是投影距離(從視圖空間原點到投影平面的距離)。投影到 處的像素的任何視點空間必須與這條射線相交。通常,我們不能反轉投影變換,因為屏幕上的一個點映射到視圖空間中的一條射線。但是,通過知道三角形的平面,我們可以將三角形與視線相交,如下所示。視圖空間中落在三角形平面內的所有點 P 由公式 1 給出。此外,我們知道三角形上投影到 的點對于某些 t 必須等于 tr。用向量 tr 代替公式 1 中的點 并求解 t,
從這個 t 值,我們可以計算出沿投影射線的點 ,它是投影到 的三角形上的視圖空間的點。這相當于發現
但是,我們現在只對 感興趣,因為我們正在嘗試計算深度緩沖的 perfragment 值。公式 2 的 分量是
作為對已知結果的快速檢查,請注意,在具有恒定深度 的三角形的特殊情況下,我們可以替換
代入公式 3,計算結果為預期常數 :
正如公式 3 中所定義的, 是一個計算每個片段的代價高昂的值(在一般的非常數深度情況下),因為它是具有非常數分母的分數。
這將需要按片段劃分來計算 ,這比我們想要的要昂貴。然而,深度緩沖只需要能夠相互比較深度值。如果我們比較 值,我們知道它們隨著深度的增加而減小(因為視圖方向是?z),給出深度測試:
≥DepthBuffer→新片段可見
<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 緩沖,因為它直接使用 。
在實踐中,屏幕空間中的深度緩沖有一些數值精度限制,可能會導致視覺偽影。正如前面討論深度緩沖區時提到的,對象被繪制到深度緩沖系統的順序(至少在不透明對象的情況下)只有在兩個表面(兩個片段)的深度值是在給定像素處相等。理論上,除非所討論的幾何對象真正共面,否則這不太可能發生。然而,由于計算機數字表示沒有無限的精度,不共面的表面可以映射到相同的深度值。這可能導致以錯誤的順序繪制對象。
如果我們的深度值被線性映射到視圖空間,那么一個 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 緩沖區。也就是說,浮點深度緩沖區可能存在其他問題,過度校正并使最靠近相機的場景區域精度太低。這在渲染場景中尤其明顯,因為最接近相機的幾何圖形對觀看者來說是最明顯的。
最后,避免這些問題的最簡單方法是通過將近平面盡可能遠地移動來最大化深度緩沖區的使用,從而不會浪費靠近近平面的精度。所有這些方法都有依賴于場景和應用程序的權衡。
在大多數圖形系統中使用深度緩沖需要在渲染代碼中添加幾個點:
第一步是確保使用深度緩沖區創建渲染窗口或設備。這因 API 不同而不同,Iv(IvGraphics 圖形 API)在所有情況下都會自動分配深度緩沖區。請求創建深度緩沖區后(在大多數情況下,只是請求深度緩沖區,取決于硬件支持),必須在每幀開始時清除緩沖區。深度緩沖區的清除通常使用與幀緩沖區清除相同的功能。Iv 使用 IvRenderer 函數 ClearBuffers,但帶有新參數 kDepthClear。雖然可以使用獨立于幀緩沖區來清除深度緩沖區
renderer->ClearBuffers(kDepthClear);如果您在幀開始時清除兩個緩沖區,則在某些系統上通過一次調用清除它們可能會更快,這在 Iv 中如下完成:
renderer->ClearBuffers(kColorDepthClear);要啟用或禁用深度測試,我們只需使用 IvRenderer 函數 SetDepthTest。要禁用測試,請通過 kDisableDepthTest。要啟用測試,請通過其他測試模式之一(例如 kLessDepthTest)。默認情況下,深度測試被禁用,因此應用程序應在渲染之前顯式啟用它。最常見的深度測試模式是 kLessDepthTest 和 kLessEqualDepthTest。如果其深度值小于或等于當前像素深度,則后一種模式會導致使用新片段。
深度值的寫入也可以啟用或禁用,與深度測試無關。正如我們將在本文后面看到的那樣,在禁用深度緩沖區寫入的同時啟用深度測試會很有用。調用 IvRenderer 函數 SetDepthWrite 可以啟用或禁用寫入 z 緩沖區。
光柵化管道的下一個階段是通過評估當前片段的當前活動片段著色器來計算片段的整體顏色(以及可能的其他著色器輸出值)。這反過來又要求在當前片段位置
請注意,給定片段可能存在許多來源。作為著色器輸入源生成的一部分,它們中的每一個都必須對每個片段進行獨立評估。在計算了每個片段的源值之后,必須通過運行片段著色器來生成最終的片段顏色。在片段著色器中組合每個片段頂點顏色值、每個頂點光照值和紋理顏色有各種方法。著色器生成最終的片段顏色,該顏色將傳遞到光柵化管道的最后階段,即混合(本文稍后將討論)。
接下來的幾節將討論如何從我們列出的源中計算每個片段的著色器源值。雖然有許多可能的方法可以使用,我們將專注于在屏幕空間中快速計算并且非常適合大多數光柵化軟件甚至一些光柵化硬件的以掃描線為中心的特性的方法。
值與管道中的所有其他階段一樣,每個對象的值或顏色最容易光柵化。對于每個片段,恒定的統一值可以直接向下傳遞給著色器。不需要對每個片段進行評估或計算。因此,統一值對片段著色過程的性能影響最小。
正如我們之前討論過的,每個頂點的屬性是從最后一個頂點處理階段(在我們的例子中,從頂點著色器)傳遞給片段著色器的變量。這些值僅在每個三角形的三個頂點處定義,因此必須進行插值以確定三角形中每個片段中心的值。正如我們將看到的,在一般情況下,要正確計算三角形的每個片段,這可能是一項昂貴的操作。但是,我們將首先查看等深三角形的特殊情況。這種情況下的映射在計算上一點也不昂貴,即使在渲染非恒定深度的三角形時(尤其是在軟件渲染器中),它也是一個誘人的近似值。
為了分析恒定深度的情況,我們將確定恒定深度三角形的映射本質,從像素空間,通過 NDC 空間,到視圖空間,通過重心坐標,最后到逐頂點源屬性。我們首先從從像素空間映射到視圖空間的特殊情況開始。
整體投影方程(從視圖空間映射到 NDC 空間到屏幕空間像素坐標)的所有形式
其中 a,c 不等于零。如果我們假設一個三角形的頂點都在相同的深度(即,視圖空間 等于三角形中所有點的常數 ),那么一個點在三角形內部是
注意 a,c 不等于零意味著 a′,c′不等于零,所以我們可以重寫這些使得
因此,對于恒定深度 的三角形,
投影在 平面上形成從屏幕頂點到視圖空間頂點的仿射映射。
重心坐標是視圖空間頂點的仿射映射。
頂點屬性定義了從重心坐標到屬性值的仿射映射(例如,Gouraud 著色)。
如果我們組合這些仿射映射,我們最終會得到一個從屏幕空間像素坐標到屬性值的仿射映射。例如,我們可以將這個從像素坐標到顏色的仿射映射寫為
其中 、 和 都是顏色(每種顏色都可能為負數或大于 1.0)。有關將三個屏幕空間像素位置和相應的三重頂點顏色映射到三種顏色 、 和 的公式的推導,請參見 Eberly[35] 的第 126 頁。從我們先前對屏幕空間中逆 z 屬性的推導,我們注意到顏色 是常數 z 三角形的屏幕仿射:
與 1/z 一樣,我們可以簡單地通過計算三角形中“基本片段”顏色的前向差異來計算常量三角形的每個頂點屬性的每個片段值。
當使用透視投影投影在相機空間中沒有恒定深度的三角形時,生成的映射不是屏幕仿射的。從我們對深度緩沖區值的討論中,我們可以看到給定視圖空間中的一般(不一定是恒定深度)三角形,從 NDC 空間到三角形上的視圖空間點的映射具有以下形式
這些是射影映射(projective mappings),而不是我們在恒定深度情況下的仿射映射(affine mappings)。這意味著從屏幕空間到線性插值的每個頂點屬性的整體映射也是投影的。為了在透視投影中正確插入三角形的頂點屬性,我們必須使用這種更復雜的投影映射。
大多數硬件渲染系統現在以透視矯正的方式插入所有每個頂點的屬性。然而,這并不總是通用的,而且對于在低功率平臺上運行的舊軟件渲染系統來說,它太昂貴了。如果被插值的每個頂點屬性是來自每個頂點光照的顏色,例如在 Gouraud 著色的情況下,則可以在精度和速度之間進行權衡。請記住,Gouraud 著色首先是一種近似方法,在“正確性”的基礎上使用投影映射的理由有所減少。此外,Gouraud 陰影顏色的插值往往非常平滑,以至于很難判斷插值是否透視正確。事實上,Heckbert 和 Moreton[75] 提到紐約理工學院的離線渲染器在幾年前就在透視中錯誤地插值了顏色,直到有人注意到!因此,軟件圖形系統通常避免了昂貴的、透視正確的 Gouraud 顏色投影插值,而僅使用仿射映射和前向差分。
也就是說,其他每個頂點的值,例如紋理坐標,并不能容忍透視矯正插值中的問題。對紋理進行光柵化的過程首先是對每個頂點的紋理坐標進行插值,以確定每個片段的正確值。實際上,在光柵化器中插值通常是紋理坐標(紋理坐標乘以紋理圖像尺寸). 這個過程類似于對其他每個頂點屬性進行插值。然而,由于紋理坐標的使用實際上與片段著色器中的頂點顏色有些不同,我們無法使用前面描述的屏幕仿射近似。紋理坐標需要正確的透視插值。紋理坐標的間接性質意味著雖然紋理坐標在三角形上平滑而微妙地變化,但生成的紋理顏色查找不會。
紋理坐標的問題與仿射和投影變換的屬性有關。仿射變換將平行線映射到平行線,而射影變換只保證將直線映射到直線。任何曾經看過一條又長又直的道路的人都知道,形成道路邊緣的兩條線似乎在遠處相遇,即使它們是平行的。透視,作為一種投影映射,不保留平行線。
仿射插值和投影插值之間差異的經典示例是紋理坐標是棋盤格,以透視圖繪制。圖 9 顯示了作為圖像的方格紋理,以及應用環繞到由兩個三角形(兩個三角形以輪廓或線框顯示)形成的正方形的圖像。當頂部在透視圖中傾斜時,請注意,如果使用投影映射(圖 10)映射紋理,則垂直線會按預期會聚到遠處。
如果使用仿射映射對紋理坐標進行插值(圖 11),我們看到兩個不同的視覺偽影。首先,在每個三角形內,所有的平行線都保持平行,垂直線不會像我們預期的那樣會聚。此外,請注意沿正方形對角線(共享三角形邊緣)的線條中明顯的“扭結”。這有可能乍一看似乎是插值代碼的一個 bug,但稍微分析一下,它實際上是仿射變換的一個基本屬性。仿射變換由三角形的三個點定義。結果,在定義了三角形的三個點及其紋理坐標后,變換就沒有更多的自由度了。每個三角形獨立于其他三角形定義其變換,結果是應該是一組穿過正方形的線的彎曲。
然而,投影變換具有額外的自由度,表示為與每個頂點關聯的深度值。這些深度值改變了紋理坐標在三角形上插值的方式,并允許映射紋理圖像中的直線在屏幕上保持筆直,即使跨越三角形邊界。
幸運的是,這個解決方案相對簡單,但成本很高。正如我們從公式 4 中看到的, 可以使用屏幕空間位置的仿射映射來計算。由于紋理坐標本身是仿射映射,我們可以將它們與 仿射映射組合起來,發現 和 是仿射映射。因此,這三個量( 、 和 )可以使用前向差分在三角形上進行插值。在每個片段中,最終 ( , ) 值可以通過將 取反得到 zv 來計算,然后將其乘以插值的 和 。
這種投射式映射的缺點是,它需要對每個片段進行正確的評估。
1990 年代的許多 PC 游戲和一些視頻游戲機使用較便宜(且不太正確)的真實透視紋理近似值。然而,如前所述,在現代硬件光柵化系統上,每個片段的透視矯正紋理被簡單地假設。此外,可編程片段著色器基本上可以允許將任何逐頂點屬性用作紋理坐標這一事實進一步影響了硬件供應商以正確的視角插入所有頂點屬性。在實踐中,許多 GPU 并未針對所有頂點屬性遵循上述過程。相反,他們使用透視矯正插值計算一組重心坐標,然后使用這些重心坐標將每個屬性映射到正確的值。
每個頂點屬性的插值只是每個片段值的一種可能來源。由于現代片段著色器的強大功能,紋理坐標和其他值不需要直接來自每個頂點的屬性。作為涉及其他逐頂點屬性的計算的結果,可以從片段著色器本身中生成的一組坐標評估紋理查找。
在片段著色器中生成的紋理坐標甚至可以是之前在同一個片段著色器中查找紋理的結果。在這種技術中,第一個紋理中的紋理圖像值不是顏色,而是紋理坐標本身。這是一種非常強大的技術,稱為間接紋理。第一個紋理查找形成一個表查找或間接查找,為第二個紋理查找生成一個新的紋理坐標。
間接紋理是更一般的紋理案例的示例,其中評估紋理樣本會生成除顏色之外的“值”。顯然,并非所有紋理查找都用作顏色。然而,為了在下面的討論中易于理解,我們將假設紋理圖像的值代表最常見的情況——顏色。
上一節描述了如何在片段著色器中插入通用的逐頂點屬性,如果這些屬性是我們所需要的,我們可以簡單地評估或運行片段著色器并計算片段的顏色。但是,如果我們有紋理查找,這只是第一步。在計算或插值給定片段的紋理坐標后,必須將紋理坐標映射到紋理圖像本身以產生顏色。
一些最早的著色語言要求紋理只能通過每個頂點的屬性來處理,并且在某些情況下,甚至在調用片段著色器之前就實際計算了紋理查找。然而,如上所述,現代著色器允許在片段著色器本身中計算紋理坐標,甚至可能作為紋理查找的結果。此外,著色器中的條件和不同的循環迭代可能會導致某些片段的紋理查找被跳過。因此,我們將紋理的光柵化視為片段著色器本身的一部分。
事實上,雖然在片段著色器內部完成的數學計算很有趣,但孤立片段著色器評估中最(數學)復雜的部分是紋理查找的計算。正如我們將看到的,紋理查找不僅僅是抓取并返回最近的紋素到片段中心。將紋理映射到幾何體,然后將幾何體映射到片段的廣泛映射需要一組更大的技術來避免明顯的視覺偽影。
在我們對光柵化紋理的討論中,我們將使用多種不同形式的坐標。這包括應用程序級的、標準化的、與紋理無關的紋理坐標(u、v),以及與紋理大小相關的紋理坐標( 、 ),它們都被認為是實數值。我們在紋理介紹中使用了這些坐標。
紋理坐標的最終形式是整數紋素坐標,或紋素地址。這些代表對紋理圖像數組的直接索引。與其他兩種形式的坐標不同,它們(顧名思義)是整數值。從紋素坐標到整數紋素坐標的映射不是通用的,并且取決于紋理過濾模式,這將在下面討論。
在對紋理進行光柵化時,我們會發現——由于透視投影的性質、幾何對象的形狀以及紋理坐標的生成方式——片段很少直接和精確地對應于一對一映射中的紋素。任何支持紋理的光柵化器都需要處理各種紋素到片段的映射。在軟紋理初始討論中,我們注意到紋素坐標通常包括精度(通過浮點數或定點數),它比似乎需要的每紋素值更細粒度。正如我們將看到的,在某些情況下,我們將在稱為紋理過濾的過程中使用這種所謂的子像素精度來提高渲染圖像的質量。
紋理過濾(以其多種形式)通過混合紋理像素坐標映射和結果紋理像素值的組合,來執行從實值紋理像素坐標到最終紋理圖像值或顏色的映射。我們將對紋理過濾的討論分為兩種主要情況:一種是單個紋素映射到一個具有多個片段大小(放大)的區域,另一種是多個紋素映射到單個片段(縮小)所覆蓋的區域,因為它們的處理方式完全不同。
我們最初的紋理討論指出,將這些子紋理精確坐標映射到紋理圖像顏色的一個常見方法是簡單地選擇包含片段中心點的紋理,并直接使用它的顏色。這種方法稱為最近鄰紋理,計算起來非常簡單。對于任何 ( , ) 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 插值的事實并不影響結果(除了潛在的精度問題)??焖偬鎿Q表明,無論哪種方法,結果都是相同的。但是,請注意這不是仿射映射。二維仿射映射是由三個不同的點唯一定義的。我們的雙線性紋理映射的第四個源點可能與其他三個點定義的映射不匹配。
使用雙線性過濾,整個紋理域的顏色是連續的。一個最近鄰過濾和雙線性過濾之間視覺差異的示例如圖 16 所示。雖然雙線性濾波可以通過減少視覺塊狀來極大地提高放大紋理的圖像質量,但它不會為紋理添加新的細節。如果將紋理放大很多(即 1 紋素映射到許多像素),由于缺乏細節,圖像看起來會很模糊。圖 16 所示的紋理被高度放大,導致左圖 (a) 中出現明顯的塊狀,右圖 (b) 中出現模糊。
IvAPI 使用 IvTexture 函數 SetMagFiltering 來控制紋理放大率。Iv 支持雙線性過濾和最近鄰選擇。它們分別設置如下:
IvTexture*?texture;到目前為止,在我們討論光柵化的過程中,我們主要通過中心來指代片段——位于正方形片段中心的無窮小點(現在繼續假設只有完整的片段)。但是,片段具有非零區域。片段區域和代表它的點樣本之間的這種差異在紋理的常見情況下變得非常明顯。
舉個例子,想象一個物體離相機很遠。場景中的物體通常都具有高細節的紋理。這樣做是為了避免模糊(比如我們在圖 16b 中看到的模糊),當一個物體靠近相機時,它應用了一個低分辨率的紋理。當相同的物體和紋理被移動到遠處時(這是動態場景中的常見情況),由于物體的視角縮放,同樣的,詳細的紋理將被映射到屏幕上越來越小的區域。這被稱為紋理縮小,因為它是放大的反比。這導致相同的對象和紋理覆蓋的碎片越來越少。
在一個極端(但實際上很常見)的情況下,整個高細節紋理可能是以這樣一種方式映射,它只映射到幾個片段。圖 17 提供了這樣一個例子;在這種情況下,請注意,如果對象稍微移動(甚至小于一個像素),覆蓋片段中心點的確切紋素可能會發生巨大變化。事實上,這樣的點采樣在紋理中幾乎是隨機的,并且會導致用于片段的紋理的點采樣顏色隨著對象在屏幕上以微小的亞像素量移動而在幀與幀之間劇烈變化。隨著時間的推移,這可能會導致閃爍,這是動畫渲染圖像中令人分心的偽影。
問題在于,紋理中的大多數紋素對片段幾乎都有相同的“要求”,因為它們都投影在片段的矩形區域內。片段紋理樣本的整體顏色應代表其內部的所有紋素。一種思考方法是將投影平面上完整片段的正方形映射到三角形平面上,得到一個(可能是傾斜的)四邊形,如圖 18 所示。為了公平地評估該片段的紋理顏色,我們需要根據每個紋素覆蓋的四邊形的相對面積,計算該四邊形中所有紋素顏色的加權平均值。給定紋素覆蓋的片段越多,該紋素的顏色對片段紋理樣本的最終顏色的貢獻就越大。
雖然精確的面積加權平均方法會給出正確的片段顏色和將避免點采樣出現的問題,實際上這不是最適合實時光柵化的算法。根據紋理的映射方式,一個片段可以覆蓋幾乎無限數量的紋素。在每個片段的基礎上查找和求和這些紋素將需要潛在的無限量的每個片段計算,這甚至遠遠超出了硬件光柵化系統的能力。需要一種更快(最好是恒定時間)的方法來逼近這種紋素平均算法。對于大多數現代圖形系統,一種稱為 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 值。
使用 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 信息的成本(至少在每個三角形的基礎上)是值得的,與視覺質量的顯著提高無關。
上述方法的工作原理是,給定片段將有一個單一的“最佳”mipmap 級別。然而,由于每個 mipmap 級別是每個維度中下一個 mipmap 級別大小的兩倍,因此最接近的 mipmap 級別可能不是精確的片段到紋理映射。線性 mipmap 過濾不是選擇給定的 mipmap 級別作為最佳,而是使用類似于(雙)線性紋理過濾的方法?;旧?#xff0c;mipmap 過濾使用實值 L 來查找限制給定片段與紋素比率的相鄰 mipmap 級別對和 和 (地板和天花板符號)。剩余的小數部分 (L 到 ) 用于在兩個 mipmap 級別中找到的紋理顏色之間進行混合。
放在一起,現在有兩個獨立的過濾軸,每個軸都有兩種可能的過濾模式,導致四種可能的 mipmap 過濾模式,如表 1 所示。在這些方法中,最流行的是線性雙線性,也稱為三線性插值濾波或 trilerp,因為它是雙線性插值的精確 3D 模擬。它是這些 mipmap 過濾操作中最昂貴的,需要每個片段查找 8 個紋素,以及 7 個線性插值(兩個 mipmap 級別中的每一個級別三個,另外一個用于在級別之間進行插值),但它也產生了最平滑的結果。在 mipmap 級別之間進行過濾也會增加使用的紋理內存帶寬量,因為每個樣本都必須訪問兩個 mipmap 級別。因此,多級 mipmap 過濾通常會抵消前面提到的 mipmap 在硬件圖形設備上的性能優勢。
最后一種更新形式的 mipmap 過濾稱為各向異性過濾。到目前為止討論的 mipmap 過濾方法隱含地假設像素在映射到紋理空間時會產生一個與某個圓非常接近的四邊形——換句話說,紋理空間中的四邊形基本上是正方形的情況。在實踐中,通常情況并非如此。對于極端透視下的多邊形,一個完整的片段通常會映射到紋理空間中一個很長、很薄的四邊形。標準的各向同性過濾模式可能看起來太模糊(根據四邊形的長軸選擇了 mipmap 級別)或太尖銳(根據四邊形的短軸選擇了 mipmap 級別)。各向異性紋理過濾在對 mipmap 進行采樣時會考慮紋理空間四邊形的縱橫比,并且能夠過濾 mipmap 中的非正方形區域以生成準確表示傾斜多邊形紋理的結果。
的默認 CreateTexture 接口僅分配基本級別的紋理數據,而不分配其他 mipmap 級別。要使用 mipmaps 創建紋理,我們使用 CreateMipmappedTexture,如下所示:
IvResourceManager*?manager;請注意,我們現在傳入了一個圖像數據數組——每個數組條目都是一個 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;為了設置縮小過濾器,使用了 IvTexture 函數 SetMinFiltering。Iv 支持非 mipmapped 模式(雙線性過濾和最近鄰選擇)和所有四種 mipmapped 模式。質量最好的 mipmapped 模式(如前所述)是三線性過濾,使用
IvTexture*?texture;到目前為止,本章已經討論了生成片段,計算片段著色器的每個片段源值,以及評估片段著色器(紋理查找)的更復雜方面的一些細節。然而,本章的前幾節概述了所有這些每片段工作的真正目標:在場景的渲染視圖中生成像素的最終顏色?;叵胍幌?#xff0c;像素是構成矩形網格屏幕(或幀緩沖區)的目標值。像素是“箱”,我們在其中放置對該像素區域有影響的的表面碎片。片段代表這些像素大小的表面碎片。最后,我們必須將所有落入給定像素的箱子中的片段轉換為該像素的單一顏色和深度。到目前為止,我們在本章中做了兩個重要的簡化假設:
綜上所述,這兩個假設導致了一個重要的整體簡化:給定像素處最近的片段完全決定了該像素的顏色。在這樣的系統中,我們需要做的就是在一個像素處找到最近的片段,對該片段進行著色,然后將結果寫入幀緩沖區。在討論可見表面確定和紋理時,這是一個有用的簡化假設。但是,它限制了表示某些常見類型的表面材料的能力。它還可能導致屏幕上對象邊緣出現鋸齒狀的視覺偽影。因此,現代圖形系統中的兩個附加功能消除了這些簡化假設:像素混合允許片段部分透明,抗鋸齒處理包含多個部分片段的像素。我們將通過對兩者討論來結束本章。
像素混合是一個以片段為單位的非幾何函數,它的輸入是當前片段的著色顏色(我們稱之為 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 混合取決于順序。
在實踐中,alpha 混合的這種順序依賴性使深度緩沖復雜化。深度緩沖區基于這樣的假設,即給定深度的片段將完全遮蓋任何深度更大的片段,這僅適用于不透明對象。在存在 alpha 混合的情況下,我們必須以非常特定的順序計算像素顏色。我們可以對所有三角形進行深度排序,但如上所述,這很昂貴,并且對許多數據集存在嚴重的正確性問題。相反,一種選擇是假設對于大多數場景,半透明三角形的數量遠小于不透明三角形的數量。給定一組三角形,嘗試正確計算混合像素顏色的一種方法如下:
這似乎可以解決問題。但是,在大多數情況下,每個三角形的深度排序仍然是一項昂貴的操作,必須在主機 CPU 上完成。此外,每個三角形排序無法解決所有差異,因為存在無法正確排序的三角形的常見配置。已經提出了其他方法來避免這兩個問題。一種這樣的方法是在每個對象級別進行深度排序以避免粗略的無序混合,然后使用更復雜的方法,例如深度剝離 [44],它使用高級可編程著色和對象的多次渲染來“剝離”更近的表面(使用深度緩沖區)并生成深度排序的顏色。雖然相當復雜,但該方法完全在 GPU 上運行,并專注于讓最接近的圖層正確,根據理論是越來越深的透明度獲得遞減的回報(因為它們對最終顏色的貢獻越來越少)。
在某些特定應用的情況下,可以避免像素混合三角形的深度排序或深度剝離。另外兩種常見的像素混合模式是可交換的,因此是順序無關的。這兩種混合模式稱為 add 和 modulate。add 混合產生“發光”物體的效果,定義如下:
modulate 混合實現顏色過濾。它被定義為
請注意,這些效果都不涉及源顏色或目標顏色的 alpha 分量。add 和 modulate 混合模式仍然需要先繪制不透明對象,然后是混合對象,但都不需要將混合對象分類為深度排序。因此,這些混合模式在粒子系統效果中非常流行,其中使用了數千個微小的混合三角形來模擬后期的煙霧、蒸汽、灰塵或水。其他更復雜的與順序無關的透明度解決方案是可能的;一個例子見 [107]。
請注意,如果深度緩沖用于未排序的混合對象,則必須在禁用深度緩沖區寫入的情況下繪制混合對象,否則兩個混合對象的任何無序(從前到后)渲染將導致更多遠處的物體沒有被繪制。從某種意義上說,混合對象不存在于深度緩沖區中,因為它們不會遮擋其他對象。
在上面的討論中,我們假設我們的顏色與直接的 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,我們可以創建一個具有子區域的紋理,該子區域代表不同粒子類型的顏色,并將其與單一混合模式一起使用?;鸹W涌梢允褂镁哂蟹橇?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。
盡管在 Iv 支持的模式之外還有許多選項,但在大多數圖形系統中都可以非常簡單地啟用和控制混合。通過 IvRenderer 函數 SetBlendFunc 設置混合模式,該函數在單個函數調用中設置 Fsrc、Fdst 和運算符⊕。要使用經典的 alpha 混合(沒有預乘 alpha),函數調用是
renderer->SetBlendFunc(kSrcAlphaBlendFunc,?kOneMinusSrcAlphaBlendFunc,使用調用設置 Add 模式
renderer->SetBlendFunc(kOneBlendFunc,?kOneBlendFunc,?kAddBlendOp);Modulate 混合可以通過調用使用
renderer->SetBlendFunc(kZeroBlendFunc,?kSrcColorBlendFunc,?kAddBlendOp);還有更多的混合功能和操作可用;有關更多詳細信息,請參閱源代碼。
回想一下,在渲染混合對象時禁用 z 緩沖區寫入通常很有用。這是通過深度緩沖屏蔽實現的,前面在深度緩沖部分中進行了描述。
我們之前提出的另一個簡化光柵化假設,即部分片段被忽略或“提升”為完整片段的想法,引發了它自己的一系列問題。將所有片段轉換為全有或全無情況的想法是允許我們假設單個片段將“贏得”一個像素并確定其顏色。我們使用這個假設將每個片段的計算減少到單點樣本。
如果我們將像素視為純點樣本,沒有區域,這是合理的。然而,在我們對片段的初步討論和對 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] 詳細介紹了廣泛的采樣理論。
到目前為止討論的方法顯示了計算基于區域的像素顏色的理論方法。這些方法要求每個片段計算像素覆蓋值。計算三角形的分析(精確)像素覆蓋值可能既復雜又昂貴。在實踐中,純基于區域的方法不會直接導致簡單、快速的硬件抗鋸齒實現。
概念上最簡單、最流行的抗鋸齒方法稱為過采樣、超級采樣或超采樣抗鋸齒 (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 硬件系統,這也太昂貴了。
超級采樣抗鋸齒最昂貴的方面是為每個樣本創建單獨的片段以及每個樣本產生的紋理和片段著色。另一種抗鋸齒形式認識到,3D 渲染中鋸齒最可能的原因是對象邊緣的部分片段,其中像素將包含來自不同對象的多個部分片段,通常具有非常不同的顏色。多重采樣抗鋸齒 (MSAA) 試圖在不像 SSAA 那樣提高渲染成本的情況下解決這個問題。MSAA 的工作方式與正常渲染類似,因為它以最終像素大小生成片段(包括部分片段)。它只對每個片段評估一次片段著色器,因此與 SSAA 相比,片段著色器調用的數量顯著減少。
MSAA 添加的信息是每個樣本的片段覆蓋率。當一個片段被渲染時,它的顏色會被評估一次,然后為片段覆蓋的每個可見樣本存儲相同的顏色。樣本中的現有顏色(來自較早的片段)可能會被新片段的顏色替換。但這是在每個樣本級別完成的。在幀結束時,仍然需要“解析”來從多個樣本中計算像素的最終顏色。然而,每個樣本、每個片段僅計算一個覆蓋值(一個簡單的幾何運算)和可能的一個深度值。計算片段顏色的昂貴步驟仍然為每個片段完成一次。與 SSAA 相比,這大大降低了 MSAA 的費用。
MSAA 有兩個微妙之處值得一提。首先,由于 MSAA 是基于覆蓋的,因此不會對完整片段計算抗鋸齒。渲染完整的片段,就好像沒有使用抗鋸齒一樣。另一方面,SSAA 通過在每個像素中多次調用片段的著色器來消除每個像素的鋸齒。一個關鍵的觀察結果是,在單采樣完整片段中最有可能導致混疊(aliasing)的項目可能是紋理(因為它是片段中的最高頻率值)。紋理已經應用了一種抗鋸齒形式:mipmapping。因此,在大多數情況下,這對 MSAA 來說不是問題。
另一個問題是選擇像素中的位置來評估部分片段上的著色器的問題。通常,我們在像素中心評估片段著色器。但是,部分片段甚至可能無法覆蓋像素中心。如果我們在像素中心對片段著色器進行采樣,我們實際上會將頂點屬性外推到預期值之外。這在紋理中尤其明顯,因為我們將在三角形中可能未映射的位置讀取紋理。這會導致明顯的視覺偽影。大多數 3DMSAA 硬件中的解決方案是選擇片段覆蓋的樣本的重心。由于片段是凸的,因此重心將始終落在片段內部。這確實給系統增加了一些復雜性,但是不包括像素中心的片段的可能配置數量是有限的。凸性和中心樣本未被觸及的事實意味著可能存在一組非常有限的覆蓋樣本配置。甚至可以在構建硬件之前預先計算出一組可能的位置。但是,重心采樣必須按逐個屬性請求。否則,硬件將默認使用像素中心采樣。
對于大多數渲染 API,使用 MSAA 最重要的一步是創建與該技術兼容的渲染幀緩沖區。深度緩沖需要幀緩沖旁邊的附加緩沖區來存儲深度值,而 MSAA 需要特殊的幀緩沖格式,其中包括每個像素內每個樣本的附加顏色、深度和覆蓋值。不同的渲染 API 甚至相同 API 上的不同渲染硬件通常有不同的方法來顯式請求 MSAA 兼容的幀緩沖區。一些呈現 API 允許應用程序以像素格式指定樣本的數量和事件布局,而另一些則僅使用單個標志來啟用單個(未指定)級別的 MSAA。
最后,在向屏幕呈現 MSAA 幀緩沖區時,某些呈現 API 可能需要特殊標志或限制。例如,有時必須使用特殊模式將 MSAA 幀緩沖區呈現給屏幕,該模式在呈現后將幀緩沖區的內容標記為無效。這考慮到幀緩沖區必須在呈現過程中從其每像素多樣本格式“解析”為每像素單一顏色的事實,從而在此過程中破壞多樣本信息。
光柵化為我們提供了整個管道中一些最低級別但數學上最有趣的概念。我們已經討論了數學概念(如投影變換)和渲染方法(如透視正確紋理)之間的聯系。此外,我們在討論深度緩沖區時解決了數學精度問題。最后,點采樣與區域采樣的概念出現了兩次,與 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/2012/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 多平臺發布
本文由 貴州做網站公司 整理發布,部分圖文來源于互聯網,如有侵權,請聯系我們刪除,謝謝!
網絡推廣與網站優化公司(網絡優化與推廣專家)作為數字營銷領域的核心服務提供方,其價值在于通過技術手段與策略規劃幫助企業提升線上曝光度、用戶轉化率及品牌影響力。這...
在當今數字化時代,公司網站已成為企業展示形象、傳遞信息和開展業務的重要平臺。然而,對于許多公司來說,網站建設的價格是一個關鍵考量因素。本文將圍繞“公司網站建設價...
在當今的數字化時代,企業網站已成為企業展示形象、吸引客戶和開展業務的重要平臺。然而,對于許多中小企業來說,高昂的網站建設費用可能會成為其發展的瓶頸。幸運的是,隨...
魔獸世界xperl怎么用?首先,確定是否顯示化身。如果仍然是系統附帶的插件,則表示插件有問題或尚未打開。下一步,如果頭像已經更改,可以設置它。哦~xperl可以單獨使用,沒有排斥,并且可以覆蓋大部分的化身插件...
北京西站換乘火車最晚買票時間?北京西站24小時售票,買票沒有最晚時間。開車前提前五分鐘停止檢票上車就行了。如果在北京西站換車,網上買票,不需要出站,省去了來回拖行李。直接檢票上車。北京西站售票廳營業時間?北京西站售票處24小時開放。工作人員24小時輪班,但是售票窗口晚上不像白天那么開放。北京西站共設91個售票窗口,分別位于車站北大廳南大廳地下一層和二層。在此基礎上,車站在南北廣場東西售票廳北二層入...
糖果色是指什么顏色?糖果色多指傾向于亮色的淺色,如粉色、淺黃、奶藍色、草綠色、橙色等亮色。是讓人整體感覺充滿朝氣和活力的顏色,讓人瞬間感到愉悅和輕松。是一種感覺積極向上,然后又不沉悶的顏色。糖果色是指什么顏色?亮色屬于糖果色!紅、藍、綠、橙、紫、白、黑、橙、黃、綠、白、粉、黑、紅、紅、紅、粉、紅、紅、棕等。糖果色是什么色系?糖果色是2009年夏天開始流行的顏色。主色調為粉色、粉藍、粉綠、粉黃、亮紫...