URP | 后處理-SSAO效果
內(nèi)容偏多

使用版本 Unity 2021.3.15
Unity 2022.1.0
目的
學習AO相關的基礎知識,AO計算都有那些?
實現(xiàn)的方法和原理,都需要注意什么?
URP管線下獲取Depth Normals方法?

原理
AO
Ambient Occlusion(以下簡稱"AO")是一種基于全局照明中的環(huán)境光(Ambient Light)參數(shù)和環(huán)境幾何信息來計算場景中任何一點的光照強度系數(shù)的算法. AO描述了表面上的任何一點所接受到的環(huán)境光被周圍幾何體所遮蔽的百分比, 因此使得渲染的結(jié)果更加富有層次感, 對比度更高.
遮蔽(Ambient Occlusion,下面簡稱AO) 是計算機圖形學中的一種著色和渲染技術,用來計算場景中每一點是如何接受環(huán)境光的。例如,一個管道的內(nèi)部顯然比外表面更隱蔽(因此也更暗),越深入管道光線就越暗。環(huán)境光遮蔽可以被看作是光線能到達表面上每一點的能力的數(shù)值。[1]在擁有開放天空的場景中,這是通過估算每個點的可看見天空的大小來完成的;而在室內(nèi)環(huán)境中,只考慮一定范圍內(nèi)的物體,并假設墻壁是環(huán)境光源。處理結(jié)果是一個漫反射、非定向的著色效果,并不會形成明確的陰影,只是能讓靠近物體及被遮蔽的區(qū)域更暗,并影響渲染圖像的整體色調(diào)。環(huán)境光遮蔽常被用作后期處理。 與局部方法如Phong著色法不同,環(huán)境光遮蔽是一種全局方法,意味著每個點的照明是場景中其他幾何體的共同作用。然而,這只是一個非常粗略的近似全局光照。僅通過環(huán)境光遮蔽得到的物體外觀與陰天下的物體相似?!?/p>
AO可以理解為一張貼圖,是來描繪物體和物體相交或靠近的時候遮擋周圍漫反射光線的效果,可以解決或改善漏光、飄和陰影不實等問題,解決或改善場景中縫隙、褶皺與墻角、角線以及細小物體等的表現(xiàn)不清晰問題,綜合改善細節(jié)尤其是暗部陰影,增強空間的層次感、真實感,同時加強和改善畫面明暗對比,增強畫面的藝術性。
單獨的物體也是有AO貼圖,處理一個物體之間的陰影物體。

計算得出的AO Texture,越黑的地方代表環(huán)境光越不可能照到該點,該點是環(huán)境光的影響就越小。
沒有SSAO

但是這樣的解決方法,可以解決單物體自己,不能解決兩個物體之間。
為什么了解決這個問題,開發(fā)出屏幕空間環(huán)境光屏蔽(Screen Space Ambient Occlusion,SSAO)
AO計算公式
處理

SSAO計算方法
SSAO背后的原理很簡單:屏幕上的每一個像素,我們都會根據(jù)周邊深度值計算一個遮蔽因子(Occlusion Factor)。這個遮蔽因子之后會被用來減少或者抵消片段的環(huán)境光照分量。遮蔽因子是通過采集片段周圍球型核心(Kernel)的多個深度樣本,并和當前片段深度值對比而得到的。高于片段深度值樣本的個數(shù)就是我們想要的遮蔽因子。
計算方法

基于Ray-Tracing的AO計算模型. 紅色的射線表示V = 1, 綠色的射線表示V = 0.

這樣我們需要對屏幕上的每一個像素進行采樣,所以SSAO很費。并且出現(xiàn)一些其他問題。
出現(xiàn)摩爾紋

所以需要進行模糊,這里使用雙邊濾波(Bilateral filter)來解決
雙邊濾波是一種非線性濾波器,它可以達到保持邊緣、降噪平滑的效果。和其他濾波原理一樣,雙邊濾波也是采用加權平均的方法,用周邊像素亮度值的加權平均代表某個像素的強度,所用的加權平均基于高斯分布[1]。最重要的是,雙邊濾波的權重不僅考慮了像素的歐氏距離(如普通的高斯低通濾波,只考慮了位置對中心像素的影響),還考慮了像素范圍域中的輻射差異(例如卷積核中像素與中心像素之間相似程度、顏色強度,深度距離等),在計算中心像素的時候同時考慮這兩個權重。
獲取到AO貼圖

得到AO貼圖后,事情就輕松很多了,只需要一一對應游戲畫面與AO貼圖,改變游戲畫面上每一個像素的GI值,就可以實現(xiàn)SSAO效果
不同的AO效果
SSAO
HBAO
HDAO
本次只是實現(xiàn)SSAO效果

