[Unity Shader] 小談深度法線紋理

在實(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ì)算:

depth = \frac{\frac{1}{z}-\frac{1}{near}}{\frac{1}{far}-\frac{1}{near}}

這個(gè) z 值是觀察空間(view space)z 分量的相反數(shù)(由于觀察空間為右手坐標(biāo)系)。深度值和 \frac{1}{z} 成正比,函數(shù)圖如下所示:

深度值計(jì)算函數(shù)

可以看到 z 值在 [1,2] 范圍中精度占了50%,現(xiàn)實(shí)中我們對(duì)近距離的深度精度需求要高于遠(yuǎn)距離的精度,這樣可以緩解深度沖突(Z-fighting) 問(wèn)題。

那么,問(wèn)題來(lái)了,這個(gè)深度值計(jì)算公式又是怎么得到的。

image

首先,這個(gè)深度值來(lái)自于頂點(diǎn)變化后的 NDC 的 z 分量, 由于經(jīng)過(guò)了投影變換 (通常是透視投影),這個(gè)深度值則是非線性。還有, NDC z 分量的范圍為 [-1,1] 我們需要對(duì)其進(jìn)行處理:

depth = 0.5 * z_{ndc} + 0.5

特別的,在 DirectX 接口中 z 分量的范圍為 [0,1]

接著,我們根據(jù)視錐矩陣,來(lái)得到裁剪 z_{clip} 分量和觀察 z_v 之間的關(guān)系:

M_{frustum} = \begin{bmatrix} \frac{ \cot{ \frac{FOV}{2} } }{Aspect} & 0 & 0 & 0 \\ 0 & \cot{ \frac{FOV}{2} } & 0 & 0 \\ 0 & 0 & -\frac{Far + Near}{Far - Near} & -\frac{2 * Near * Far}{Far - Near} \\ 0 & 0 & -1 & 0 \end{bmatrix}

攝像機(jī)

由此可得:

z_{clip} = - z_{view}\frac{(Far + Near) }{Far - Near} -\frac{2 * Near * Far}{Far - Near}

w_{clip} = -z_{view}

同時(shí),我們可以得到 NDC 和 z_{view} 的關(guān)系:

z_{ndc} = \frac{z_{clip}}{w_{clip}} = -\frac{(Far + Near) }{Far - Near} -\frac{2 * Near * Far}{(Far - Near) * z_{view}}

由于我們知道了 NDC 的 z 分量和深度值之間的關(guān)系,我們就能推出深度值最開(kāi)始計(jì)算深度值的函數(shù)關(guān)系(我們需要對(duì) z_{view} 取反,因?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è)范圍在 [0,1] 的線性深度值。這兩個(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 觀察深度信息和法線信息。

渲染結(jié)果
深度信息
發(fā)信信息

當(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)行效果

運(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)行效果

運(yùn)行效果
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容