前提
環境光(ambient occlusion)是一種GI,其簡化形式SSAO可以用“微量高效”來形容,消耗得很少,得到的效果很好。
環 境光遮蔽(ambient occlusion)的本質是計算在一個點的半球面范圍內有多少方向被阻塞(如下圖1.2.1),然后根據它調整表面顏色。如果實時渲染使用的話非常消 耗,所以在游戲中一般都使用(screen-space ambient occlusion)SSAO。SSAO使用depth buffer來近似一個離散的場景(如下圖1.2.2),從而獲得固定的開銷,而且實現起來比較簡單。但是由於SSAO采樣數目小,所以產生的效果也不理 想,很容易產生noise。在物體移動時noise會更明顯。而且SSAO是low-frequency低分辨率的。所以我們使用時間相干性 (temporal coherence)的SSAO來解決這個問題。


反 向二次投影技術(reverse reprojection)讓我們重用上一幀的像素點並且隨時間優化精制它們。這可以保持每幀采樣點的數量較少,並在短時間有效的累加到上百個采樣點。所 以時間相干性很適用於AO。並且與光源和視點無關,AO只與局部鄰域(local neighborhood)的幾何結構相關,SSAO與像素鄰域的場景結構有關。本文主要講解在延遲渲染戲下使用二次投影技術(reverse reprojection)提升SSAO的質量。
AO(Ambient Occlusion)
從物理的角度來看,AO時sky light 產生的漫反射。計算公式如下:

w 是半球上的所有方向,V是二進制可見度函數(binary visibility function),如果可見V(p,w)=1,如果被阻塞的話就為0。D是一個在0-1之間的單調遞減函數,隨着p到ξ的距離增大而減小,ξ為射線到最 近的表面的交點。簡化一下,D是一個階梯函數。盡管光滑的衰減已經提供了不錯的結果,但是采樣半徑仍是最大的問題
SSAO(Screen Space Ambient Occlusion)
SSAO是一種近似AO的方法,在屏幕空間完成。
我們可以用兩種方法判斷阻塞:
1. 采樣點與當前的depth距離
2. 采樣點和當前點的距離
可以用如下公式表示:

si為采樣點,p為當前點,C為contribution
為了近似公式1.1使用蒙特卡羅積分法,contribution函數如下:

AO 的方向射線被替換為p周圍的采樣點,所以這里的V(p,si)二進制可見度函數(binary visibility function),在p點是否可見采樣點si,如果可見則為0,不可見則為1。V是相對於當前z-buffer的。有些SSAO方法省略了depth test,它們的si的contribution是基於p到si的距離和入射角度。D是p到si距離的衰減。
反向二次投影(Reverse Reprojection)
反 向二次投影將當前幀像素與上一幀相同世界空間位置的像素結合起來,可以在當前幀重用上一幀的像素。這個方法可以用在很多技術上,比如shadow mapping, antialiasing, motion blur等等。我們把上一幀的AO信息存在render target中。在靜態幾何結構中,二次投影在整個幀是一個常量。使用上一幀(f-1)和當前幀(f)的視點(V)和投影矩陣(P),t為像素的 post-perspective位置

我 們使用延遲渲染存儲的這一幀和上一幀eye-linear depth來求出世界空間坐標,也可以直接存儲世界坐標到一張render target中(博主覺得后者比較方便,也免去重復計算)。我們只需要轉換當前世界坐標Pf與上一幀的view-projection matrix Pf-1Vf-1來得到tf-1
我們根據tf-1來計算查找紋理坐標texf-1來查找上一幀的AO buffer。應用透視除法並且縮放結果在0-1范圍內。

動態物體
如 果在動態場景中方程1.4是無效的,因為二次投影是基於動態物體的轉換。解決方案是進行兩次頂點轉換,一次是使用當前幀轉換參數(modeling matrix, skinning.),另一次是使用前一幀的參數。我們無法儲存轉換參數,所以我們把Pf-1-Pf存到一張render target中,
此處可以使用比depth精度低的render target來存儲這個偏移,可以用16bit。


