進階渲染系列(六)——FXAA快速近似抗鋸齒(平滑像素)

目錄算法

1 設置場景數組

1.1 測試場景微信

1.2 超採樣函數

1.3 後處理性能

2 亮度測試

2.1 計算亮度優化

2.2 支持亮度數據url

2.3 採樣亮度spa

3 混合高對比度像素.net

3.1 肯定與相鄰像素的對比度

3.2 跳太低對比度像素

3.3 計算混合因子

3.4 混合方向

3.5 融合

4 沿着邊混合

4.1 邊亮度

4.2 沿着邊走

4.3 沿着兩個方向

4.4 肯定混合因子

4.5 質量

4.6 性能

5 顏色空間

5.1 LDR

5.2 Gamma

收起


本文重點:

一、計算圖像亮度

二、查找高對比度像素

三、識別對比度邊緣

四、選擇性混合

五、搜索邊的終點


本教程介紹瞭如何建立FXAA後期效果。它是接着「景深」教程以後發佈的。

本教程使用Unity 2017.3.0p3製做。


(掌握FXAA的藝術以對抗鋸齒和螢火蟲)


1 設置場景


由於顯示的分辨率有限。結果,與像素網格不對齊的圖像特徵會出現鋸齒。對角線和彎曲線顯示爲梯度,一般稱爲鋸齒。細線可能會斷開鏈接並變成虛線。小於或小於一個像素的高對比度功能有時會出現,有時卻不會出現,致使物體移動時閃爍,一般稱爲螢火蟲。實際上,已經開發了一系列抗鋸齒技術來緩解這些問題。本教程介紹了經典的FXAA解決方案。

(細線及其光柵化後的表現)


1.1 測試場景


在本教程中,我建立了一個相似於「景深」中的測試場景。它包含高對比度和低對比度區域,較亮和較暗的區域,多個筆直和彎曲的邊緣以及一些小的特徵。和往常同樣,咱們使用HDR和線性色彩空間。全部場景屏幕截圖都會放大,以使單個像素更易於區分。


(測試場景 放大四倍 沒有任何抗鋸齒)


1.2 超採樣


消除混疊的最直接方法是以高於顯示器的分辨率進行渲染並對其進行下采樣。這是一種空間抗鋸齒的方法,它能夠捕獲和平滑對於顯示器而言較高的子像素特徵。

(在雙倍的分辨率下采樣,而後平均2X2的塊)


超級採樣抗鋸齒(SSAA)就是這樣作的。至少,場景要以最終分辨率的兩倍渲染到緩衝區,並平均四個像素的塊以生成最終圖像。甚至可使用更高的分辨率和不一樣的採樣模式來進一步改善效果。這種方法消除了鋸齒,但也會稍微模糊整個圖像。


(SSAA 2倍)


儘管SSAA有效,但它是一種暴力的方法,很是昂貴。將分辨率加倍,內存和陰影中的像素數量的存儲須要同時增長三倍。尤爲是填充率將會成爲瓶頸。爲了減輕這種狀況,引入了多樣本抗混疊(MSAA)。它還能夠實現更高的分辨率和更高的下采樣率,可是會改變片斷的渲染方式。它不是簡單地渲染高分辨率塊的全部片斷,而是每一個三角形渲染一個覆蓋該塊的單個片斷,從而有效地將結果複製到高分辨率像素。這樣可使填充率易於管理。這也意味着僅三角形的邊緣會受到影響,其餘全部內容均保持不變。這也是爲何MSAA不會平滑經過cutout 材質建立的透明邊的緣由。


(MSAA 2倍和8倍)


MSAA表現良好而且使用較多,可是它須要大量內存,而且不會與依賴深度緩衝區的效果(例如延遲渲染)結合在一塊兒。這就是爲何許多遊戲選擇不一樣的抗鋸齒技術的緣由。


那CSAA呢?

CSAA是指覆蓋採樣抗鋸齒(coverage sampling anti-aliasing)。它是MSAA的變體,但我在這裏不作詳細介紹。


1.3 後處理


執行抗鋸齒的第三種方法是經過後期效果處理。這些是與其餘效果同樣的全屏Pass,所以它們不須要更高的分辨率,但可能依賴於臨時渲染紋理。這些技術必須以最終分辨率工做,所以它們沒法訪問實際的子像素數據。相反,他們必須分析圖像並根據該結果進行選擇性地模糊。

