Unity shader 獲取深度的詳細(xì)數(shù)學(xué)原理

需要從shader?中獲取深度值,主要涉及到渲染流水線中的以下幾個(gè)節(jié)點(diǎn)

1.觀察空間:觀察空間是以攝像機(jī)所在位置為原點(diǎn)的空間。我們嘗試獲取的深度信息就是在這個(gè)空間之下。

2.裁剪空間(投影空間、齊次裁剪空間):將頂點(diǎn)轉(zhuǎn)換到此空間下來判斷是否在視椎體內(nèi),從而裁剪掉不在攝像機(jī)視野內(nèi)的頂點(diǎn);將頂點(diǎn)變換到此空間下可以方便做后續(xù)的投影工作。

3.屏幕空間:將沒有被裁剪的頂點(diǎn)從裁剪空間轉(zhuǎn)換到此空間,從而最終呈現(xiàn)在屏幕上。


主要涉及到的術(shù)語

1.裁剪矩陣(投影矩陣):

用于將頂點(diǎn)從觀察空間轉(zhuǎn)換到裁剪空間的矩陣。

經(jīng)過裁剪矩陣的操作后,頂點(diǎn)的x、y、z分量則被轉(zhuǎn)換到裁剪空間中。

裁剪空間下的頂點(diǎn)滿足以下條件的,即為在視野內(nèi)的頂點(diǎn):

-w<=x<=w;

-w<=y<=w;

-w<=z<=w;

以上裁剪原理與深度獲取關(guān)系不大,只需要注意:

經(jīng)過裁剪矩陣的操作后,頂點(diǎn)的w?分量保存了頂點(diǎn)在觀察空間下的深度信息(觀察空間的頂點(diǎn)的z?分量)。


2.齊次除法(透視除法):

經(jīng)過裁剪矩陣的操作之后,將裁剪空間下的頂點(diǎn)的x、y、z?分量分別除以w?分量的過程。此過程完成后,裁剪空間下的頂點(diǎn)將會轉(zhuǎn)換到NDC?中,即變換到一個(gè)各分量從-1到1長度為2的立方體內(nèi)部。如果使用的是透視攝像機(jī),因?yàn)閦?分量保留了深度信息,經(jīng)過透視除法以后,越遠(yuǎn)離攝像機(jī)的頂點(diǎn)其xy分量數(shù)值將越小(因?yàn)閣?分量是觀察空間中頂點(diǎn)的z?分量,對于兩個(gè)頂點(diǎn),如果他們x、y?分量相同,而w?分量不同即深度不同,越遠(yuǎn)的頂點(diǎn)經(jīng)過透視除法后,其數(shù)值也就越?。?。齊次除法完成后,頂點(diǎn)的x、y?分量再經(jīng)過簡單的縮放映射即可投射到屏幕空間二維坐標(biāo)中,而z?分量通過d = 0.5*z + 0.5?轉(zhuǎn)變?yōu)?-1?范圍并作為最終會存貯于深度圖中的數(shù)據(jù)。


3.屏幕空間:

渲染管線中最后一個(gè)流程將裁剪空間中的頂點(diǎn)通過透視除法和屏幕映射映射最終從3D?的裁剪空間轉(zhuǎn)換到2D?的空間中,這個(gè)2D?空間就是屏幕空間。屏幕空間是左下角為(0,0),右上角為(screenwidth, screenheight)的二維空間。


Shader?中獲取深度(unity 2019.4.19 urp)

Shader?中將頂點(diǎn)從裁剪空間轉(zhuǎn)換到屏幕空間(即齊次除法和屏幕映射)由底層完成,而獲取深度的原理就是再現(xiàn)這一操作中某些步驟的過程。

通過頂點(diǎn)數(shù)據(jù)可以直接獲得頂點(diǎn)的深度;通過頂點(diǎn)數(shù)據(jù)和屏幕映射公式可以逆推獲得屏幕紋理坐標(biāo),使用屏幕紋理坐標(biāo)可以采樣深度圖,然后將深度圖中的數(shù)據(jù)逆推回模型空間就可以獲得需要的場景深度。

