知乎上一篇很好的文章:反向Z(Reversed-Z)的深度缓冲原理 - 知乎
先只考虑投影后裁剪空间的深度坐标 z 范围,有:
也就是说,OpenGL 平台的投影矩阵会和其它平台不一样,而几篇经典的文章(一个例子)都是推的 OpenGL 的投影矩阵:
而对应 D3D 等其它平台的投影矩阵为
当然投影矩阵还会跟参考坐标系有关系,例如 OpenGL 使用的是右手坐标系,但是在NDC中使用的是左手坐标系,因此构造投影矩阵时,需要将 near 和 far 取负
而如今的游戏中,往往会使用近处 1.0,远处 0.0 的深度分布,Unity 引擎中,某些 Graphics API 上会默认开启这样的改动,这一块可以直接参考官方手册:
使用反向 z 的好处是可以均匀深度值 z 的精度,使得无论接近近裁平面还是远裁平面,精度都能得到一定的保证:这主要是从这两点考量的:
而反向 Z 就可以完美的使这两个性质做到互补,而非要不都满足精度高的条件,要不都不满足以带来更大的精度不平衡
Unity shader 开发时,在使用深度发生反转的平台上的着色器满足条件:
因此如果要手动提取深度缓冲区值,则可能需要检查缓冲区方向
- float z = tex2D(_CameraDepthTexture, uv);
- # if defined(UNITY_REVERSED_Z)
- z = 1.0f - z;
- # endif
如果你想自己计算投影矩阵,就要非常小心了,如果处于反向 Z 的平台上,则 GL.GetGPUProjectionMatrix() 输入当前矩阵可以给你提供一个还原反向 Z 的矩阵,但如果要手动从投影矩阵中进行合成(例如,对于自定义阴影或深度渲染),则需要通过脚本按需自行还原深度 方向
当然还会有一些情况:比如我的深度需要用于参与计算,而非用于深度检测,这时就需要你指定一个正确的投影矩阵,并且要求是一个平台无关的投影矩阵,就像下面这个例子一样:
- // 无视平台的转换投影矩阵到GL格式
- private static Matrix4x4 GetGPUProjMatrix(Matrix4x4 p)
- {
- p[2, 0] = p[2, 0] * (-0.5f) + p[3, 0] * 0.5f;
- p[2, 1] = p[2, 1] * (-0.5f) + p[3, 1] * 0.5f;
- p[2, 2] = p[2, 2] * (-0.5f) + p[3, 2] * 0.5f;
- p[2, 3] = p[2, 3] * (-0.5f) + p[3, 3] * 0.5f;
- return p;
- }
-
- public static void GetShadowMatrix(Vector2 size, Vector2 worldHeight, Vector2 worldOffset, Vector2 lightDirection, out Matrix4x4 m, out Matrix4x4 p)
- {
- m = Matrix4x4.TRS(new Vector3(-size.x * 0.5f + worldOffset.x, -size.y * 0.5f + worldOffset.y, 0.0f), Quaternion.Euler(-90, 0, 0), Vector3.one);
- p = Matrix4x4.Ortho(-size.x * 0.5f, size.x * 0.5f, -size.y * 0.5f, size.y * 0.5f, worldHeight.x, worldHeight.y);
- float z = Mathf.Sqrt(1 - lightDirection.x * lightDirection.x - lightDirection.y * lightDirection.y);
- p[0, 2] = -(lightDirection.x/z) / size.x * 2.0f;
- p[1, 2] = -(lightDirection.y/z) / size.y * 2.0f;
- // ESM 的阴影是在DX11下烘焙的 这里将ESM_Matrix转换成了GL的矩阵格式
- // GLES3.0 的绘制模式下没有做翻转,但是实际需要使用翻转后的矩阵
- // GL.GetGPUProjectionMatrix 接口只会转DX到GL 遇到GL时直接不做处理
- // 为了平台数据一致 直接统一转换
- // p = GL.GetGPUProjectionMatrix(p, false);
- p = GetGPUProjMatrix(p);
- }
由于 Matrix4x4.Ortho 函数返回的投影矩阵默认裁剪后 z 范围为 [-1, 1],并且不会考虑反向 Z,因此如果你想使最后深度在 [0, 1] 的范围内,就需要自行进行转换,由于深度计算和平台无关,因此不能直接调用 GL.GetGPUProjectionMatrix() ,它在 OpenGL 平台上会不起作用