選擇須要應用多種後處理技術了。第一個是形態抗鋸齒(morphological anti-aliasing )(MLAA)。在本教程中,咱們將建立咱們本身的快速近似抗鋸齒(FXAA)版本。它是由NVIDIA的Timothy Lottes開發的,其功能恰如其名。與MLAA相比,它以質量換取速度。儘管FXAA的一個常見問題是它模糊的太多,但這取決於使用哪一種變體以及如何對其進行調整。咱們將建立最新版本FXAA 3.11,針對PC的高質量版本。


對於新的FXAA着色器,咱們將使用與DepthOfField着色器相同的設置。你能夠複製它並將其減小爲僅執行一次blit的單個通道。

再次使用與景深效果相同的方法,建立一個最小的FXAAEffect組件。

設置組件腳本的默認着色器引用。

(默認的着色器引用)


將咱們的新效果做爲惟一效果附加到相機上。再次假設咱們在線性HDR空間中進行渲染,所以請相應地配置項目和攝影機。另外,因爲咱們執行本身的抗鋸齒功能,所以請確保已禁用MSAA。


(HDR攝像機 不使用MASS 而使用FXAA)


我仍然在場景視圖中看到MSAA嗎?

場景觀察相機使用質量設置中的MSAA設置,如今這種狀況下,它不會模仿主相機。


2 亮度


FXAA經過選擇性下降圖像對比度,平滑視覺上明顯的鋸齒和孤立的像素來起做用。經過比較像素的光強度來肯定對比度。像素的確切顏色可有可無,重要的是它們的亮度。實際上,FXAA可在僅包含像素亮度的灰度圖像上工做。這意味着,當它們的亮度類似時,不一樣顏色之間的硬過渡不會被平滑不少。僅在視覺上明顯的過渡會受到嚴重影響。


2.1 計算亮度


讓咱們從檢查此單色亮度圖像的外觀開始。因爲綠色份量對像素的亮度影響最大,所以能夠經過簡單地使用它來建立快速預覽,而無需丟棄紅色和藍色數據。


(使用綠色通道做爲亮度)


這是亮度的粗略近似。若是可以適當地計算亮度就最好了,爲此,咱們可使用UnityCG的LinearRgbToLuminance函數。



FXAA但願亮度值在0–1範圍內,可是在使用HDR顏色時不能保證這一點。一般,抗鋸齒是在色調映射和顏色分級以後進行的,應該消除大多數(即便不是所有)HDR顏色。可是咱們在本教程中不使用這些效果,而是使用鉗位的顏色來計算亮度。


(亮度)


LinearRgbToLuminance是什麼樣子的?

它是顏色通道的簡單加權總和,綠色是最重要的。



2.2 支持亮度數據


FXAA不會自行計算亮度。這是很是昂貴的,由於每一個像素須要多個亮度樣本。取而代之的是,必須經過更早的Pass將亮度數據放入alpha通道中。另外,當因爲某種緣由沒法使用Alpha通道時,FXAA可使用綠色做爲亮度。使用FXAA時,Unity的後期效果堆棧v2兩種方法都支持。


咱們也支持這兩個選項,可是因爲咱們不使用後期效果堆棧,所以也須要支持本身計算亮度。在FXAAEffect中添加枚舉字段以控制此字段,而後在檢查器中將其設置爲Calculate。

(luminance Source ,設置爲calculate)


當咱們必須本身計算亮度時,咱們將單獨進行一次處理,將原始RGB加亮度數據存儲在臨時紋理中。而後,實際的FXAA通道將使用該紋理而不是原始源。此外,FXAA通道須要知道是使用綠色仍是Alpha通道來得到亮度。咱們將經過LUMINANCE_GREEN shader關鍵字進行指示。



咱們能夠將現有的通道用於亮度通道。惟一的變化是亮度應存儲在alpha通道中,並保留原始RGB數據。新的FXAA通道以簡單的blit通道開始,並帶有LUMINANCE_GREEN的多編譯選項。



2.3 採樣亮度


要應用FXAA效果,咱們必須對亮度數據進行採樣。這是經過對主要紋理進行採樣並選擇其綠色或Alpha通道來完成的。爲此,咱們將建立一些便捷的功能,並將其所有放在着色器文件頂部的CGINCLUDE塊中。


