學習筆記——Bloom
1.應用場景
顯示器能產(chǎn)生的光量是有限的。它可以從黑色到全亮態(tài),這在著色器(shader)中對應RGB數(shù)值的0和1.這就是光的低動態(tài)范圍(LDR)。全白像素的亮度應顯示器而異,可以根據(jù)使用情況做調整,當時他不會過曝到閃瞎我們的狗眼。
真實世界是不會局限于LDR光照的。它沒有最大的亮度。同一時間匯集的光子越多,物體就會越亮,直到讓我們眼睛有痛覺或閃瞎我們狗眼。
為了表達出更明亮的顏色,我們可以超越LDR進入高動態(tài)范圍(HDR)。這僅僅意味著我們不局限于最大值為1。shader使用HDR顏色沒有問題,只要輸入和輸出的格式可以存儲大于1的值。但是顯示器不能超過他們的最大亮度,所以最終的顏色仍然被固定在LDR上。
為了使HDR顏色可見,它們必須映射到LDR上,這被稱為色調映射(tonemapping)。這可以使圖像非線性變暗,因此可以區(qū)分最初的HDR顏色。這有點類似于我們的眼睛如何應對并處理高亮的場景,盡管tonemapping是恒定的。還有動態(tài)曝光技術??梢詣討B(tài)調整圖像亮度。兩者都是可以一起使用的。但是我們的眼睛并不是總能完美適應。有些場景就是單純的太亮了,讓我們無法看清。我們如何能夠在LDR顯示器上展示這種效果呢?
這個時候Bloom就出現(xiàn)了,bloom是一種通過使像素的顏色滲透到相鄰像素來擾亂圖像的效果。這就像模糊圖像一樣,但是基于亮度。這樣我們就可以通過模糊來傳達過于鮮艷的顏色。這有點類似于光在我們眼睛里的擴散方式,在高亮度的情況下會變得明顯,但這主要是一種真實的效果。
許多人不喜歡bloom效果,因為它破壞了原本清晰的圖像,是物體看起來不像真實的發(fā)光。這并不是bloom的固有缺陷,只是它經(jīng)常被使用而已。如果你是寫實主義,在合理的情況下適度使用bloom即可。bloom也可以用于藝術上的非寫實效果,例如夢境序列幀,表示眩暈場景,或者場景過渡等。
1.1.Bloom場景
我將通過相機后期效果組件創(chuàng)建我們自己的bloom效果,創(chuàng)建一個默認照明的新場景。在里面放一堆明亮的物體,在黑暗的背景上。我用了一個黑色的平面,上面有一堆白色、黃色、綠色和紅色的立方體和不同大小的球體。確保相機啟用了HDR。還要將項目設置為使用線性色彩空間,這樣我們才能最好地看到效果。

通常,你會將色調映射應用到線性和HDR渲染的場景中。你可以先進行自動曝光,然后應用bloom,然后執(zhí)行最終的色調映射。但在本案例中,我將專注于bloom,不會應用任何其他效果。這意味著所有超出LDR的顏色都將在最終圖像中被約束。
1.2Bloom影響
創(chuàng)建一個新的BloomEffect組件,讓它在編輯模式下執(zhí)行,并給它一個OnRenderImage方法。最初它不做任何額外的事情,所以只是從源渲染紋理到目標渲染紋理。

Blit(幀緩沖區(qū)位塊傳送):將一個平面的一部分或全部圖象整塊從這個平面復制到另一個平面
ExecuteInEditMode:在編輯模式即可預覽awake和update等,無需在play模式下
ImageEffectAllowedInSceneView:可在scene窗口下看圖像特效等。
讓我們也把這個效果應用到場景視圖中,這樣就更容易從不同的角度看到效果。這是通過將ImageEffectAllowedInSceneView屬性添加到類中來實現(xiàn)的。
2.模糊處理
bloom效果是通過獲取原始圖像,以某種方式模糊它,然后將結果與原始圖像結合來創(chuàng)建的。因此,要創(chuàng)造bloom,我們必須首先能夠模糊圖像。
2.1渲染到另一個紋理
應用效果是通過從一個渲染紋理到另一個渲染紋理來完成的。如果我們在要給pass中完成所有工作,那么我們可以使用適當?shù)闹骱唵蔚貜脑吹侥康牡剡M行blit。但是模糊化需要大量的工作。所以讓我介紹一個中間步驟。我們首先從源紋理到臨時紋理,然后從該紋理到最終目標。
獲得一個臨時渲染紋理最好是通過調用RenderTexture.GetTemporary來完成。這個方法負責為我們管理臨時紋理,創(chuàng)建,緩存和銷毀它們,因為Unity更適合這些。至少,我們必須指定紋理的尺寸。我們將從與源紋理相同的大小開始。