1.頂點(diǎn)著色器中需要完成將頂點(diǎn)從模型空間轉(zhuǎn)換到裁剪空間下的任務(wù)。

2.來到片元著色器中,經(jīng)過Unity?渲染流程的處理,頂點(diǎn)輸出的裁剪空間頂點(diǎn)(SV_POSITION?語義)坐標(biāo)的xy?分量已經(jīng)做了透視除法和屏幕映射處理轉(zhuǎn)換到了屏幕空間當(dāng)中,z 分量也做了透視除法并轉(zhuǎn)換到(0,1)范圍內(nèi),w?分量仍然是觀察空間下的深度值。


獲取頂點(diǎn)的深度值:

裁剪空間頂點(diǎn)坐標(biāo)w?分量就是視角空間下的深度值,將其除以遠(yuǎn)裁面就是0-1?的深度值。

遠(yuǎn)裁剪面的距離可以通過內(nèi)置的_ProjectionParams.z?變量來獲取。?

或者比較傻的辦法:

1.上面講到片元著色器中,裁剪空間中的頂點(diǎn)(SV_POSITION?語義)坐標(biāo)的z?分量已經(jīng)做了透視除法并轉(zhuǎn)換到了(0,1)范圍中,此時(shí)只需要將其逆推回觀察空間即可;

2.利用公式:(n表示遠(yuǎn)裁剪面;f表示近裁剪面;d表示ndc中重映射后的深度值,即第2步驟計(jì)算出的數(shù)值)

zv?= 1/((n-f/n*f)*d + 1/n)?計(jì)算出觀察空間下的深度值,將其除以遠(yuǎn)裁剪面就是0-1范圍的觀察空間下的深度值,公式:

z01=1/((n-f)/n*d + f/n)

以上公式實(shí)際上是將渲染管線處理后的頂點(diǎn)逆推回觀察空間中,實(shí)際上是結(jié)合了投影矩陣z?分量上的處理公式、透視除法公式、屏幕映射公式三個(gè)步驟逆推出來的公式。

之所以說這個(gè)方法很傻,是因?yàn)槔@了一個(gè)圈,因?yàn)轫旤c(diǎn)從裁剪空間轉(zhuǎn)到屏幕空間下又逆推回了觀察空間,之所以要介紹這種辦法,是因?yàn)樵谟龅叫枰@取場景深度值的需求時(shí),我們可以拿到的數(shù)據(jù)(即深度圖)存儲的正是步驟1?中頂點(diǎn)z?分量的值。

由于頂點(diǎn)在轉(zhuǎn)換到裁剪空間時(shí),其w?分量就是觀察空間下的z?分量,如果只需要頂點(diǎn)的深度,還是建議直接使用裁剪空間頂點(diǎn)的w?分量作為深度值。

?

獲取場景的深度值:

1.上面提到在片元著色器中,頂點(diǎn)坐標(biāo)的xy?分量已經(jīng)從裁剪空間轉(zhuǎn)換到了屏幕空間。

2.直接將頂點(diǎn)坐標(biāo)的xy?分量除以屏幕的長寬就可以得到0-1?范圍的uv?坐標(biāo)(屏幕紋理坐標(biāo)),屏幕的長寬可以通過內(nèi)置變量_ScreenParams?獲取。

3.對深度圖進(jìn)行采樣: SAMPLE_TEXTURE2D_X(_CamearaDepthTexture,?sampler_CameraDepthTexture,?uv)

采樣深度圖需要在shader?中作如下聲明:

TEXTURE2D_X_FLOAT(_CameraDepthTexture);

SAMPLER(sampler_CameraDepthTexture);

4.利用公式:(n表示遠(yuǎn)裁剪面;f表示近裁剪面;d表示深度圖中的數(shù)據(jù),即第四步結(jié)果的x分量)