SSAO理論知識
實現(xiàn)原理
先利用深度圖和屏幕UV坐標反算出當前頂點世界坐標的位置,
再使用沿著法線正方向半球內(nèi)的隨機點進行采樣, 獲得新的坐標點.
兩個點進行比較, 判斷是否被遮擋, 然后加權處理獲得AO.
AO這時候充滿噪點, 需要高斯模糊一下.
最后和場景顏色RT進行混合疊加.
SSAO原理流程圖

獲取深度我們是可以獲取到的,法線深度URP是沒有的我們需要自己獲取。
Depth + Normals 實現(xiàn)
unity URP 管線官方不支持深度法線,我們需要自己實現(xiàn)深度法線,
URP | 后處理-描邊 - 嗶哩嗶哩 (bilibili.com)
上面我們制作描邊的時候,也是使用SSAO的深度法線,這次我們單獨實現(xiàn),深度法線。
Unity URP 不支持深度法線

使用自定義渲染器功能來實現(xiàn)
Scriptable Renderer Feature ? 自定義渲染器功能
創(chuàng)建一個腳本 DepthNormalsFeature ?繼承 ScriptableRendererFeature
這里有兩種方法,
第一種方法
第二種方法

URP默認SSAO的法線效果 ?和我們第二種方法一樣。

擴展 他們兩種方法有什么不同嗎?有什么區(qū)別?
DepthNormalsPass ?是深度和法線一起獲取的,在一起需要進行解碼使用。
DepthNormals 只是獲取像素深度的法線信息。

SSAO | AO實現(xiàn)
設置渲染管線
我們當前準備好渲染管線 ,
這里是使用基礎的顏色來測試管線是否正確
Shader部分
Volume
效果

獲取世界空間位置,法線,切線數(shù)據(jù)
獲取世界空間中的頂點的位置
我們定義一個庫用來放放一些我們自定義的函數(shù)
這個函數(shù)是輸入屏幕UV坐標,從深度中轉(zhuǎn)換為齊次剪裁空間坐標(ndc),使用矩陣轉(zhuǎn)換成世界空間。
那這里我們需要獲取兩個數(shù)據(jù),
深度 ?_CameraDepthTexture
轉(zhuǎn)換世界空間的矩陣 ? ?sampler_CameraDepthTexture
深度我們URP管線自帶,勾選就可以調(diào)用, 轉(zhuǎn)換世界空間的矩陣我們要在管線中獲取,傳到Shader中。
Shader中 定義一個矩陣變量,等等我們在后處理管線中傳入。
SSAO.cs中
我們需要獲取攝像機,在由攝像機來生成矩陣,傳到我們的Shader
在SSAOPass 中我們前定義一個攝像機的變量
Execute 中進行初始化
定義一個函數(shù) ,這個函數(shù)用來后面?zhèn)鬟f控制變量。
在渲染里調(diào)用這個函數(shù),
當前完成
回去到Shader中我們調(diào)用 函數(shù)求處頂點位置。
效果

2. 獲取世界空間中法線
我們需要需要深度法線來計算出像素在世界空間下的位置信息。
深度Unity管線為我們提供了。
這里我們使用上面的第二種方法。
在管線中增加

在庫中輸入我們的 貼圖
在定義一個函數(shù)
Shader中我們調(diào)用函數(shù)
效果

3. 獲取世界空間中切線
切線就是我們需要生成的隨機向量部分,
在庫里定義兩個函數(shù)
shader中調(diào)用
注意:這些都是在世界空間下計算的,后面需要轉(zhuǎn)換成View空間
效果
這就是我們生成的一些隨機噪點