當我們要模糊圖像時,我們不會對深度緩沖區(qū)做任何事情。為此,使用0作為第三個參數(shù)。

因為使用的是HDR,所以必須使用適當?shù)募y理格式。由于相機應該啟用HDR,源紋理的格式將是正確的,所以可以使用它。它很可能是ARGBHalf,但也可能使用另一種格式。

不是直接從初始幀到目標幀,而是先從初始幀到臨時紋理,然后再從臨時紋理到目標幀。

之后,我們不再需要臨時紋理。為了使它可以重用,可以調用RenderTexture.ReleaseTemporary來釋放它。

雖然結果看起來仍然相同,但我們現(xiàn)在正在通過一個臨時紋理傳遞它。
2.2微減像素采樣(Downsampling)
模糊圖像是通過平均像素來完成的。對于每個像素,我們必須決定一堆附近的像素來組合。包含哪些像素定義用于效果的過濾器內核。可以通過只平均幾個像素來實現(xiàn)一點模糊,這意味著一個小內核。大量的模糊將需要一個大的內核,并結合許多像素。
內核中像素越多,我們對輸入紋理采樣的次數(shù)就越多。由于這是每個像素,一個大的內核可能需要大量的采樣工作。所以讓我們盡可能的簡單。
求平均像素的最簡單和最快的方法是利用GPU內置的雙線性過濾。如果我們將臨時紋理的分辨率減半,那么每組4個源像素就有一個像素。分辨率較低的像素將在原始四個像素之間進行采樣,因此我們最終得到它們的平均值。我們甚至不需要使用自定義著色器。


使用一半大小的中間紋理意味著我們將源紋理采樣到一半分辨率。在這一步之后,我們從臨時紋理到目標紋理,從而再次放大采樣到原始分辨率。

這是一個兩步模糊處理過程,每個像素都與它周圍的4×4像素塊混合在一起,有四種可能的配置。

結果得到的圖像比原始圖像更厚實,也更模糊。

我們可以通過進一步減小中間步驟的大小來增加效果。

2.3漸進降采樣
不幸的是,直接下采樣到低分辨率會導致較差的結果。我們通常會丟棄像素,只保留四個像素的孤立組的平均值。

更好的方法是多次降低采樣,每一步將分辨率減半,直到達到所需的水平。這樣一來,所有像素最終都會對最終結果做出貢獻。

為了控制這樣做的次數(shù),可以添加一個公共迭代字段。將其設置為范圍為1-16的滑塊。這將允許我們將65536^2(2的16次冪)的紋理一直壓縮到一個像素,這應該足夠了。


要做到這一點,首先將r重命名為currentDestination。在第一個blit之后,添加一個顯式的currentSource變量并將currentDestination分配給它,然后將其用于最后一個blit并釋放它。

現(xiàn)在我們可以在當前源的聲明和最后的blit之間放入一個循環(huán)。由于它位于第一個downsample之后,它的迭代器應該從1開始。每一步,從紋理大小再次減半開始。然后抓取一個新的臨時紋理和blit當前源到它。然后釋放當前源,并將當前目標作為新源。

這是有效的,除非我們最終有太多的迭代,將大小減小到零。為了防止這種情況發(fā)生,在這種情況發(fā)生之前跳出循環(huán)。典型顯示器的高度通常小于它的寬度,因此可以僅以高度為基礎。因為單像素線實際上添加的內容并不多,所以當紋理高度下降到2以下時,我已經(jīng)中止了。


2.4漸進升采樣
雖然漸進下采樣是一種改進,但結果仍然會變得太塊狀、太快。讓我們看看如果我們也逐步向上抽樣是否會有幫助。
在兩個方向上迭代意味著我們最終要對每個大小渲染兩次,除了最小的大小。在每次渲染中,我們不再釋放和聲明兩次相同的紋理,而是在一個數(shù)組中跟蹤它們。我們可以簡單地使用一個固定大小為16的數(shù)組字段,這應該足夠了。

每次我們抓取一個臨時紋理,也將它添加到數(shù)組中。

然后在第一個循環(huán)之后添加第二個循環(huán)。這是從最底層開始的。我們可以從第一個循環(huán)中取出迭代器,減去2,并將其作為另一個循環(huán)的起點。第二個循環(huán)返回,將迭代器一直減小到0。這是我們應該釋放舊的源紋理的地方,而不是在第一個循環(huán)中。同樣,我們也來清理一下這個數(shù)組。


結果好了很多,但仍然不夠好。
2.5自定義著色器
為了改善模糊,必須切換到比簡單的雙線性濾波更高級的濾波核。這需要使用一個自定義著色器,所以創(chuàng)建一個新的Bloom著色器。就像DeferredFog著色器一樣,從一個簡單的著色器開始,它有_MainTex屬性,沒有剔除,也不使用深度緩沖區(qū)。給它一個帶有頂點和片段程序的單pass。