zv?= 1/((n-f/n*f)*d + 1/n)?計(jì)算出模型空間下的深度值,將其除以遠(yuǎn)裁剪面就是0-1范圍的深度值,公式:

z01=1/((n-f)/n*d + f/n)

也可以在頂點(diǎn)著色器中計(jì)算采樣坐標(biāo),但是因?yàn)閺捻旤c(diǎn)到片元著色器有一個(gè)插值過程,所以不能在頂點(diǎn)著色器中進(jìn)行齊次除法,因此需要乘回w?分量,計(jì)算公式:(vc 為裁剪空間下的頂點(diǎn)坐標(biāo)、vcw為該坐標(biāo)的w分量)

vcw*(vc/vcw*0.5+0.5) => 0.5vc+0.5vcw

這也是內(nèi)置函數(shù)ComputeScreenPos?的實(shí)現(xiàn)。

然后在片元著色器中進(jìn)行齊次除法獲得uv?坐標(biāo),然后從步驟3繼續(xù)往下執(zhí)行

如果在頂點(diǎn)著色器使用了ComputeScreenPos獲得Vs;

那么對于步驟5可以使用unity?內(nèi)部提供的兩個(gè)方法Linear01Depth、LinearEyeDepth?傳入屏幕紋理坐標(biāo)和_ZBufferParams?來獲取觀察空間的場景深度和0-1線性深度,函數(shù)內(nèi)部的原理即為上述步驟5。這也是shaderGraph?中SceneDepth?節(jié)點(diǎn)的做法。_ZBufferParams?是Unity?提供的內(nèi)置變量,里面包含了遠(yuǎn)近裁剪平面相關(guān)的預(yù)計(jì)算。?同理,頂點(diǎn)的坐標(biāo)也可以使用這種方式來獲得,只不過頂點(diǎn)的深度我們可以直接通過齊次除法來取得。

源碼

Shader "Custom/Depth"

{

Properties

{

}

SubShader

{

Tags { "RenderType"="Transparent" "Queue"="Geometry" "RenderPipeline" = "UniversalRenderPipeline"}

LOD 200

Pass

{

HLSLPROGRAM

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

#pragma vertex vert

#pragma fragment frag

struct a2v

{

float4 vertex : POSITION;

};

struct v2f

{

float4 vertex : SV_POSITION;

//結(jié)合ComputeScreenPos 使用

//float4 screenUV : TEXCOORD5;

};

CBUFFER_START(UnityPerMaterial)

CBUFFER_END

TEXTURE2D_X_FLOAT(_CameraDepthTexture);

SAMPLER(sampler_CameraDepthTexture);

v2f vert(a2v i)

{

v2f o;

o.vertex = TransformObjectToHClip(i.vertex.xyz);

//需要在片元中做透視除法

//o.screenUV = ComputeScreenPos(o.vertex,_ProjectionParams.x);

return o;

}

half4 frag(v2f i) : SV_TARGET

{

//頂點(diǎn)深度

half dv = i.vertex.w;

//頂點(diǎn)01 深度

half dv01 = i.vertex.w / _ProjectionParams.z;

//直接獲取屏幕紋理坐標(biāo)

float2 screenUV = i.vertex.xy / _ScreenParams.xy;

//結(jié)合ComputeScreenPos 計(jì)算屏幕紋理坐標(biāo)

//float2 screenUV = i.screenUV.xy / i.screenUV.w;

//深度圖中存儲的深度,需要逆推回到觀察空間

float dd = SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, screenUV).r;

//觀察空間中的0-1場景深度

float ds01 = Linear01Depth(dd, _ZBufferParams);

//觀察空間中的場景深度

float ds = LinearEyeDepth(dd, _ZBufferParams);

return ds01;

}

ENDHLSL

}

}

}

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

相關(guān)閱讀更多精彩內(nèi)容

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