需要從shader?中獲取深度值善茎,主要涉及到渲染流水線中的以下幾個節(jié)點
1.觀察空間:觀察空間是以攝像機所在位置為原點的空間蚯舱。我們嘗試獲取的深度信息就是在這個空間之下愚隧。
2.裁剪空間(投影空間海蔽、齊次裁剪空間):將頂點轉換到此空間下來判斷是否在視椎體內(nèi)锥咸,從而裁剪掉不在攝像機視野內(nèi)的頂點;將頂點變換到此空間下可以方便做后續(xù)的投影工作。
3.屏幕空間:將沒有被裁剪的頂點從裁剪空間轉換到此空間欢唾,從而最終呈現(xiàn)在屏幕上且警。
主要涉及到的術語
1.裁剪矩陣(投影矩陣):
用于將頂點從觀察空間轉換到裁剪空間的矩陣。
經(jīng)過裁剪矩陣的操作后礁遣,頂點的x斑芜、y、z分量則被轉換到裁剪空間中祟霍。
裁剪空間下的頂點滿足以下條件的杏头,即為在視野內(nèi)的頂點:
-w<=x<=w;
-w<=y<=w;
-w<=z<=w;
以上裁剪原理與深度獲取關系不大沸呐,只需要注意:
經(jīng)過裁剪矩陣的操作后醇王,頂點的w?分量保存了頂點在觀察空間下的深度信息(觀察空間的頂點的z?分量)。
2.齊次除法(透視除法):
經(jīng)過裁剪矩陣的操作之后崭添,將裁剪空間下的頂點的x寓娩、y、z?分量分別除以w?分量的過程呼渣。此過程完成后棘伴,裁剪空間下的頂點將會轉換到NDC?中,即變換到一個各分量從-1到1長度為2的立方體內(nèi)部屁置。如果使用的是透視攝像機排嫌,因為z?分量保留了深度信息,經(jīng)過透視除法以后缰犁,越遠離攝像機的頂點其xy分量數(shù)值將越小(因為w?分量是觀察空間中頂點的z?分量怖糊,對于兩個頂點帅容,如果他們x、y?分量相同伍伤,而w?分量不同即深度不同并徘,越遠的頂點經(jīng)過透視除法后,其數(shù)值也就越腥呕辍)麦乞。齊次除法完成后,頂點的x劝评、y?分量再經(jīng)過簡單的縮放映射即可投射到屏幕空間二維坐標中姐直,而z?分量通過d = 0.5*z + 0.5?轉變?yōu)?-1?范圍并作為最終會存貯于深度圖中的數(shù)據(jù)。
3.屏幕空間:
渲染管線中最后一個流程將裁剪空間中的頂點通過透視除法和屏幕映射映射最終從3D?的裁剪空間轉換到2D?的空間中蒋畜,這個2D?空間就是屏幕空間声畏。屏幕空間是左下角為(0,0),右上角為(screenwidth, screenheight)的二維空間。
Shader?中獲取深度(unity 2019.4.19 urp)
Shader?中將頂點從裁剪空間轉換到屏幕空間(即齊次除法和屏幕映射)由底層完成插龄,而獲取深度的原理就是再現(xiàn)這一操作中某些步驟的過程愿棋。
通過頂點數(shù)據(jù)可以直接獲得頂點的深度;通過頂點數(shù)據(jù)和屏幕映射公式可以逆推獲得屏幕紋理坐標均牢,使用屏幕紋理坐標可以采樣深度圖糠雨,然后將深度圖中的數(shù)據(jù)逆推回模型空間就可以獲得需要的場景深度。
1.頂點著色器中需要完成將頂點從模型空間轉換到裁剪空間下的任務徘跪。
2.來到片元著色器中甘邀,經(jīng)過Unity?渲染流程的處理,頂點輸出的裁剪空間頂點(SV_POSITION?語義)坐標的xy?分量已經(jīng)做了透視除法和屏幕映射處理轉換到了屏幕空間當中真椿,z 分量也做了透視除法并轉換到(0鹃答,1)范圍內(nèi),w?分量仍然是觀察空間下的深度值突硝。
獲取頂點的深度值:
裁剪空間頂點坐標w?分量就是視角空間下的深度值测摔,將其除以遠裁面就是0-1?的深度值。
遠裁剪面的距離可以通過內(nèi)置的_ProjectionParams.z?變量來獲取解恰。?
或者比較傻的辦法:
1.上面講到片元著色器中锋八,裁剪空間中的頂點(SV_POSITION?語義)坐標的z?分量已經(jīng)做了透視除法并轉換到了(0,1)范圍中护盈,此時只需要將其逆推回觀察空間即可挟纱;
2.利用公式:(n表示遠裁剪面;f表示近裁剪面腐宋;d表示ndc中重映射后的深度值紊服,即第2步驟計算出的數(shù)值)
zv?= 1/((n-f/n*f)*d + 1/n)?計算出觀察空間下的深度值,將其除以遠裁剪面就是0-1范圍的觀察空間下的深度值胸竞,公式:
z01=1/((n-f)/n*d + f/n)
以上公式實際上是將渲染管線處理后的頂點逆推回觀察空間中欺嗤,實際上是結合了投影矩陣z?分量上的處理公式、透視除法公式卫枝、屏幕映射公式三個步驟逆推出來的公式煎饼。
之所以說這個方法很傻,是因為繞了一個圈校赤,因為頂點從裁剪空間轉到屏幕空間下又逆推回了觀察空間吆玖,之所以要介紹這種辦法,是因為在遇到需要獲取場景深度值的需求時马篮,我們可以拿到的數(shù)據(jù)(即深度圖)存儲的正是步驟1?中頂點z?分量的值沾乘。
由于頂點在轉換到裁剪空間時,其w?分量就是觀察空間下的z?分量积蔚,如果只需要頂點的深度意鲸,還是建議直接使用裁剪空間頂點的w?分量作為深度值。
?
獲取場景的深度值:
1.上面提到在片元著色器中,頂點坐標的xy?分量已經(jīng)從裁剪空間轉換到了屏幕空間怎顾。
2.直接將頂點坐標的xy?分量除以屏幕的長寬就可以得到0-1?范圍的uv?坐標(屏幕紋理坐標)读慎,屏幕的長寬可以通過內(nèi)置變量_ScreenParams?獲取。
3.對深度圖進行采樣: SAMPLE_TEXTURE2D_X(_CamearaDepthTexture,?sampler_CameraDepthTexture,?uv)
采樣深度圖需要在shader?中作如下聲明:
TEXTURE2D_X_FLOAT(_CameraDepthTexture);
SAMPLER(sampler_CameraDepthTexture);
4.利用公式:(n表示遠裁剪面槐雾;f表示近裁剪面夭委;d表示深度圖中的數(shù)據(jù),即第四步結果的x分量)
zv?= 1/((n-f/n*f)*d + 1/n)?計算出模型空間下的深度值募强,將其除以遠裁剪面就是0-1范圍的深度值株灸,公式:
z01=1/((n-f)/n*d + f/n)
也可以在頂點著色器中計算采樣坐標,但是因為從頂點到片元著色器有一個插值過程,所以不能在頂點著色器中進行齊次除法擎值,因此需要乘回w?分量慌烧,計算公式:(vc 為裁剪空間下的頂點坐標、vcw為該坐標的w分量)
vcw*(vc/vcw*0.5+0.5) => 0.5vc+0.5vcw
這也是內(nèi)置函數(shù)ComputeScreenPos?的實現(xiàn)鸠儿。
然后在片元著色器中進行齊次除法獲得uv?坐標屹蚊,然后從步驟3繼續(xù)往下執(zhí)行
如果在頂點著色器使用了ComputeScreenPos獲得Vs;
那么對于步驟5可以使用unity?內(nèi)部提供的兩個方法Linear01Depth进每、LinearEyeDepth?傳入屏幕紋理坐標和_ZBufferParams?來獲取觀察空間的場景深度和0-1線性深度汹粤,函數(shù)內(nèi)部的原理即為上述步驟5。這也是shaderGraph?中SceneDepth?節(jié)點的做法田晚。_ZBufferParams?是Unity?提供的內(nèi)置變量嘱兼,里面包含了遠近裁剪平面相關的預計算。?同理贤徒,頂點的坐標也可以使用這種方式來獲得芹壕,只不過頂點的深度我們可以直接通過齊次除法來取得。
源碼
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;
//結合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
{
//頂點深度
half dv = i.vertex.w;
//頂點01 深度
half dv01 = i.vertex.w / _ProjectionParams.z;
//直接獲取屏幕紋理坐標
float2 screenUV = i.vertex.xy / _ScreenParams.xy;
//結合ComputeScreenPos 計算屏幕紋理坐標
//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
}
}
}