如今,咱們的FXAA通道能夠僅將片斷的紋理座標做爲參數來調用ApplyFXAA函數。



3 混合高對比度像素


FXAA經過混合高對比度像素來完成工做。但這不是直接模糊圖像。首先,必須計算局部對比度。其次,若是有足夠的對比度,則必須根據對比度選擇混合因子。第三,必須研究局部對比度梯度以肯定混合方向。最後,在原始像素與其相鄰像素之間執行混合。


3.1 肯定與相鄰像素的對比度


經過將當前像素的亮度與其相鄰像素的亮度進行比較,能夠找到局部對比度。爲了使對鄰居像素的採樣變得容易,添加一個SampleLuminance函數變量,該變量具備以像素爲單位的U和V座標的偏移參數。這些應經過紋理像素大小進行縮放,並在採樣前添加到uv中。



FXAA使用直接的水平和垂直鄰居以及中間像素自己來肯定對比度。由於咱們將屢次使用此亮度數據,因此將其放在LuminanceData結構中。咱們將使用羅盤方向來引用鄰居數據,使用North表示正V,使用East表示位置U,使用South表示負V,使用West表示負U。對這些像素進行採樣並在單獨的函數中初始化亮度數據,而後調用 在ApplyFXAA中。

(NESW十字和中間像素)



南北不該該互換嗎?

我使用的是OpenGL約定,UV座標從左到右,從下到上。FXAA算法雖然並不關心相對方向,但必須保持一致。


這些像素之間的局部對比度只是它們最高和最低亮度值之間的差別。因爲亮度在0-1範圍內定義,所以對比度也是如此。咱們在取樣後當即計算出最低,最高和對比度值。將它們添加到結構中,以便咱們稍後能夠在ApplyFXAA中訪問它們。對比度是最重要的,因此咱們看看它是什麼樣的。


(局部對比)


結果就像一個粗糙的邊緣檢測濾波器。由於對比度不關心方向,因此對比度不一樣的兩側的像素最終都具備相同的值。所以,咱們獲得的邊緣至少有兩個像素厚,由南北像素或東西向像素對造成。


3.2 跳太低對比度像素


讓咱們經過對比度閾值滑塊進行配置哪些區域不用執行抗鋸齒。原始的FXAA算法也具備此閾值,而且具備如下代碼文檔:



儘管文檔中提到它會修剪深色區域,但其實是根據對比度而不是亮度進行修剪,所以不管它是亮仍是暗。咱們將使用與文檔中指示的範圍相同的範圍,但默認使用低閾值。


(對比度閾值)


在着色器內部,若是對比度低於閾值,則只需在對鄰域採樣後返回便可。爲了使跳過的像素在視覺上顯而易見,我將它們設置爲紅色。

(紅色像素被跳過)


除了絕對對比度閾值之外,FXAA還具備相對閾值。這是它的代碼文檔:


這聽起來像咱們剛剛介紹的閾值,可是在這種狀況下,它是基於鄰域的最大亮度的。鄰域越亮,對比度就必須越高。咱們還將爲該相對閾值添加一個配置滑塊,使用指示的範圍,一樣將最小值做爲默認值。


(相對對比度閾值)


閾值是相對的,由於它由對比度縮放得出來的。使用該值而不是先前的閾值來查看差別。此次,我用綠色表示跳過的像素。


(綠色像素被跳過)


整體而言,「Contrast Threshold」最積極地跳過像素,可是「Relative Threshold 」能夠跳過較亮區域中的較高對比度像素。例如,在下面的屏幕截圖中,我將兩種顏色都與兩種閾值進行了最大組合。黃色表示同時使用兩個標準跳過的像素。在此場景中,只有一些白色陰影區域和白色球體僅受相對閾值的影響。


(兩種閾值 最大值)


要應用兩個閾值,只需將對比度與兩個最大值進行比較便可。爲了清楚起見,將此比較放在單獨的函數中。目前,若是跳過像素,只需返回零便可將其變爲黑色。


(對比度,跳過0像素)


3.3 計算混合因子


如今咱們有了所需像素的對比度值,能夠繼續肯定混合因子。爲此建立一個單獨的函數,以亮度數據做爲參數,並使用該函數肯定最終結果。