簡單講解一下unity render target format
| 色彩渲染貼圖格式,每通道8 bits |
|
| 一種depth貼圖 |
|
| 色彩渲染貼圖格式,每通道16 bit |
|
| 一種本地系統的 shadowmap渲染貼圖格式 |
|
| 色彩渲染貼圖格式 |
|
| 色彩渲染貼圖格式,每通道4 bit |
|
| 色彩渲染貼圖格式, rgb通道5 bit,Alpha通道1 bit |
|
| 默認色彩渲染貼圖格式, Frame Buffer也是這個格式 |
|
| 色彩渲染貼圖格式。顏色10 bits,alpha 2 bits |
|
| 默認HDR色彩渲染貼圖格式,HDR的 Frame Buffer也是這個格式 |
|
| 色彩渲染貼圖格式,每通道32 bit浮點值 |
|
| 兩種顏色 (RG)渲染貼圖格式, 每通道32 bit浮點值 |
|
| 兩種顏色 (RG)渲染貼圖格式,每通道16 bit浮點值 |
|
| 標量(R)渲染貼圖格式,32 bit浮點值 |
|
| 標量(R)渲染貼圖格式,16 bit浮點值 |
|
| 標量(R)渲染貼圖格式, 8 bit fixed point. |
|
| 四通道(ARGB)渲染貼圖格式, 每通道32 bit帶符號整數 |
|
| 二通道(RG)渲染貼圖格式, 每通道32 bit帶符號整數 |
|
| 標量(R)渲染貼圖格式,32 bit帶符號整數 |
隨時間精制SSAO
在當前幀f,我們計算新的contribution Cf,k為有效的(可視V>0)采樣點個數

jf(p)是所有已用的采樣點的個數。
結合前一幀的信息我們得到AO的求法

權重wf(p)是累積的所有采樣點的個數。有一個最大值作為閾值。
理 論上來講這種方法可以任意的采樣,但是在實踐中,是不可以的,因為二次投影並不是准確的,而且重建需要雙邊過濾,隨時間每一次二次投影都使這個誤差加劇, 這種誤差帶來顯而易見的模糊,而且會隨時間越來越模糊。而且,新的采樣點逐漸趨於零,先前的采樣永遠不會消除。所以我們用先前定義的wmax這個閾值來 clamp wf,使舊的contributions隨時間逐漸衰減。此處wmax趨於正無窮。

博主把每幀的w值存在了y通道中,另外把最終的AO值存在了x通道中,把depth存在了z通道中。
起始索引jf存在通道a中
從像素中心獲取索引值,像素中心根據上面求出的紋理坐標來求得texf-1,res x,y是當前幀buffer的分辨率。

檢測和處理無效的像素
當我們重投影一個fragment,我們需要判斷前一幀是否對應當前像素,也就是說我們儲存的上一幀的AO是否有效。我們通過下面三種條件來判斷是否有效:1.當前fragment無阻塞2.fragment的采樣鄰域發生變化3.fragment之前在幀的外面。
判斷無阻塞
我們用上一幀和當前幀的depth來判斷是否無阻塞,當前幀depth值為df,上一幀為df-1。

上式可以在一個大場景中產生一個穩定的結果,有很寬的depth范圍,對近面不敏感,對遠距離十分敏感。這種情況下,我們discard上一幀,把wf-1設為0,然后計算新的AO。
判斷采樣鄰域的變化
判 斷無阻塞這一步驟已經避免了大部分時間相干性的缺陷,但也只局限於固定不動的場景。但是SSAO是從鄰域像素收集信息的,通過使用空間sampling kernel。所以,在動態場景我們需要考慮當前像素點鄰域的動態移動的物體會影響到當前像素點,即使在無阻塞的像素也是這樣。
我們在AO中使用采樣做兩件事一件是計算當前contribution Cf(p)另一件是檢測有效性。檢測原理如下圖
有效的采樣點si和像素p通過采樣和像素的相關位置的改變被估算:

sif-1通過之前存儲的偏移向量(offset vectors)和si來算出(上面提到過的的方法)。只使用tangent 面前面的采樣。
平滑失效處
上面的方法中我們用一個二進制閾值來檢測無阻塞,我們通過
檢查所有采樣來discard上一幀的值。但是,在變形緩慢的表面,AO改變也是很緩慢,此時我們沒有必要全部discard掉上一幀的值,用一種新的方法求出改變的程度,如下公式:
計算了一個在0-1范圍內的confidence來表示之前的SSAO的有效程度。

S控制無效處的光滑程度在15-30范圍內,分布如下圖示。如果相關距離沒變的話δ(x)=0,隨着δ(si)增大confidence趨於零。

先前的AO中的所有confidence:

把它和前面的權重weight(公式1.6)相乘,為了防止閃爍, convergence小於一定閾值下時我們不重用相同的采樣,比如conv(p)<0.5(公式1.7)。
過高的S會移除時間相干性的缺陷,但是會產生很多noise。
處理frame-buffer邊界
在幀邊界處的信息是不正確的,所以我們檢測上一幀中的邊界處的采樣,在邊界不使用上面的平滑失效,直接discard掉,在當前幀計算confidence時也不在超出邊界處采樣。
自適應Convergence-Aware空間濾波。
SSAO通常使用一個空間濾波來減緩由采樣率不足引起的noise。TSSAO也使用一種空間濾波,通過的采樣點與當前點在世界空間的距離,這種自適應空間濾波能自動考慮到depth差距,並檢測到depth 差距很大的不連續的地方:

