在實(shí)現(xiàn)一些特定的效果,我們需要獲取場(chǎng)景中的深度紋理和法線紋理。雖然,在ShaderLab中,獲取它們很簡(jiǎn)單,但在使用他們之前,應(yīng)該了解他們根源。
1 概念
法線信息是通過(guò)頂點(diǎn)輸入階段而得的,這節(jié)我們只討論深度紋理,因?yàn)樯疃刃畔⒈容^特殊,需要通過(guò)計(jì)算得到的。
1.1 深度紋理
深度紋理實(shí)際上來(lái)自深度緩沖,它包含了一個(gè)介于 0.0 和 1.0 之間的深度值,通常用于做深度測(cè)試(depth test)。
實(shí)際上這個(gè)深度值并不是均勻分布的,這個(gè)深度值是依據(jù)下列公式計(jì)算:
這個(gè) z 值是觀察空間(view space)下 分量的相反數(shù)(由于觀察空間為右手坐標(biāo)系)。深度值和
成正比,函數(shù)圖如下所示:

可以看到 值在
范圍中精度占了50%,現(xiàn)實(shí)中我們對(duì)近距離的深度精度需求要高于遠(yuǎn)距離的精度,這樣可以緩解深度沖突(Z-fighting) 問(wèn)題。
那么,問(wèn)題來(lái)了,這個(gè)深度值計(jì)算公式又是怎么得到的。

首先,這個(gè)深度值來(lái)自于頂點(diǎn)變化后的 NDC 的 z 分量, 由于經(jīng)過(guò)了投影變換 (通常是透視投影),這個(gè)深度值則是非線性。還有, NDC z 分量的范圍為 我們需要對(duì)其進(jìn)行處理:
特別的,在 DirectX 接口中 z 分量的范圍為
接著,我們根據(jù)視錐矩陣,來(lái)得到裁剪 分量和觀察
之間的關(guān)系:

由此可得:
同時(shí),我們可以得到 NDC 和 的關(guān)系:
由于我們知道了 NDC 的 分量和深度值之間的關(guān)系,我們就能推出深度值最開(kāi)始計(jì)算深度值的函數(shù)關(guān)系(我們需要對(duì)
,因?yàn)樗怯^察坐標(biāo)系,總是為負(fù)數(shù))
2 深度法線可視化
在Unity中,深度紋理可以直接來(lái)自于真正的深度緩存,也可以由一個(gè)單獨(dú)的Pass進(jìn)行渲染,取決于使用的渲染路徑和硬件。在延遲渲染中,深度和紋理信息會(huì)直接渲染到G-buffer中,所以可以直接訪問(wèn)。而前向渲染中,是不會(huì)創(chuàng)建法線紋理的,因此,Unity底層使用了一個(gè)單獨(dú)的 Pass (在 buildin_shaders-xxx/DefaultResources/Camera-DepthNormalTexture.shader 中)把整個(gè)場(chǎng)景都渲染了一遍
我們可以讓攝像機(jī)生成一張深度紋理(精度為24位或16位,取決于深度緩存的精度)或者深度+法線紋理(共32位,深度、法線紋理各占16位)。
2.1 Unity API
我們需要在腳本中設(shè)置攝像機(jī)紋理的渲染模式:
// 渲染深度信息 在shader中通過(guò) _CameraDepthTexture訪問(wèn)
camera.depthTextureMode = DepthTextureMode.Depth;
// 渲染深度和法線信息,通過(guò)_CameraDepthNormalsTexture
camera.depthTextureMode = DepthTextureMode.DepthNormals;
// 可以同時(shí)產(chǎn)生深度和深度+法線紋理
camera.depthTextureMode |= DepthTextureMode.Depth;
camera.depthTextureMode |= DepthTextureMode.DepthNormals;
大多情況,我們可以通過(guò) tex2D 函數(shù)直接采樣,由于某些平臺(tái)(如 PS3和PS2)上,我們需要一些特殊處理。因此,需要使用宏 SAMPLE_DEPTH_TEXTURE 進(jìn)行采樣:
float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
還有類似的宏如:SAMPLE_DEPTH_TEXTURE_PROJ、SAMPLE_DEPTH_TEXTURE_LOD
通過(guò)紋理采樣得到的深度值是非線性的,即為ndc坐標(biāo)下的深度值,我們可以通過(guò)LinearEyeDepth將采樣結(jié)果轉(zhuǎn)化為觀察空間下的深度值。還有Linear01Depth則會(huì)返回一個(gè)范圍在 的線性深度值。這兩個(gè)函數(shù)使用了內(nèi)置的
_ZBufferParams變量來(lái)得到遠(yuǎn)近裁剪平面的距離。
獲取深度+法線紋理,可以直接使用tex2D函數(shù)對(duì)_CameraDepthNormalsTexture進(jìn)行采樣。Unity提供了輔助函數(shù)DecodeDepthNormal來(lái)為這個(gè)采樣結(jié)果進(jìn)行解碼,從而獲得深度值和法線信息,它在UnityCG.cginc被定義:
inline void DecodeDepthNormal(float4 enc, out float depth, out float3 normal){
// 范圍為[0,1]的線性深度值
depth = DecodeFloatRG(enc.zw);
// 觀察空間下的法線信息
normal = DecodeViewNormalStereo(enc);
}
當(dāng)我們開(kāi)啟攝像機(jī)渲染深度+法線紋理時(shí),我們可以在 Frame Debugger 觀察深度信息和法線信息。



當(dāng)然,可以通過(guò)編寫(xiě) Shader 的方式來(lái)觀察深度和法線信息。
2.2 深度信息可視化
fixed4 frag(v2f i): SV_TARGET{
float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);
float depth = 1-d;
return fixed4(depth,depth,depth,1.0);
}

運(yùn)行效果

2.3 法線信息可視化
fixed4 frag(v2f i): SV_TARGET{
half4 depthNormal = tex2D(_CameraDepthNormalsTexture, i.uv);
half3 normal = DecodeViewNormalStereo(depthNormal);
return fixed4(normal,1.0);
}

運(yùn)行效果