咱們應該混合多少取決於中間像素及其整個鄰域之間的對比度。儘管咱們已經使用NEWS十字來肯定局部對比度,但這不足以表示鄰域。咱們還須要四個對角鄰居。所以,將它們添加到亮度數據。即便咱們最終可能會跳過像素,也能夠在SampleLuminanceNeighborhood和其餘鄰居中直接對它們進行採樣。着色器編譯器負責優化代碼,所以僅在須要時才進行額外採樣。

(所有鄰居)


如今咱們能夠肯定全部相鄰鄰居的平均亮度。可是,因爲對角鄰點在空間上距離中間較遠,所以它們的做用應該較小。咱們經過將NESW鄰居的權重加倍,將總數除以12而不是8來將其計入平均值。結果相似於賬篷(Tent)濾波器,並充當low-pass濾波器。

(鄰居權重)


(高對比度區域上的低通濾波器)


接下來,經過它們的絕對差找到中間值與該平均值之間的對比度。結果如今變成了high-pass濾波器。


(高通濾波器)


接下來,經過除法相對於NESW十字的對比度對濾鏡進行歸一化。將結果限制爲最大值1,由於濾鏡覆蓋的像素比十字形要多,咱們可能會獲得更大的值。


(歸一化後的濾波器)


結果是用做混合因子的過渡至關艱難。使用smoothstep函數對其進行平滑處理,而後對結果求平方以使其變慢。


(線性 VS 平方 平滑步長)


(混合因子)


3.4 混合方向


如今咱們有了混合因子,下一步就是肯定要混合的兩個像素。FXAA將中間像素與NESW十字中的一個相鄰像素混合。選擇這四個像素中的哪一個取決於對比度梯度的方向。在最簡單的狀況下,中間像素觸及兩個對比區域之間的水平或垂直邊緣。在水平邊緣的狀況下,它應該是北鄰仍是南鄰,這取決於中間是在邊緣之下仍是之上。不然,它應該是東邊的鄰居或者西邊的鄰居,這取決於中間位置是在邊緣的左側仍是右側。

(混合方向。紅色表明亮度差別,深色或淺色)


邊緣一般不是徹底水平或垂直,可是咱們會選擇最佳近似值。爲了肯定這一點,咱們比較鄰域中的水平和垂直對比度。當存在水平邊緣時,在中間上方或下方會產生強烈的垂直對比度。咱們經過加北和南,減去中間兩次並取其絕對值來進行測量,所以| n + s-2m |。相同的邏輯適用於垂直邊緣,但使用東西向代替。


這僅給咱們指示了NESW十字內部的垂直對比度。咱們也能夠經過包括對角線鄰居來提升邊緣方向檢測的質量。對於水平邊緣,咱們對東邊的三個像素和西邊的三個像素執行相同的計算,將結果相加。一樣,這些附加值離中間值很遠,所以咱們將它們的相對重要性減半。得出最終公式 2 | n + s-2m | + | ne + se-2e | + | nw + sw-2w | 用於水平邊緣對比度,而用於垂直邊緣對比度。咱們不須要對結果進行歸一化,由於咱們只關心哪一個更大,它們都使用相同的比例。


若是水平邊緣的對比度大於或等於垂直邊緣,則咱們有一個水平邊緣。建立一個結構以保存此邊緣數據,並將其計算結果放在單獨的函數中。而後讓ApplyFXAA調用它。這使咱們能夠可視化檢測到的邊緣方向,例如經過將水平邊緣設爲紅色。

(紅色像素在水平邊緣)


知道邊緣方向能夠告訴咱們須要混合的尺寸。若是它是水平的,那麼咱們將在邊緣垂直融合。到UV空間中下一個像素的距離取決於紋理像素大小,而且取決於混合方向。所以,咱們也將此步長添加到邊緣數據中。


接下來,咱們必須肯定是否應在正向或負向融合。經過在適當尺寸的中間任一側比較對比度(亮度梯度)來作到這一點。若是咱們有一條水平邊,那麼北是正鄰,南是負鄰。若是咱們有一條垂直邊,那麼東是正鄰,西是負鄰。



比較漸變。若是正面具備最高的對比度,則可使用適當的紋理像素大小不變。不然,咱們必須朝相反的方向走,須要取反。