頂點程序甚至比霧效果的程序更簡單。它只需要轉換頂點位置來剪輯空間,并通過全屏四方的紋理坐標。因為我們將以多次傳遞結束,所以除了片段程序之外的所有內容都可以在CGINCLUDE塊中共享和定義。

我們將在傳遞本身中定義FragmentProgram函數(shù)。最初,簡單地采樣源紋理并將其作為結果使用,使其變?yōu)榧t色以驗證我們正在使用自定義著色器。通常HDR顏色以半精度格式存儲,所以讓我們顯式地使用half而不是float,即使這對非移動平臺沒有區(qū)別。

為我們的效果添加一個公共字段來保存對這個著色器的引用,并在檢查器中連接它。

添加一個字段來保存將使用這個著色器的材料,它不需要序列化。在渲染之前,檢查我們是否有這個材料,如果沒有創(chuàng)建它。我們不需要在層次結構中看到它,也不需要保存它,因此相應地設置它的hideFlags。

每次我們blit,它應該用這個材料而不是默認。


2.6閥箱取樣(Box Sampling)
我們將調整我們的著色器,使它使用不同的采樣方法,雙線性濾波。因為采樣取決于像素大小,所以將神奇的float4 _MainTex_TexelSize變量添加到CGINCLUDE塊中。請記住,這對應于源紋理的texel大小,而不是目標紋理。

由于我們總是對主紋理進行采樣,只關心RGB通道,讓我們創(chuàng)建一個方便的最小Sample函數(shù)。

我們不再僅僅依賴于雙線性濾波器,而是使用一個簡單的盒形濾波器內核。它需要四個樣本而不是一個,對角線定位,所以我們得到四個相鄰的2×2像素塊的平均值。將這些樣本相加并除以4,所以我們最終得到4×4像素塊的平均值,使我們的內核大小翻倍。


在我們的片段程序中使用這個采樣函數(shù)。


2.7不同的pass
結果更加流暢,質量也更高,但也更加模糊。這主要是由于新的4×4框過濾器的上采樣。當我們使用源的texel大小來定位樣本點時,我們最終覆蓋了一個很大的區(qū)域,具有不聚焦的規(guī)則權重分布。

我們可以通過調整我們用來選擇樣本點的UV來調整我們的盒子過濾器。為了實現(xiàn)這一點,將delta轉換為參數(shù),而不是總是使用1。

復制我們的著色器通道,所以我們最終有兩個。第一個-傳遞0 -將用于下采樣,因此它應該使用原始的delta(1)。第二步是上采樣,我們將使用0.5的增量。

當UV 降為為0.5時,我們最終覆蓋了一個3×3盒子,其中有重疊的樣本。因此,一些像素對結果的貢獻不止一次,增加了它們的權重。中間像素在所有樣本中都涉及,對角線像素只使用一次,而其他像素出現(xiàn)兩次。結果是一個更集中的上采樣內核。

接下來,我們必須指出在分位時應該使用哪個通道。為了簡化這個操作,在BloomEffect中添加常量,這樣我們就可以使用名稱而不是索引了。

前兩次是向下傳遞,其他兩次是向上傳遞。

在這一點上,我們有一個相當簡單但體面的模糊過程。我們可以使用許多不同的內核來代替這些簡單的過濾器內核,每種內核都有自己的優(yōu)點——比如更好的時間穩(wěn)定性——和成本。然而,在本教程中,我們將繼續(xù)使用這些方法。

3.創(chuàng)建Bloom
模糊原始圖像是創(chuàng)建bloom的第一步。第二步是將模糊圖像與原始圖像結合,使其變亮。然而,我們不會只使用最終的模糊結果,因為這會產(chǎn)生相當均勻的模糊。相反,較低的模糊量應該比較高的模糊量對結果的貢獻更大。我們可以通過積累中間結果來做到這一點,并在上抽樣時添加到舊數(shù)據(jù)中。

3.1疊加式混合
添加到我們已經(jīng)在某些中間級別可以通過使用添加混合來完成,而不是替換紋理的內容。我們所要做的就是將上采樣通道的混合模式設置為one one。

這種簡單的方法適用于所有中間通道,但是當我們渲染到最終目的地時就會出錯,因為我們還沒有渲染到最終目的地。我們最終可能會在每一幀中積累光線,放大圖像或其他東西,這取決于Unity如何重用紋理。為了解決這個問題,我們必須為最后一個上樣例創(chuàng)建一個單獨的通道,在那里我們將原始的源紋理與最后一個中間紋理結合起來。所以我們需要一個著色器變量的來源。

