在Unity编辑器中实际上是左手和右手2套坐标系共存

物体空间坐标系为左手坐标系

选中任意物体显示的坐标轴gizmos, 以及给物体附加Vector3.forward。 都可以确定物体空间的的正方向的为 +Z方向

而相机空间坐标系确为右手坐标系

选中相机后,提示的坐标轴gizmos 显示相机的观察方向实际上是 -z
因此假设相机在0,0,0 位置, 当我们将一个 0,0,10 的世界坐标物体,变换到相机空间时,该物体相对相机实际上是在 0,0,-10
Matrix4x4 camSpace = Camera.main.worldToCameraMatrix;
Debug.Log(camSpace.MultiplyPoint(new Vector3(0, 0, 10)));
也就是说所有相机可见物体都是在相机身后

那么既然相机观察矩阵坐标系是右手坐标系
那么拍出来的深度图中存储的深度值又是怎样的呢

我们直接使用depth进行渲染
float depth = tex2D(_CameraDepthTexture, screenPos).r;
return depth;
从图中可以看到距离相机越远的物体,深度趋向0, 距离相机越近趋向1
这是由于所有物体在相机的右手坐标系下位于身后,而物体按Z轴/ZFar 排序的话,区间为 0~-1. 而纹理必须正数,估计Unity在渲染深度图时,直接 1 – Depth 并存储,因此会出现近距离1,远距离0这样相反的情况
而当我们使用Linear01 变换深度到0~1区间时,可以说是将深度从1~0 翻转为 0~1, 这也就是为什么很多人使用Linear01来调试深度图时,距离近的反而显示为黑色,这对于新人来说很容易有误导,实际上要考虑相机的左右手坐标系情况

Linear01 的算法为 return 1.0f / (_ZBufferParams.x * z + _ZBufferParams.y);
其中 _ZBufferParams.x = (1.0f – myCamera.farClipPlane / myCamera.nearClipPlane)
_ZBufferParams.y = myCamera.farClipPlane / myCamera.nearClipPlane;

在后处理特效中需要大量使用深度图还原世界真实坐标的情况
大致思路为,将深度通过 projective -> cameraView -> world 还原回世界坐标
从细节上来说,首先深度图中的深度存储的值为0~1 而ndc空间中的坐标系为 -1~1. 因此第一步是值域变换, 既 depth * 2 – 1. 但是由于unity 为右手坐标系,而unity本地坐标系左手坐标系,因此我们在右手坐标系下还原depth要从0~1 变换回0~-1。
fixed4 clipPos = fixed4(screenPos.x * 2 – 1, screenPos.y * 2 – 1, -depth * 2 + 1, 1);
//还原回相机空间
//fixed4 cameraSpacePos = mul(unity_CameraInvProjection, clipPos);
//还原回世界空间
//fixed4 worldSpacePos = mul(unity_MatrixInvV, cameraSpacePos);

如果觉得要变换两次在shader中消耗比较大,也可以外部直接算好传入
var projMatrix = Camera.main.projectionMatrix;
var m = projMatrix * Camera.main.worldToCameraMatrix;
float4x4 projectorToWorld = m.m.inverse;

之后shader中直接变换
fixed4 worldSpacePos = mul(_ProjectorToWorld, clipPos);

说道 Camera.main.projectionMatrix, 在U3d的论坛上有人讨论是不推荐使用,更推荐的是GL.GetGPUProjectionMatrix(Camera.main.projectionMatrix, false);
根据测试在 FrameDebugger里的MatrixP 和 Camera.main.projectionMatrix 值是不同的,和 GL.GetGPUProjectionMatrix(Camera.main.projectionMatrix, false) 值确实相同的
根据资料说明, Camera.main.projectionMatrix 会根据你设备驱动变化,比如DX下是Z值域 (0,1),OPENGL是 (-1,1)等, 而GL.GetGPUProjectionMatrix会将任意驱动下的矩阵变成opengl的矩阵, 不过由于我么有别的设备测试Camera.main.projectionMatrix到底都会变成啥,因此安全起见,都使用GL.GetGPUProjectionMatrix

Tips:一旦使用GL.GetGPUProjectionMatrix 基于左手坐标系的矩阵,在变换深度时直接 fixed4 clipPos = fixed4(screenPos.x * 2 – 1, screenPos.y * 2 – 1, depth, 1); 即可,深度不需要反算了,直接变换即可。

除了通过矩阵变换深度回世界坐标方法外
还有一种方法,使用LinearEyeDepth 将深度转为距离相机的距离
之后通过 相机朝向当前屏幕空间的像素的 向量 * 深度距离 + 相机世界坐标位置来获取该像素深度的世界坐标。
而向量的计算则是通过计算相机近平面4个角的向量,然后根据uv区块选择向量。 :D不过个人角色这种方案获取的深度不太准确,不过性能应该会稍好,毕竟不需要逐像素计算矩阵乘法
像之前的高度雾就是使用这种方式