談起深度信息,我第一個反應就是陳嘉棟大大寫的# 神奇的深度圖:復雜的效果缔刹,不復雜的原理振湾,最近看到SSAO技術(shù)里面也會用到深度信息杀迹,去重建屏幕上像素在世界空間下的坐標,閱讀相關(guān)代碼時看到重建這個信息的代碼很簡單:
float depthTextureValue = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
//自己操作深度的時候押搪,需要注意Reverse_Z的情況
#if defined(UNITY_REVERSED_Z)
depthTextureValue = 1 - depthTextureValue;
#endif
float4 ndc = float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, depthTextureValue * 2 - 1, 1);
float4 worldPos = mul(_InverseVPMatrix, ndc);
worldPos /= worldPos.w;
就是采樣深度圖树酪,將得到的數(shù)據(jù)對應到[-1,1]之間(坐標是在這個區(qū)間),然后乘以一個的逆矩陣大州,最后除以就好了续语。那么,這到底怎么來的厦画,這篇博客就來推導下疮茄。
首先對于這個問題,我們有哪些已知信息根暑,這是很重要的×κ裕現(xiàn)在已知屏幕上的像素坐標(用或者表達都行),深度信息也是已知的排嫌,矩陣已知則其逆矩陣也已知畸裳。到此,我們嘗試推導下能不能重建世界坐標淳地。
已知:怖糊,
下的現(xiàn)在已知帅容,那么我們只需要知道就能知道像素所對應的剪裁空間坐標,那么得到世界空間坐標就易如反掌了伍伤。
現(xiàn)在問題來了并徘,根據(jù)上面的已知條件,似乎沒法知道是多少啊扰魂。麦乞。。
這里其實我們已知的信息除了上面所說外阅爽,還有一個很重要的信息路幸,就是世界空間坐標下。有了這個以后付翁,我們就能繼續(xù)推導了简肴。
已知:,
那么:
=>
=> (式子1)
到了式子1這一步百侧,世界空間下的功能就要發(fā)揮出來了砰识。
已知:
那么:
為什么會這樣呢?是個標量佣渴,是個辫狼,有四個分量,其中的分量乘以是1辛润,以對應世界空間下膨处,所以有了式子1。
所以:
現(xiàn)在知道了砂竖,將它代入式子1就可以算出世界坐標了真椿。
最終:
現(xiàn)在代碼中的為什么要這么寫就應該很清楚了。
2020.11.26更新
上面的方法雖然直觀乎澄,但涉及到矩陣運算突硝,效率上會有些影響。那么有沒有不涉及矩陣運算也能得出世界坐標的方法呢置济?有解恰!具體代碼如下
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UnityStereoScreenSpaceUVAdjust(i.uv, _CameraDepthTexture_ST));
float3 wp = _WorldSpaceCameraPos.xyz + i.ray * Linear01Depth(depth);
我們需要一個相機在世界坐標下的位置,深度信息浙于,以及射線信息就能得出世界坐標了护盈。那么有人要問了,深度信息我知道羞酗,可以從深度圖中獲取腐宋,相機信息引擎也提供,那么這個射線信息是什么鬼呢?還有為什么這樣可以得出世界坐標呢脏款?
我們先來解決一個問題,上面這個式子怎么來的裤园。根據(jù)Secrets of CryENGINE 3 Graphics Technology這個PPT來看撤师,我們可以這么來理解。
上面是PPT中的原圖拧揽,下面我自己手繪了一下二維版的剃盾,這樣理解起來就相對容易。
作為攝像機淤袜,為遠剪裁面痒谴,前面那條豎線是近剪裁面(忘記標記名字了= =),我們需要算出點的位置铡羡,那么容易得到(為向量积蔚,即為我們想要計算的世界坐標),現(xiàn)在該怎么計算呢烦周?我們知道尽爆,如果將深度限制在空間中時,读慎,所以上面代碼在使用深度時使用Linear01Depth
將深度轉(zhuǎn)換到空間下漱贱。并且由相似三角形定理得,現(xiàn)在夭委,那么幅狮,即為空間的深度,那么≈昃模現(xiàn)在問題轉(zhuǎn)化為怎么求崇摄,而這個代表著攝像機到屏幕上每個像素的射線。針對屏幕后處理來說蚂且,我們實際上只是在渲染一個Quad配猫,那么我們只需要通過攝像頭上的一些參數(shù),將攝像機屏幕上的四個頂點計算出來杏死,然后利用光柵化的插值功能泵肄,即可得到我們想要的射線了。
C#計算四個頂點的代碼為
cam = GetComponent<Camera>();
cam.depthTextureMode |= DepthTextureMode.Depth;
camTrans = cam.transform;
Matrix4x4 frustumCornors = Matrix4x4.identity;
float fov = cam.fieldOfView;
float near = cam.nearClipPlane;
float far = cam.farClipPlane;
float aspect = cam.aspect;
float fovWHalf = fov * 0.5f;
Vector3 toRight = camTrans.right * near * Mathf.Tan (fovWHalf * Mathf.Deg2Rad) * aspect;
Vector3 toTop = camTrans.up * near * Mathf.Tan (fovWHalf * Mathf.Deg2Rad);
Vector3 topLeft = (camTrans.forward * near - toRight + toTop);
float camScale = topLeft.magnitude * far/near;
topLeft.Normalize();
topLeft *= camScale;
Vector3 topRight = (camTrans.forward * near + toRight + toTop);
topRight.Normalize();
topRight *= camScale;
Vector3 bottomRight = (camTrans.forward * near + toRight - toTop);
bottomRight.Normalize();
bottomRight *= camScale;
Vector3 bottomLeft = (camTrans.forward * near - toRight - toTop);
bottomLeft.Normalize();
bottomLeft *= camScale;
frustumCornors.SetRow(0, bottomLeft);
frustumCornors.SetRow(1, bottomRight);
frustumCornors.SetRow(2, topRight);
frustumCornors.SetRow(3, topLeft);
mat.SetMatrix("_Ray", frustumCornors);
Shader這邊只要這樣處理就行了
SubShader {
ZWrite Off
ZTest Always
Cull Off
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 ray : TEXCOORD1;
};
v2f vert (appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv,_MainTex);
o.uvDepth = v.uvDepth;
int index = 0;
if (v.uv.x < 0.5 && v.uv.y < 0.5){
index = 0;
}else if (v.uv.x > 0.5 && v.uv.y < 0.5){
index = 1;
}else if (v.uv.x > 0.5 && v.uv.y > 0.5){
index = 2;
}else{
index = 3;
}
o.ray = _Ray[index].xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target {
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UnityStereoScreenSpaceUVAdjust(i.uv, _CameraDepthTexture_ST));
float3 wp = _WorldSpaceCameraPos.xyz + i.ray * Linear01Depth(depth);
return fixed4(wp,1.0);
}
ENDCG
}
}
參考
Unity Shader 深度值重建世界坐標
Unity Shader-深度相關(guān)知識總結(jié)與效果實現(xiàn)(LinearDepth淑翼,Reverse Z腐巢,世界坐標重建,軟粒子玄括,高度霧冯丙,運動模糊,掃描線效果)
How to go from device coordinates back to worldspace in OpenGL (with explanation)