unity 深度紋理和法線紋理

原理

在實現某些屏幕後處理的時候,僅僅只有圖像的rgb信息是很難實現的,同簡單的圖像處理不同的是,我們能在shader中獲得圖像每個像素的深度信息和法線信息。
想要理解深度紋理的原理就必須要對渲染流水線有個比較深入的瞭解,深度紋理和法線紋理通過unity底層使用的一個單獨的pass來把整個場景再次渲染一遍,保存在_CameraDepthTexture裏面,其中RG通道存儲法線信息,BA通道存儲深度信息。
由於經過了非線性的投影變換,我們的深度信息也不是線性的,如果需要得到線性的深度信息,我們需要對其進行手動轉換,這過程就涉及到渲染流水線中的投影變換。
在這裏插入圖片描述觀察一下我們的投影矩陣,很容易得到裁剪空間的z座標與w座標
在這裏插入圖片描述
深度紋理存儲的是映射到(0,1)範圍的NDC,因此有
在這裏插入圖片描述
上面那些公式很容易能推導出來觀察空間的深度的表達式
在這裏插入圖片描述

但是unity攝像機正向對應的z爲負值,爲了得到正數的表示,我們把上面的結果取反,就能得到最後的結果了。
在這裏插入圖片描述

因爲這個z是真實的距離,因此其範圍是近裁剪面到遠裁剪面,也就是[near,far],如果想要把它映射到0~1之間,只需要除以一個far就行了,
在這裏插入圖片描述
上面的結果就是映射到01空間的線性深度值。unity的函數LinearEyeDepth和Linear01Depth分別就是計算真實深度和映射到01的深度的函數。

2.實現

下面三張圖片,第一張圖片是原圖,第二張圖片是其深度紋理,第三張圖片是法線紋理。
在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述其輸出過程是非常簡單的,但是得到這兩張紋理的方法確實後面的一些高級的屏幕特效所需要的。
深度紋理的獲取方法:
1.攝像機設置好正確的模式
Camera.main.depthTextureMode = DepthTextureMode.DepthNormals;
2.聲明正確命名的變量
sampler2D _CameraDepthTexture;
3.解碼
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv);
float liearDepth = Linear01Depth(depth);
法線紋理的獲取方法
1.攝像機設置好正確的模式
Camera.main.depthTextureMode = DepthTextureMode.DepthNormals;
2.聲明正確命名的變量
sampler2D _CameraDepthNormalsTexture
3.解碼
fixed3 normal = DecodeViewNormalStereo(tex2D(_CameraDepthNormalsTexture, i.uv));
獲取上面的圖片只需要簡單的輸出他們即可

3.系統函數解析

inline float Linear01Depth( float z )
{
    return 1.0 / (_ZBufferParams.x * z + _ZBufferParams.y);
}
// Z buffer to linear depth
inline float LinearEyeDepth( float z )
{
    return 1.0 / (_ZBufferParams.z * z + _ZBufferParams.w);
}

上面的兩個函數看起來好像和我們上面描述的公式不大一樣,其中重點可能就是_ZBufferParams,我們再來看一看_ZBufferParams的源碼

// Values used to linearize the Z buffer (http://www.humus.name/temp/Linearize%20depth.txt)
// x = 1-far/near
// y = far/near
// z = x/far
// w = y/far
// or in case of a reversed depth buffer (UNITY_REVERSED_Z is 1)
// x = -1+far/near
// y = 1
// z = x/far
// w = 1/far
float4 _ZBufferParams;

把_ZBufferParams的各個分量代入到上面的公式裏面,得到的結果正是我們上面推導出來的~

inline float3 DecodeViewNormalStereo( float4 enc4 )
{
    float kScale = 1.7777;
    float3 nn = enc4.xyz*float3(2*kScale,2*kScale,0) + float3(-kScale,-kScale,1);
    float g = 2.0 / dot(nn.xyz,nn.xyz);
    float3 n;
    n.xy = g*nn.xy;
    n.z = g-1;
    return n;
}

乍一看這個函數有點超出我的理解範圍了…
但是查閱了一些資料,對它還是有了那麼一點理解。上面的函數是解碼函數,不妨我們從編碼函數開始理解

inline float2 EncodeViewNormalStereo( float3 n )
{
    float kScale = 1.7777;
    float2 enc;
    enc = n.xy / (n.z+1);
    enc /= kScale;
    enc = enc*0.5+0.5;
    return enc;
}

函數的作用就是把3維的法線映射到2維且0~1的座標下,這裏使用的方法叫做球極投影,
在這裏插入圖片描述P就是我們的法線座標,N是(0,0,1),然後把法線和N連線,在XOY平面上的投影就是最後的座標了,根據相似三角形的關係,我們很快就能得到
x’ = x/(1+z) ,y’ = y/(1+z),如果P和N在同一側,當然p’可能就不在圓內,不過一想,這樣的點我們是看不到的,在背面剔除的時候已經沒了,爲了使得編碼得到的紋理效果儘可能好,投影值應該除以一個縮放值之後再編碼到紋理中,unity內置的是1.777,這個算是一個經驗值吧,也就是16:9,然後再要把法線從(-1,1)映射到(0,1)之間,至此,編碼操作的代碼已經很容易理解了。
理解了編碼操作之後,解碼操作也只是其逆操作,首先nn把其重新映射到(-1,1)範圍,然後再恢復x,y的值,最後我還是沒弄懂爲什麼z=2/(xx+yy+z*z)-1,魔法一樣,不想了,頭髮要緊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章