添加第三個通道,它是第二個通道的副本,除了它使用默認的混合模式,并將盒子樣本添加到源紋理的樣本中。

為這一過程定義一個常數(shù),它將Bloon應用于原始圖像。

最后一個blit必須使用這個通道,使用正確的源紋理。

3.2Bloom閾值
現(xiàn)在我們還在模糊整個圖像。對于明亮的像素來說,這是最明顯的。但是bloom的一個用途是只應用在非常亮的像素上。為了實現(xiàn)這一點,我們必須引入亮度閾值。為此添加一個公共字段,滑動條的范圍從0到一些非常亮的值,比如10。讓我們使用默認閾值1,不包括LDR像素。


閾值決定了哪些像素有助于Bloom效果。如果它們不夠亮,就不應該包括在下采樣和上采樣過程中。簡單地將它們轉換為黑色就可以做到這一點,這必須由著色器完成。所以在我們blit之前設置材質的_Threshold變量。


我們將使用閾值過濾掉我們不希望包含的像素。當我們在模糊過程開始時這樣做時,它被稱為預濾鏡步驟。為此創(chuàng)建一個函數(shù),該函數(shù)接受顏色并輸出過濾后的顏色。



為了避免在著色器中除以零,確保除數(shù)的最小值很小,比如0.00001。然后使用結果來調節(jié)顏色。

所以復制第一次傳遞,把它放在頂部作為傳遞0。將過濾器應用于盒樣的結果。

為這個新的傳遞添加一個常數(shù),并將所有后續(xù)傳遞的索引增加一個。

對第一個 blit 使用新的 pass。

此時,將閾值設置為 1,假設使用的光線和材質沒有 HDR 值,您可能看不到或幾乎看不到高光溢出。要使光暈出現(xiàn),您可以增加某些材質的光貢獻。例如,我將黃色材質設為自發(fā)光,它與反射光一起將黃色像素推入 HDR。

3.3Isolating Bloom
為了更好地了解圖像的哪些部分導致了泛光,如果我們能夠單獨查看模糊效果,那將會很方便。因此,讓我們?yōu)槲覀兊男Ч砑右粋€調試選項,通過一個公共布爾字段進行控制。


我們將為調試目的創(chuàng)建一個單獨的通道,因此在底部為其添加一個常量。

在調試模式下,使用調試通道將最后的中間結果直接 blit 到最終目的地,而不是將其添加到源。

新的調試通道簡單地執(zhí)行最后的上采樣并將其與任何東西結合起來。


在調試模式下,我們可以清楚地看到黃色像素最終產(chǎn)生了光暈。除此之外,還包括一些白色像素,但前提是它們最終反射了大量的定向光。
3.4Soft Threshold
我們用來調制顏色的拐點曲線以一個角度切入零,導致一個突然的截止點。這就是為什么它也被稱為硬膝。這意味著我們最終可以在產(chǎn)生開花的區(qū)域和不產(chǎn)生開花的區(qū)域之間形成急劇的過渡。這可以在上面屏幕截圖中的大白色球體中看到。該領域有一個明確定義的部分被包括在內。這在某種程度上被模糊所混淆,但它仍然是一個嚴酷的過渡。
可以使這種過渡更平滑,從零貢獻到完全貢獻。我們將使用滑塊來控制它。在 0 處,我們得到了當前的嚴酷過渡。在 1 處,我們得到一個軟閾值,該閾值從亮度 0 一直平滑地淡化光暈,直到它與硬拐點匹配。我們將使用 0.5 作為默認值。


這種淡化也是由著色器完成的,因此將軟閾值因子傳遞給材質。

并將它的變量添加到著色器中。




請注意,軟拐點功能的某些部分可以被隔離,因此它們僅取決于配置值,每次通過都是恒定的。我們可以預先計算這些部分并將它們以向量的形式傳遞給著色器,從而減少它必須做的工作量。我們可以將這些與閾值組合在一個過濾器向量中。


3.5Bloom Intensity
最后,讓我們可以調節(jié)光暈效果的強度。這使得淡入淡出成為可能,并且還可以創(chuàng)建非常強烈的效果。為此添加一個滑塊,范圍為 0-10。默認值應為 1。


將此強度值作為材質屬性傳遞給著色器。由于通常使用伽馬空間中的一個因子來設置光暈的強度,因此將其從伽馬空間轉換為線性空間。

將適當?shù)淖兞刻砑拥街鳌?/span>

在最后兩次傳遞中將強度計入最終的盒子樣本。




你現(xiàn)在有了一個基本的綻放效果。它與 Unity 的后期處理堆棧版本 2 的綻放效果非常相似。可以通過添加色調、使采樣增量可配置、使用不同的過濾器等進一步擴展它?;蛘吣梢赞D到景深。
本文引用:https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/