都獲取到了,那計算我們需要的副切線,構(gòu)造一個矩陣。
矩陣準備好了.
注意:wTan = cross(wBin, wNor); ?還需要cross在計算一次。? ? ? ?
?
計算AO效果
定義兩個變量,一個變量是我們的ao 效果,一個是我們控制噪點多少。
定義一個循環(huán)遍歷所有采樣點。
使用隨機向量生成一個半球向量 offDir 通過 scale控制范圍
在HLSL庫中
增加GetRandomVecHalf 函數(shù)
這里增加控制半球控制大小, 世界空間轉(zhuǎn)換裁剪空間,這里需要 wPos 世界空間頂點位置和矩陣。
這里我們需要兩個 矩陣,
v_Matrix ? ? ? ? ?視圖矩陣
是世界空間到觀察者空間的變換矩陣。它描述了相機的位置、朝向和上方向等信息,可以用于將場景從世界坐標系轉(zhuǎn)換到相機坐標系。
p_Matrix ? ? ? ? ? 投影矩陣
是將相機坐標系中的場景投影到屏幕坐標系的變換矩陣。它描述了相機的視錐體、投影方式和屏幕尺寸等信息,可以用于將場景從相機坐標系轉(zhuǎn)換到屏幕坐標系。
SetMatData函數(shù) 使用同樣的方法
采樣深度
SampleSceneDepth函數(shù)獲取屏幕坐標對應的深度值,然后通過LinearEyeDepth函數(shù)將其轉(zhuǎn)換為線性深度。該函數(shù)使用了_ZBufferParams參數(shù),它包含了一些與深度緩沖相關的信息,如深度緩沖的近、遠裁剪面等。
采樣AO
這里我們需要獲取默認的深度值
首先定義一個sampleZ變量,表示采樣點的深度值。然后通過計算sampleDepth和sampleZ之間的距離,并將其與_Radius參數(shù)相除,得到一個范圍檢查的值rangeCheck。這個值用于控制采樣點的范圍,使得只有距離當前像素一定范圍內(nèi)的采樣點才會對AO值產(chǎn)生影響。
接下來的代碼中,通過判斷sampleDepth是否小于depth - 0.08來進行自遮蔽檢查。如果采樣點比當前像素更靠近觀察者,則返回1,否則返回0。這個值用于控制自遮蔽的影響。
最后,將rangeCheck、selfCheck、_AOInt和weight相乘,得到一個權重值,并將其乘以1(即對AO值產(chǎn)生1的影響),最終得到AO值。
采樣深度
這里在庫定義一個獲取深度的函數(shù)
在Sahder中定義
輸出AO
我們在Shader部分輸出ao效果
現(xiàn)在的效果是白色的,啥都沒有。
我們的SSAOVolume還沒有關聯(lián)屬性
SSAOVolume
在這里我們關聯(lián)屬性
RendererFeature
關聯(lián)屬性,我們把屬性都放到 SetMatData()函數(shù)中
增加完代碼
效果

AO效果就計算完成,
我們增加一個判斷,控制開關AO效果,方便對比。
效果

AO部分就算完成。
SSAO | 模糊算法
SSAO我們需要多個Pass
AO
水平模糊
垂直模糊
AO + 原圖
整理Shader
我們需要4個pass這里不適合放到一起,為了方便,我們 重新創(chuàng)建 hlsl文件,放我們的不同的執(zhí)行過程。
Shader 只執(zhí)行我們渲染設置,
Hlsl 文件執(zhí)行計算,

這里重新創(chuàng)建兩個hlsl文件
AOpass
BlurPass

Shader
SSAOFunLib.hlsl
AOPass
BlurPass
這個部分查看
URP | 后處理-模糊算法總結(jié) - 嗶哩嗶哩 (bilibili.com)
在 Volume這里增加上控制模糊的變量
Volume
主要是在管線里處理。
RendererFeature
這里我們來處理模糊
需要定義兩個臨時RT用來處理模糊
獲取臨時RT
注意:這里RT1 和R2是儲存是不一樣的,RT2是一半是我們中間過程,RT是輸出結(jié)果所以使用高精度。
釋放臨時RT
在else里面增加邏輯
這里在執(zhí)行一遍AO效果,裝修完儲存到 bufferid1中,
cmd.SetGlobalTexture("_AOTex", bufferid1) ?把AO效果傳遞到Shader中進行計算。
執(zhí)行兩次 ?水平一次,垂直一次,輸出查看效果

AO原圖混合
我們把處理的結(jié)果傳入到Shader
這樣就可以實現(xiàn)完成了。

可以改變陰影的顏色

這個效果其實還可以在模糊一次,
SSAO.cs
Volune
總結(jié)
在實現(xiàn)AO效果的時候比較麻煩,平常是有兩種方法,第一種方法都是在 View視角下計算,這里使用第二種方法,在世界空間下計算完成,在轉(zhuǎn)換成View視角,
Vertex position vectors in view space

Vertex normal vectors in view space

Sample vectors in tangent space

Noise vectors in tangent space

2. 計算AO具體流程,使用UV計算出需要的 位置,法線,切線構(gòu)建矩陣, 遍歷所有的采樣點,使用隨機向量計算出半求,擴展半球范圍大小。
????計算完成轉(zhuǎn)換到裁剪空間,根據(jù)深度判斷是否在陰影范圍。
3. 在管線里使用攝像機獲取矩陣傳遞給Shader
_VPMatrix_invers ? ? ?視圖投影矩陣的逆矩陣
_VMatrix ? ? ? ? 視圖矩陣是將場景從世界坐標系轉(zhuǎn)換到相機坐標系的變換矩陣
_PMatrix ? ? ? ?投影矩陣則是將相機坐標系中的場景投影到屏幕坐標系的變換矩陣