x是采樣點,K(p)是標准化因子,g是空間濾波kernel(比如高斯濾波)。

給出濾波處代碼
<span style="font-size:14px;"> for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
uv_sam = uv + float2(-1 + i, -1 + j) / _Size;
buffer = tex2D(_AoTex, uv_sam);
conv = buffer.y;
c = buffer.x;
pos = GetPosition(uv,0);
pos_sam = GetPosition(uv_sam,0);
g = G(distance(pos,pos_sam));
Kp += g*conv;
Ao += Kp*c;
}
}</span>
采樣方式
采樣距離不要過近,判斷的阻塞很少,在半球的半徑或半徑的一半這樣的距離之間最好。
可以參考以前寫過的這篇文章超級采樣 Supersampling 方式匯總,此處推薦Halton sequence方法
2D Halton sequence

3D Halton sequence

x,z分量計算隨機方向的單位球,y分量設置采樣半徑r。
在Game Programming Gems8使用半隨機方法semi-random 3D vectors
在法線周圍隨機方向向量和距離
這里給出代碼:
float3 reflect( float3 vSample, float3 vNormal )
{
return normalize( vSample – 2.0f * dot( vSample, vNormal ) * vNormal );
}
float3x3 MakeRotation( float fAngle, float3 vAxis )
{
float fS;
float fC;
sincos( fAngle, fS, fC );
float fXX = vAxis.x * vAxis.x;
float fYY = vAxis.y * vAxis.y;
float fZZ = vAxis.z * vAxis.z;
float fXY = vAxis.x * vAxis.y;
float fYZ = vAxis.y * vAxis.z;
float fZX = vAxis.z * vAxis.x;
float fXS = vAxis.x * fS;
float fYS = vAxis.y * fS;
float fZS = vAxis.z * fS;
float fOneC = 1.0f - fC;
float3x3 result = float3x3(
fOneC * fXX + fC, fOneC * fXY + fZS, fOneC * fZX - fYS,
fOneC * fXY - fZS, fOneC * fYY + fC, fOneC * fYZ + fXS,
fOneC * fZX + fYS, fOneC * fYZ - fXS, fOneC * fZZ + fC
);
return result;
}
float4 PostProcessSSAO( float3 i_VPOS )
{
...
const float c_scalingConstant = 256.0f;
float3 vRandomNormal = ( normalize( tex2D( p_sSSAONoise, vScreenUV *
p_vSrcImageSize / c_scalingConstant ).xyz * 2.0f
– 1.0f ) );
float3x3 rotMatrix = MakeRotation( 1.0f, vNormal );
half fAccumBlock = 0.0f;
for ( int i = 0; i < iSampleCount; i++ ) {
float3 vSamplePointDelta = reflect( p_vSSAOSamplePoints[i],
vRandomNormal );
float fBlock = TestOcclusion(
vViewPos,
vSamplePointDelta,
p_fOcclusionRadius,
p_fFullOcclusionThreshold,
p_fNoOcclusionThreshold,
p_fOcclusionPower ) ) {
fAccumBlock += fBlock;
}
...
}
結果
博主實現總共分三步:
1.SSAO
2.TSSAO
3.Filter
給出三步的實現結果

上圖為沒有濾波的SSAO

上圖為沒有濾波的TSSAO

上圖為有濾波的TSSAO

上圖為Diffuse Color 無光照和陰影

上圖為Diffuse Color 和有濾波的TSSAO

上圖為Diffuse Color 和有濾波的TSSAO和光照顏色

原圖

原圖和有濾波的TSSAO
總結
對 於SSAO本身有一個問題就是對過近或過遠的物體處理的不好,因為在固定的采樣范圍下,過遠的物體采樣范圍偏大忽略了遠處細節的阻塞,在近處又由於采樣范 圍偏小,導致判斷幾乎在同一個位置上、幾乎沒有阻塞,解決這個問題的方法就是根據遠近動態調節采樣范圍,算是SSAO的一種優化處理。
SSAO本身是一種低分辨率的處理,因為性能的原因采樣點過少,采樣率也就過少,但是TSSAO有效的解決了這個問題,使得采樣點隨時間累加,且能很好的處理動態場景,效果比SSAO好很多,而且更加正確,Noise也很少。
本篇到此結束,
順便求實習工作,哈哈
近期成果:最近弄的一些渲染
email: wolf_crixus@sina.cn
------ by wolf96
