谈起深度信息,我第一个反应就是陈嘉栋大大写的# 神奇的深度图:复杂的效果,不复杂的原理,最近看到SSAO技术里面也会用到深度信息,去重建屏幕上像素在世界空间下的坐标,阅读相关代码时看到重建这个信息的代码很简单:
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;
就是采样深度图,将得到的数据对应到[-1,1]之间(坐标是在这个区间),然后乘以一个
的逆矩阵,最后除以
就好了。那么,这到底怎么来的,这篇博客就来推导下。
首先对于这个问题,我们有哪些已知信息,这是很重要的。现在已知屏幕上的像素坐标(用或者
表达都行),深度信息
也是已知的,
矩阵已知则其逆矩阵也已知。到此,我们尝试推导下能不能重建世界坐标。
已知:,
下的
现在已知,那么我们只需要知道
就能知道像素所对应的剪裁空间坐标,那么得到世界空间坐标就易如反掌了。
现在问题来了,根据上面的已知条件,似乎没法知道是多少啊。。。
这里其实我们已知的信息除了上面所说外,还有一个很重要的信息,就是世界空间坐标下。有了这个以后,我们就能继续推导了。
已知:,
那么:
=>
=> (式子1)
到了式子1这一步,世界空间下的功能就要发挥出来了。
已知:
那么:
为什么会这样呢?是个标量,
是个
,有
四个分量,其中
的分量乘以
是1,以对应世界空间下
,所以有了式子1。
所以:
现在知道了,将它代入式子1就可以算出世界坐标了。
最终:
现在代码中的为什么要这么写就应该很清楚了。
2020.11.26更新
上面的方法虽然直观,但涉及到矩阵运算,效率上会有些影响。那么有没有不涉及矩阵运算也能得出世界坐标的方法呢?有!具体代码如下
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UnityStereoScreenSpaceUVAdjust(i.uv, _CameraDepthTexture_ST));
float3 wp = _WorldSpaceCameraPos.xyz + i.ray * Linear01Depth(depth);
我们需要一个相机在世界坐标下的位置,深度信息,以及射线信息就能得出世界坐标了。那么有人要问了,深度信息我知道,可以从深度图中获取,相机信息引擎也提供,那么这个射线信息是什么鬼呢?还有为什么这样可以得出世界坐标呢?
我们先来解决一个问题,上面这个式子怎么来的。根据Secrets of CryENGINE 3 Graphics Technology这个PPT来看,我们可以这么来理解。
![](https://img.haomeiwen.com/i19490537/ea487e54879e47a4.jpg)
上面是PPT中的原图,下面我自己手绘了一下二维版的,这样理解起来就相对容易。
![](https://img.haomeiwen.com/i19490537/a8fcc8195aa7c8f4.jpeg)
作为摄像机,
为远剪裁面,前面那条竖线是近剪裁面(忘记标记名字了= =),我们需要算出
点的位置,那么容易得到
(
为向量,
即为我们想要计算的世界坐标),现在
该怎么计算呢?我们知道,如果将深度限制在
空间中时,
,所以上面代码在使用深度时使用
Linear01Depth
将深度转换到空间下。并且由相似三角形定理得
,现在
,那么
,
即为
空间的深度
,那么
。现在问题转化为
怎么求,而这个
代表着摄像机到屏幕上每个像素的射线。针对屏幕后处理来说,我们实际上只是在渲染一个Quad,那么我们只需要通过摄像头上的一些参数,将摄像机屏幕上的四个顶点计算出来,然后利用光栅化的插值功能,即可得到我们想要的射线
了。
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-深度相关知识总结与效果实现(LinearDepth,Reverse Z,世界坐标重建,软粒子,高度雾,运动模糊,扫描线效果)
How to go from device coordinates back to worldspace in OpenGL (with explanation)
网友评论