爲了實現這一目標,我將全部帶有負步長的像素設爲紅色。由於像素應該在邊緣上融合,因此這意味着邊緣右側或頂部的全部像素都變爲紅色。


(紅色像素沿負方向融合)


3.5 融合


此時,咱們既有混合因子,又知道混合的方向。經過使用混合因子在適當方向上在中間像素及其相鄰像素之間線性插值,能夠得到最終結果。咱們能夠經過簡單地以等於由混合因子縮放的像素步長的偏移量採樣圖像來實現此目的。另外,若是咱們決定不混合原始像素,請確保返回原始像素。我將原始亮度保留在Alpha通道中,以防你想將其用於其餘用途,但這不是必需的。



請注意,最終樣本最終會在四個可能的方向上發生偏移,而且距離可變,而每一個像素之間的距離可能會有很大差別。這弄亂了各向異性紋理過濾和mipmap選擇。雖然咱們不使用mipmaps做爲臨時紋理,而且一般也沒有其餘後效果來作這個,但咱們也沒有明確禁用各向異性過濾,所以可能會使最終樣本走樣失真。爲確保不該用大量透視過濾,請使用tex2Dlod並不使用Sample調整進行紋理訪問,取代使用tex2D。

(有和沒有混合)


結果是使用FXAA子像素混合的抗鋸齒圖像。它會影響高對比度邊緣,但也會影響紋理中的許多低對比度細節。雖然這有助於減輕螢火蟲的影響,但模糊程度過多。該效果的強度能夠經過0-1範圍因子進行調整,以調製最終偏移。原始的FXAA實現也容許這樣作,並帶有如下代碼文檔:


爲子像素融合添加一個滑塊以達到咱們的效果。咱們將使用全強度做爲默認值,Unity的後期效果堆棧v2也將使用此默認值,而且它不容許你進行調整。

(子像素融合滑動條)


使用_SubpixelBlending調製混合因子,而後再將其返回到DecisionPixelBlendFactor中。如今,咱們能夠控制FXAA效果的強度。



(調整混合數量)


4 沿着邊混合


因爲像素混合因子是在3×3塊內肯定的,所以只能平滑該比例的特徵。可是邊緣能夠比這更長。像素可能會終止於成角度的邊樓梯的長臺階上的某個位置。雖然局部邊緣是水平或者垂直的,但真正的邊是帶有角度的。若是咱們知道此真實邊,則能夠更好地匹配相鄰像素的混合因子,從而在整個邊上進行平滑。

(沒有,當前以及所需的邊融合)


4.1 邊亮度


爲了弄清楚咱們正在處理哪一種邊,必需要追蹤更多信息。咱們知道3×3塊的中間像素在邊的一側,其餘像素之一在另外一側。爲了進一步識別邊,咱們須要知道其梯度,即其任一側區域之間的對比度差別。咱們已經在DefineEdge中弄清楚了這一點。讓咱們跟蹤此漸變以及另外一側的亮度。



咱們將使用一個單獨的函數來肯定邊的新混合因子。如今,在肯定邊後當即返回它,跳過着色器的其他部分。還將跳過的像素設置回零。首先,咱們將輸出邊緣漸變。


(邊漸變)


4.2 沿着邊走


咱們必須找出沿水平或垂直邊緣段的像素的相對位置。爲此,咱們將向兩個方向沿着邊走,直到找到終點爲止。能夠經過沿邊採樣像素對並檢查其對比度梯度是否仍與原始邊緣的對比度梯度想匹配來實現。

(搜索邊緣的末端)


可是咱們實際上並不須要每一個步驟都對兩個像素進行採樣。能夠用它們之間的單個樣原本作。這給了咱們剛好在邊緣的像素的平均亮度,咱們能夠將其與第一個邊緣交叉進行比較。

(搜索時紋理樣本(黃色)和3×3樣本(黑色))


所以,咱們首先肯定邊緣上的UV座標,該距離距離原始UV座標只有半個步長。

接下來,沿着邊緣的單個步驟的UV偏移量取決於其方向。它能夠是水平的或垂直的。

咱們將經過比較沿邊行走時採樣的亮度與原始邊緣位置的亮度(這是咱們已經擁有的亮度對的平均值)來找到終點。若是發現的亮度與原始亮度足夠類似,那麼仍然處於邊緣,必須繼續前進。若是相差太大,說明已經到了盡頭。


