extjs 渲染之前的方法_Unity通用渲染管线(URP)系列(十一)——后处理(Bloom)...
200+篇教程總入口,歡迎收藏:
放牛的星星:[教程匯總+持續更新]Unity從入門到入墳——收藏這一篇就夠了?zhuanlan.zhihu.com本文重點內容:1、創建簡單的post-FX棧
2、修改渲染后的圖像
3、需要的時候完成后處理的呈現
4、制作Bloom的效果
這是關于創建自定義腳本渲染管道的教程系列的第11部分。它增加了對后處理的支持,目前只支持bloom。
本教程是CatLikeCoding系列的一部分,原文地址見文章底部。本教程使用Unity 2019.4.4f1制作。
發光吧!1 Post-FX Stack
大多數情況下,渲染的圖像不會按原樣顯示。圖像經過了后期處理,并獲得了各種效果(簡稱FX)。常見的FX包括光暈,顏色分級,景深,運動模糊和色調映射。這些FX作為堆棧應用,有指定的順序,一個在另一個之上。在本教程中,我們將創建一個簡單的post-FX棧,該棧最初僅支持Bloom。
1.1 設置資產
一個項目可能需要多個post-FX棧配置,因此我們首先創建一個PostFXSettings資產類型來存儲一個棧的設置。
在本教程中,我們將使用單個棧,方法是在RP上通過向CustomRenderPipelineAsset添加配置選項將其提供給RP,然后將其傳遞給RP的構造函數。
然后,CustomRenderPipeline必須追蹤FX設置,并將它們與其他設置一起在渲染過程中傳遞給相機渲染器。
CameraRenderer.Render最初對設置不執行任何操作,因為我們還沒有棧。
現在我們可以創建一個空的post-FX Setting資產,并將其分配給管道資產。
分配 post FX Setting1.2 棧對象
我們將為棧使用和Lighting、Shadows相同的方法。為它創建一個類,該類有Buffer,Context,Camera和post-FX settings,并使用公共Setup方法對其進行初始化。
接下來,添加一個公共屬性以指示棧是否處于活動狀態,只有在有設置的情況下,情況才如此。想法是,如果未提供設置,則應跳過后處理。
最后,我們需要一個公共的Render方法來渲染棧。通過使用適當的著色器簡單地繪制一個覆蓋整個圖像的矩形,即可對整個圖像應用效果。現在我們沒有著色器,因此我們只需要復制到目前為止渲染的任何內容到相機的幀緩沖區即可。這可以通過在命令緩沖區上調用Blit,并將源和目標的標識符傳遞給Blit來完成。這些標識符可以以多種格式提供。我們使用整數作為源,并為其添加一個參數,并使用BuiltinRenderTextureType.CameraTarget作為目標。然后執行并清除緩沖區。
本案例中,我們不需要手動開始和結束緩沖區樣本,因為我們可以完全替換目標位置,因此不需要調用ClearRenderTarget。
1.3 使用棧
現在,CameraRenderer需要一個棧實例,并在Render中調用它的Setup,就像它對其Lighting對象所做的一樣。
到目前為止,我們始終直接渲染到攝像機的幀緩沖區,該緩沖區既可以用于顯示,也可以用于配置的渲染紋理。我們沒有直接控制權,只能寫入它們。因此,要為活動棧提供源紋理,我們需要使用渲染紋理作為相機的中間幀緩沖區。獲取一個并將其設置為渲染目標的方法類似于陰影貼圖,只是我們將使用RenderTextureFormat.Default格式。在清除渲染目標之前執行此操作。
如果我們有一個活動棧,還可以添加一個Cleanup方法來釋放紋理。也可以將lighting的clearup移到那里。
在render的末尾,submitting之前調用clearup。如果棧處于活動狀態,則在此之前直接渲染棧。
此時,結果看起來應該沒有什么不同,但是增加了一個額外的繪制步驟,從中間幀復制到最終幀緩沖區。它在幀調試器中列為Draw Dynamic。
渲染 FX 棧1.4 強制清除
當繪制到中間幀緩沖區時,我們的渲染器會填充有任意數據的紋理。幀調試器處于活動狀態時,你可以看到此信息。Unity確保幀調試器在每個幀的開始都獲得一個清理后的幀緩沖區,但是當渲染到我們自己的紋理時,我們會避開它。通常,這會導致我們在前一幀的結果之上進行繪制,但這并不能一定保證。攝像機的清除標志設置為天空盒還是純色都沒關系,因為我們保證可以完全覆蓋以前的數據。但是其他兩個選項不起作用。為防止出現隨機結果,除非使用天空盒,否則當棧處于活動狀態時,請始終清除深度并清除顏色。
請注意,這會使得無法在不使用后FX堆棧的情況下,清除之前在另一個像機渲染結果上進行渲染。有許多解決方法,但這超出了本教程的范圍。
1.5 Gizmos
目前,我們正在同時繪制所有gizmos,但是在FX前或者后渲染的控件之間存在一些區別。因此,讓我們將DrawGizmos方法一分為二。
然后我們可以在正確的時間在Render中繪制它們。
請注意,當3D圖標用于Gizmos時,當棧處于活動狀態時,它們將不再被對象遮擋。發生這種情況是因為場景窗口依賴于我們沒有使用的原始幀緩沖區的深度數據。之后,我們將結合post FX i來介紹深度。
3D gizmos 有和沒有FX1.6 自定義畫法
我們當前使用的Blit方法會繪制一個四邊形網格(兩個三角形),該網格覆蓋了整個屏幕空間。但是我們只畫一個三角形就可以得到相同的結果,工作量少了一點。我們甚至不需要將單個三角形的網格發送到GPU,可以按程序生成它。
這有顯著的區別嗎?
這樣做的明顯好處是將頂點從六個減少到三個。但是,更重要的區別是,它消除了四邊形的兩個三角形相交處的對角線。由于GPU將片元并行地分成小塊,因此某些片元最終會沿三角形的邊緣造成浪費。由于四邊形有兩個三角形,沿對角線的片元塊將渲染兩次,因此效率低下。除此之外,渲染單個三角形可以具有更好的本地緩存一致性。
在我們的RP的Shaders文件夾中創建一個PostFXStackPasses.hlsl文件。我們將棧中的所有Pass放入其中。我在其中定義的第一件事是Varyings結構,該結構僅需要包含剪輯空間位置和屏幕空間UV坐標。
接下來,創建一個默認的頂點Pass,僅將頂點標識符作為參數。這是具有SV_VertexID語義的無符號整數uint。使用ID生成頂點位置和UV坐標。X坐標為-1,-1、3。Y坐標為-1、3,-1。要使可見的UV坐標覆蓋0–1范圍,請對U使用0、0、2,對V使用0、2、0。
覆蓋了剪輯空間的三角形添加片元Pass并進行簡單的復制,使其最初返回UV坐標以用于調試。
在同一文件夾中創建一個附帶的著色器文件。所有Pass均不使用任何剔除并忽略深度,因此我們可以將這些指令直接放在Subshader塊中。我們也總是包含Common和PostFXStackPasses文件。現在唯一的途徑就是使用我們創建的頂點和片元函數進行復制。我們還可以使用Name指令為其命名,這在將同一著色器中的多個Pass組合在一起時非常方便,因為幀調試器會將其用作遍歷標簽,而不是數字。最后,將其菜單項放在Hidden文件夾下,以便在為材質選擇著色器時不顯示該菜單項。
簡單地通過其設置將著色器手動鏈接到我們的棧上。
分配 Post FX 著色器但是渲染時我們需要材質,因此添加一個公共屬性,可以使用該屬性直接從設置資產中獲取材質。我們將根據需要創建它,并將其設置為隱藏而不保存在項目中。同樣,由于材質是按需創建的,因此無法與資產一起序列化。
由于通過名稱而不是數字來尋址Pass很方便,因此可以在PostFXStack中創建一個Pass枚舉,最初只包含Copy Pass。
現在我們可以定義自己的Draw方法。給它兩個RenderTargetIdentifier參數以指示應該從何處繪制到何處,以及一個pass參數。在其中,通過_PostFXSource紋理使源可用,像以前一樣將目標用作渲染目標,然后繪制三角形。我們通過使用未使用的矩陣,棧材質和pass作為參數調用緩沖區上的DrawProcedural來做到這一點。之后又有兩個需要解決的問題。首先是我們要繪制的形狀,即MeshTopology.Triangles。第二個是我們想要多少個頂點,單個三角形是三個。
最后,用我們自己的方法替換對Blit的調用。
1.7 不要總是應用 FX
現在,我們應該看到場景窗口中出現了屏幕空間UV坐標。游戲窗口中也可以看到。刷新一次后,還可以在材質預覽中,甚至在反射探針中看到。
反射探針 應用了FX我們的想法是將后FX應用于適當的相機,而不是其他任何東西。可以通過檢查PostFXStack.Setup中是否有Game或Scene攝像機來強制執行此操作。如果不是,我們將設置設為null,這將停用該相機的棧。
除此之外,還可以通過其工具欄中的效果下拉菜單在場景窗口中切換后處理。可以同時打開多個場景窗口,可以單獨啟用或禁用后期效果。為了支持此功能,請使用ApplySceneViewState方法為PostFXStack創建一個編輯器局部類,該方法在構建中不執行任何操作。它的編輯器版本檢查我們是否正在處理場景攝像機,如果當前繪制的場景視圖的狀態禁用了圖像效果,則禁用棧。
在Setup結束時調用此方法。
1.8 拷貝
通過使復制過程返回源顏色來完成棧。為此創建一個GetSource函數,進行采樣。我們將始終使用線性鉗位采樣器,以便我們可以明確聲明它。
我們最終將原始圖像取回來了,但是在某些情況下,通常是在場景窗口中,它是顛倒的。這取決于圖形API以及源和目標的類型。發生這種情況是因為某些圖形API的紋理V坐標從頂部開始,而另一些圖形API的紋理V坐標從底部開始。Unity通常會隱藏它,但是在涉及渲染紋理的所有情況下都不能這樣做。幸運的是,Unity指示是否需要通過_ProjectionParams向量的X分量進行手動翻轉,該向量應在UnityInput中定義。
如果值為負,我們需要翻轉DefaultPassVertex中的V坐標。
2 Bloom
Bloom 的post效果用于使物體發光。這是物理學的基礎,但是典型的Bloom效果是藝術而非現實的。不真實的發光非常明顯,也因此可以很好地證明了我們的后FX棧有效。在下一個教程中,討論HDR渲染時,我們將看到更加逼真的Bloom。現在,我們的目標是完成LDR光暈發光效果。
2.1 Bloom金字塔
Bloom表示顏色的散射,可以通過模糊圖像來完成。明亮的像素會滲入相鄰的較暗像素,因此看起來會發光。使紋理模糊的最簡單,最快的方法是將其復制到寬度和高度一半的另一個紋理中。Copy Pass的每個樣本最終在四個源像素之間進行采樣。通過雙線性濾波,可以平均2×2像素的塊。
雙線性下采樣4X4到2X2此操作執行一次只會模糊一點。因此,我們需要重復此過程,逐漸降低采樣率直至達到所需的水平,從而有效地構建紋理金字塔。
帶有4個紋理的金字塔,每級維度減半我們需要跟蹤棧中的紋理,但是有多少層取決于金字塔中有多少層,而這又取決于源圖像的大小。讓我們在PostFXStack中最多定義16個級別,這足以將65,536×65,526的紋理一直縮小到單個像素。
為了跟蹤金字塔中的紋理,我們需要紋理標識符。我們將使用屬性名稱_BloomPyramid0,_BloomPyramid1,依此類推。但是,我們不要明確地寫下所有這十六個名稱。相反,我們將在構造函數方法中獲取標識符,并且僅跟蹤第一個標識符。之所以可行,是因為Shader.PropertyToID只是簡單地按照請求新屬性名稱的順序順序分配標識符。我們只需要確保立即請求所有標識符,因為數字在應用程序會話中是固定的,無論是在編輯器中還是在構建中。
現在創建一個DoBloom方法,該方法將bloom效果應用于給定源標識符。首先將攝像機的像素寬度和高度減半,然后選擇默認的渲染紋理格式。最初,我們將從源復制到金字塔中的第一個紋理。追蹤那些標識符。
然后循環遍歷所有金字塔級別。每次迭代都首先檢查一個級別是否會退化。如果是,我們到此為止。如果未獲得新的渲染紋理,請復制到該紋理,使其成為新的源,增加目標,然后再次將尺寸減半。在循環外部聲明循環迭代器變量,稍后我們將需要它。
金字塔完成后,將最終結果復制到攝像機目標。然后遞減迭代器并向后循環,釋放我們要求的所有紋理。
現在,我們可以使用Bloom效果替換Render中的簡單Copy。
2.2 可配置的Bloom
我們現在的模糊次數太多了,但最終結果幾乎是一致的。你可以通過框架調試器檢查中間步驟。但這些步驟作為結束似乎也沒有什么問題,因此讓我們可以盡早停止。
三次迭代后的下采樣我們可以通過兩種方式做到這一點。首先,我們可以限制模糊迭代的次數。其次,我們可以將縮小比例限制設置為更高的值。通過在PostFXSettings內添加帶有它們選項的BloomSettings配置結構來支持這兩種方法。通過getter屬性使其公開可用。
默認的Bloom設置讓PostFXStack.DoBloom使用這些設置來限制自己。
限制下采樣次數,三次和5次2.3 高斯過濾
使用小型2×2濾波器進行下采樣會產生非常塊狀的結果。通過使用更大的濾波器內核(例如大約9×9高斯濾波器),可以大大提高效果。如果我們將其與雙線性下采樣相結合,我們會將其有效倍增至18×18。這就是Universal RP和HDRP發揮作用的原因。
盡管此操作混合了81個樣本,但它是可分離的,這意味著可以將其分為水平和垂直Pass,將單個行或列混合為九個樣本。因此,我們只需要采樣18次,但是每次迭代需要繪制兩次。
可分離的過濾器如何工作?
這是一個可以用對稱行向量乘以其轉置來創建的過濾器。
讓我們從水平Pass開始。在PostFXStackPasses中為其創建一個新的BloomHorizontalPassFragment函數。它累積了以當前UV坐標為中心的九個樣本行。我們還將同時下采樣,因此每個偏移步長都是源紋理像素寬度的兩倍。從左側開始的樣本權重為0.01621622、0.05405405、0.12162162、0.19459459,然后為中心的權重為0.22702703,另一側的權重相反。
這些權重從何而來?權重是從Pascal三角形得出的。對于適當的9×9高斯濾波器,我們選擇三角形的第9行,即1 8 28 56 70 56 28 81。但是,這使得在濾波器邊緣的樣本貢獻太弱而無法察覺, 因此,我們向下移至第十三行并切掉其邊緣,得出66 220 495 792 924 792 495 220220。這些數字的總和為4070,因此將每個數字除以得出最終權重。
還要為其添加一個Pass到PostFXStack著色器。我將其放在Copy Pass的上方,以使其保持字母順序。
再次以相同的順序為其添加一個條目到PostFXStack.Pass枚舉。
現在,在DoBloom中進行下采樣時,可以使用Bloom-horizontal pass。
水平高斯 3和5次限制,結果顯然是水平拉伸的,但是看起來很有希望。我們可以通過復制BloomHorizontalPassFragment,重命名并從行切換到列來創建垂直通道。我們在第一個Pass中進行了下采樣,但是這次我們保持相同的大小以完成高斯濾波,因此紋理像素大小的偏移量不應增加一倍。
也添加Pass和枚舉項。從現在開始,我將不再顯示這些步驟。
現在,我們需要在每個金字塔等級的中間增加一個步驟,為此,我們還需要保留紋理標識符。可以通過簡單地將PostFXStack構造函數中的循環限制加倍來實現。由于我們還沒有引入其他著色器屬性名稱,因此標識符將全部按順序排列,否則將需要重新啟動Unity。
現在,在DoBloom中,目標標識符必須從每個下采樣步驟開始,增加一個,然后增加兩個。然后可以在中間放置紋理。水平繪制到中間,然后垂直繪制直到達到目標。我們還需要釋放其他紋理,這是最簡單的方法,即從上一個金字塔源向后工作。
水平到下一個級別 然后進行垂直操作完全的高斯,3和5次現在,我們的下采樣濾波已經完成,并且看起來比簡單的雙線性濾波要好得多,但需要更多的紋理樣本。幸運的是,通過使用雙線性濾波以適當的偏移量在高斯采樣點之間進行采樣,我們可以減少一些采樣量。這樣可以將9個采樣減少到5個。我們可以在BloomVerticalPassFragment中使用此技巧。偏移在兩個方向上分別為3.23076923和1.38461538,權重為0.07027027和0.31621622。
我們不能在BloomHorizontalPassFragment中執行此操作,因為我們已經在該Pass中使用了雙線性過濾來進行下采樣。其九個樣本中的每個樣本平均2×2源像素。
2.4 疊加模糊
使用bloom金字塔的頂部作為最終圖像產生的統一的混合,看起來并不像任何發光的東西。我們可以通過逐步向上采樣,再向下采樣金字塔,在一張圖像中累積所有的層次來得到想要的結果。
疊加上采樣,恢復紋理我們可以使用添加混合來組合兩個圖像,但是讓我們對所有通道使用相同的混合模式,而不是添加第二個源紋理。在PostFXStack中聲明它的標識符。
然后,在完成DoBloom中的金字塔后,不再直接執行最終的Draw。相反,釋放用于上一次迭代的水平繪制的紋理,并將目標設置為用于水平繪制的紋理低一層。
當循環返回時,我們將在相反的方向上再次繪制每個迭代,并將每個級別的結果作為第二個來源。這只能發揮第一次的作用,因此我們需要提前停止一步。之后,以原始圖像作為輔助來源繪制到最終目標上。
為了使它起作用,我們需要使用第二個源可用于著色器通道。
并引入一個新的bloom組合通道,以采樣并添加兩個紋理。和以前一樣,我只展示片元程序代碼,而不顯示新的著色器通道或新的枚舉項。
上采樣時使用新的Pass。
疊加上采樣,3次和5次我們終于有了一個看起來一切都在發光的效果。但是我們的新方法只有在至少有兩次迭代的情況下才有效。如果最終只執行一次迭代,則應該跳過整個上采樣階段,而只需要釋放用于第一次水平Pass的紋理。
如果我們最終完全跳過bloom,我們就需要中止并執行一個Copy來替代。
2.5 三線性上采樣
盡管高斯濾波器會產生平滑的結果,但在上采樣時我們仍會執行雙線性濾波,這可能會使輝光看起來像塊狀。這在原始圖像中的收縮較高的地方(尤其是在運動時)尤為明顯。
黑色背景上的白色輝光顯得塊狀化我們可以通過切換到雙三次過濾來消除這些失真。對此沒有硬件支持,但是我們可以使用在Core RP Library的Filtering include文件中定義的SampleTexture2DBicubic函數。通過傳遞紋理和采樣器狀態,UV坐標以及交換了尺寸對的紋理像素尺寸矢量,使用它來創建自己的GetSourceBicubic函數。除此之外,它還具有一個用于最大紋理坐標的參數,該參數僅為1,其后是另一個未使用的參數,該參數僅為零。
在bloom-combine傳遞中使用新功能,因此我們使用雙三次濾波來上采樣。
三線性上采樣返回平滑的輝光三線性采樣產生更好的結果,但是需要四個加權的紋理樣本或一個樣本。因此,讓我們通過著色器布爾值將其設為可選。這對應于Universal RP和HDRP的High Quality光暈切換。
為其添加一個切換選項到PostFXSettings.BloomSettings。
雙線性上采樣切換在開始進行上采樣之前,將其傳遞給PostFXStack.DoBloom中的GPU。
2.6 減半分辨率
由于所有紋理采樣和繪制,Bloom可能需要大量時間才能生成。降低成本的一種簡單方法是以一半的分辨率生成它。由于效果很柔和,所以我們可以避免這種情況。這將改變效果的外觀,因為我們實際上是在跳過第一次迭代。
首先,在決定跳過bloom時,我們應該提前一步考慮。降低限制加倍為初始檢查。
其次,我們需要為將要用作新起點的一半大小的圖像聲明紋理。它不是Bloom金字塔的一部分,因此我們將為其聲明新的標識符。我們將其用于預過濾步驟,因此請適當命名。
返回DoBloom,將源復制到預過濾紋理,并將其用于金字塔的開始,同時將寬度和高度也減半。上金字塔后,我們不需要預過濾紋理,因此可以在那時釋放它。
一半分辨率的Bloom,2次和4次2.7 閾值
Bloom通常在藝術上用于僅使某些東西發光,但是我們的效果目前適用于所有對象,不管它有多亮。盡管從物理上講沒有意義,但是我們可以通過引入亮度閾值來限制影響效果的因素。
我們不能突然消除效果中的顏色,因為這會在預期會逐漸過渡的地方引入清晰的邊界。相反,我們將顏色乘以一個權重
其中b為其亮度,t 為配置閾值。我們將使用最大的顏色的RGB通道為b。當閾值為0時,結果總是1,這將保持顏色不變。隨著門檻的增加,體重曲線會向下彎曲,在b <= t 處為零。由于這條曲線的形狀,它被稱為膝蓋曲線。
閾值設置為0.25,0.5,0.75,和1該曲線在某個角度處達到零,這意味著盡管過渡過程比夾具更平滑,但仍存在一個陡峭的截止點。這就是為什么它也被稱為硬膝蓋的原因。我們可以通過改變重量來控制膝蓋的形狀
并且K就是膝蓋,設置為0~1的滑動區間。
閾值為1,膝蓋為0,,0.25,0.5,0.75,1讓我們將閾值和拐點滑塊添加到PostFXSettings.BloomSettings中。我們將配置的閾值視為伽瑪值,因為它在視覺上更直觀,因此在將其發送到GPU時,必須將其轉換為線性空間。我們將其設為開放式,即使閾值大于零將在此時消除所有顏色,因為我們僅限于LDR。
我們將通過一個名為_BloomThreshold的向量將閾值發送到GPU。在PostFXStack中為其聲明標識符。
我們可以計算權重函數的常數部分,并將其放入向量的四個分量中,以使著色器更簡單:
我們將在新的預過濾器通道中使用它,該通道將替換DoBloom中的初始復制通道,從而在將圖像大小減半的同時將閾值應用于2×2像素的平均值。
將閾值向量和一個將其應用于顏色的函數添加到PostFXShaderPasses,然后是使用它的新的Pass函數。
四次迭代 閾值和Knee為0.52.8 強度
我們通過添加強度滑塊來控制光暈的整體強度來結束本教程。我們不會給它一個限制,因此可以根據需要將整個圖像放大。
沒有上限的強度設置如果強度設置為零,我們可以跳過光暈,因此請在DoBloom開始時進行檢查。
否則,使用_BloomIntensity的新標識符將強度傳遞給GPU。我們將在合并過程中使用它來加權低分辨率圖像,因此我們不需要創建額外的Pass。對于所有繪制(將最終繪制除去到相機目標),將其設置為1。
現在,我們只需要將BloomCombinePassFragment中的低分辨率顏色乘以強度即可。
強度為0.5和5下一章,介紹HDR。
本文翻譯自 Jasper Flick的系列教程
總結
以上是生活随笔為你收集整理的extjs 渲染之前的方法_Unity通用渲染管线(URP)系列(十一)——后处理(Bloom)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 移动商城html 源码,基于weui的移
- 下一篇: 参考灵敏度_美信MAXREFDES103