咱們將經過沿邊緣獲取亮度增量(採樣亮度減去原始邊緣亮度)並檢查其是否知足閾值來執行此比較。做爲閾值,FXAA使用原始梯度的四分之一。在正方向上執行此步驟,明確追蹤亮度增量以及是否到達邊緣末端。經過顯示白色展現哪些像素與其正邊緣相鄰,其餘則爲黑色。


(一步步走向正端點)


咱們能夠看到,孤立的像素如今大部分都是白色的,可是沿較長角度線的一些像素仍然是黑色的。它們距離局部水平或垂直邊緣的正端點比一步還遠。對於這些像素,必須繼續沿着邊緣走。所以,請在第一個搜索步驟以後添加一個循環,最多執行九次,每一個像素最多能夠執行十個步長。


(升級到10個步長)


如今,咱們能夠找到距十個像素遠的正端點,而且在示例屏幕快照中幾乎全部像素都變爲白色。經過獲取相關的UV增量並將其放大10倍來可視化UV空間到端點的距離。

(正端距離,最多十個像素)


4.3 沿着兩個方向


沿邊緣還有一個負方向的端點,所以也請使用示例方法搜索該端點。而後,最終距離變爲正距離和負距離中的最短距離。

(到最近邊緣的距離)


4.4 肯定混合因子


如今,咱們知道到邊緣最近端點的距離(若是它在範圍內),能夠用來肯定混合因子。咱們將在距離終點越近的地方融合更多,從而使階梯變得平滑。可是,咱們只會在邊緣向包含中間像素的區域傾斜的方向上執行此操做。經過比較沿邊緣的亮度增量和沿邊緣的亮度增量的符號來發現這一點。

(選擇正確的一面)


若是增量方向相反,那麼咱們會遠離邊,應使用零混合因子來跳過混合。這樣能夠確保咱們僅在緣的一側混合像素。

(只有正確的一邊纔有像素)


若是咱們有一個有效的像素可用於混合,則咱們將混合0.5倍減去到沿邊緣到最近端點的相對距離。這意味着咱們越靠近端點就進行更多的混合,而根本不會在邊緣的中間進行混合。

(邊緣融合因子)


要了解經過此方法發現了哪些邊緣(僅考慮3×3區域時會丟失的),請從邊混合因子中減去像素混合因子。


(經過邊因子添加混合)


FXAA的最終混合因子只是兩個混合因子的最大值。所以,它始終使用邊緣混合因子,而且能夠經過滑塊控制像素混合因子的強度。


(邊混合VS原始圖像)

(帶有子像素混合 0 vs 1)


4.5 質量


如今,咱們老是在搜索多達十次迭代以找到邊的終點。這在許多狀況下就足夠了,但對於階梯步長超過十個像素寬的那些邊緣來講,還不夠。若是最終找不到邊緣,那麼終點在更遠的地方。若是不收集更多樣本,咱們所能作的就是猜想末端到底有多遠。這至少要相距一步,所以在這種狀況下,咱們能夠再增長一次UV偏移。會永遠更加準確。


除此以外,咱們能夠改變咱們要採起的步驟。還能夠更改步長大小,以精確度爲代價跳過像素來檢測更長的邊。也能夠不使用恆定的步長,經過在數組中定義步長來增長步長。最後,咱們能夠調整用於猜想太大距離的偏移量。讓咱們使用宏定義這些設置,以使着色器變體可以支持。


原始FXAA算法包含質量定義列表。Unity的後期效果堆棧v2使用默認質量等級28。它的步數爲10,第二步爲1.5,而不是1,全部後續步數爲2,最後一步爲4,若是還不夠找到終點,最後的猜想會加上8。


經過一次包含一個半像素偏移,咱們最終從那開始在相鄰像素對之間進行採樣,一次處理四個像素而不是兩個像素的平均值。這不是很準確,可是可使用步長2而不跳過像素。

(邊緣融合因子,可實現28像素質量步距與單像素圖像步距)


與使用單像素搜索步驟相比,混合因子可能會更阻塞而且質量有所降低,可是做爲回報,短邊須要較少的搜索迭代,而長邊也能夠被檢測到。


(邊緣融合的結果是質量爲28仍是單像素爲步長)



在FXAA通道中添加一個多編譯選項。



和一個Toggle來控制它。


(低質量,僅邊緣融合)


4.6 性能


最後,讓咱們考慮一下性能。循環並不理想。原始的FXAA代碼顯式展開了循環,其中包含一長串嵌套的if語句。幸運的是,咱們不須要這樣作。咱們能夠簡單地經過UNITY_UNROLL屬性告訴着色器編譯器爲咱們執行此操做。就我所知,展開循環能夠顯着提升性能,即便沒有這種優化,FXAA仍然很是快。



除此以外,原始的FXAA代碼還將兩個循環組合爲一個循環,並在兩個方向上以鎖步方式進行搜索。每次迭代,只有還沒有完成的方向前進並再次採樣。在某些狀況下,這可能會更快,但在咱們的例子裏,單獨的循環要比融合的循環執行得更好。


若是檢查原始的FXAA代碼(版本3.11),你會發現它主要由積極的低級優化主導。除了使代碼難以閱讀以外,這些優化現在可能再也不有意義。在咱們的案例中,着色器編譯器實際上爲咱們處理了全部此類優化,這比咱們本身能作的更好。激進的手動優化可能會使狀況更糟。


Unity的後期效果堆棧v2幾乎逐字的使用了FXAA 3.11代碼,但就咱們的例子來講,本教程中介紹的更清晰的版本實際上表現更好。與之前同樣,若是要得到絕對最佳的性能,請針對每一個項目,每一個目標平臺本身進行測試。


5 顏色空間


在此測試場景中,咱們在線性空間中使用HDR照明,並將渲染的圖像直接饋送到FXAA,而無需進行任何顏色調整。這可能不會產生最佳結果。


5.1 LDR

儘管咱們在計算用於肯定混合因子的亮度時已經對顏色進行了鉗位,可是實際上並無對在混合時所使用的RGB通道進行鉗位。這意味着咱們最終能夠混合HDR顏色。當HDR顏色成分混合在一塊兒時,咱們看不到差別,由於結果仍然超出最終顯示範圍。這不是問題,由於替代方案將是1到1之間的混合。可是,當混合LDR和HDR數據時,這就會成爲問題。若是HDR組件很是亮,則結果也將被拉入HDR。這會將本來的LDR像素帶入HDR範圍,可能會增長鋸齒而不是減小鋸齒。


提供LDR數據是提供FXAA通道輸入者的責任。在咱們的例子裏,在使用亮度通道時能夠完成這一點。

(LDR VS HDR 混合)


5.2 Gamma


着色時使用線性空間,由於它能夠產生物理上正確的照明。可是,FXAA是關於感知的,而不是物理的。如原始FXAA代碼中所述,與在γ空間中進行混合相比,在線性空間中進行混合會產生視覺上較差的結果。Unity的後期效果堆棧v2僅在線性空間中混合,所以結果還不錯。可是咱們能夠支持這兩種方法。


當咱們假設是線性空間渲染時,請爲咱們的效果添加一個伽瑪空間混合切換開關,該開關可控制shader關鍵字。

若是須要,在亮度Pass中,將顏色轉換爲伽馬空間,而後再輸出。若是跳過此通道的話,則誰提供FXAA輸入,誰來確保顏色在伽瑪空間中。

在FXAA通道結束時,一旦得到了最終樣本,則在適當的狀況下將其轉換回線性空間。咱們必須這樣作,由於渲染管線假定輸出在線性空間中。

(Gamma VS Linear 混合)


最後,咱們能夠按照設計的默認設置重現FXAA效果。這些設置爲「Contrast Threshold 0.0833」,「Relative Threshold 0.166」,「Subpixel Blending 0.75」,而且同時啓用了「Low Quality」和「Gamma Blending」。

(具備原始默認設置的FXAA VS 鋸齒化的圖像)


固然,這只是配置FXAA的許多方法之一。你能夠根據須要進行調整。你可能會決定僅適用邊混合,並徹底不須要任何子像素混合。在這種狀況下,您能夠經過刪除DefinePixelBlendFactor 函數(多是着色器變體)來加快效果。並且,當在遊戲中提供FXAA選項時,你能夠將其某些設置公開給播放器,而不只僅是FXAA開關。



本文分享自微信公衆號 - 壹種念頭(OneDay1Idea)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索