Projection Matrix, NDC Space and Reversed-Z

1. Different platform APIs, different strategies

Know the last good article: Depth Buffer Principle of Reverse Z (Reversed-Z)- Know about

First consider only the depth coordinate z range of the clipping space after projection, there are:

  • Direct3D / Metal / various game consoles : [0, 1] / [0, far]
  • OpenGL classes : [-1, 1] / [-near, far]

In other words, the projection matrix of the OpenGL platform will be different from other platforms, and several classic articles ( an example ) are pushing the projection matrix of OpenGL:

\left(\begin{array}{cccc} \frac{2 n}{r-l} & 0 & \frac{r+l}{r-l} & 0 \\ 0 & \frac{2 n}{t-b} & \frac{t+b}{t-b} & 0 \\ 0 & 0 & \frac{-(f+n)}{f-n} & \frac{-2 f n}{f-n} \\ 0 & 0 & -1 & 0 \end{array}\right)

The projection matrix corresponding to other platforms such as D3D is

\left(\begin{array}{cccc} \frac{2 n}{r-l} & 0 & \frac{-r-l}{r-l} & 0 \\ 0 & \frac{2 n}{t-b} & \frac{-t-b}{t-b} & 0 \\ 0 & 0 & \frac{f}{f-n} & \frac{-f n}{f-n} \\ 0 & 0 & 1 & 0 \end{array}\right)

Of course, the projection matrix is ​​also related to the reference coordinate system. For example, OpenGL uses a right-handed coordinate system, but NDC uses a left-handed coordinate system. Therefore, when constructing a projection matrix, you need to take near and far negative.

1.1 Reverse Z (Reversed-Z)

In today's games, the depth distribution of 1.0 near and 0.0 far away is often used. In the Unity engine, some Graphics APIs will enable such changes by default. You can directly refer to the official manual for this one :

  • DirectX 11 / DirectX 12 / Metal: Reversed direction:[1, 0] / [near, 0]
  • OpenGL : [-1, 1] / [-near, far] (this range has no effect if the reverse Z is used; but the clipping range can be modified to [0.0, 1.0] through the 4.5 version of the API, so that reverse Z can also be used to Z)
  • Other Direct3D-like platforms that don't use inverted z : [0, 1] / [0, far]

The advantage of using reverse z is that the accuracy of the depth value z can be uniformed, so that the accuracy can be guaranteed no matter whether it is close to the near clipping plane or the far clipping plane: this is mainly considered from two points:

  • After the calculation of the projection matrix itself, the depth z in NDC space is not linearly related to the actual depth. The closer to the near clipping plane, the smaller the distribution density of z and the higher the accuracy
  • Another point that is easily overlooked is the principle of floating-point numbers : for floating-point numbers closer to 0, the higher the precision

The reverse Z can perfectly make these two properties complement each other, instead of either satisfying the condition of high precision, or not satisfying both to bring greater precision imbalance

1.2 Back to game development, what you should notice

When the Unity shader is developed, the shader on the platform where the depth is inverted meets the conditions:

  • UNITY_REVERSED_Z is defined.
  • The texture range of the _CameraDepth texture is [1, 0]
  • The clipping space range is [1, 0] / [near, 0]

So if you want to manually extract the depth buffer value, you may need to check the buffer orientation

float z = tex2D(_CameraDepthTexture, uv);
# if defined(UNITY_REVERSED_Z)
    z = 1.0f - z;
# endif

If you want to calculate the projection matrix yourself, be very careful. If you are on a reverse Z platform,  GL.GetGPUProjectionMatrix()  can provide you with a matrix that restores the reverse Z if you are on a reverse Z platform. Matrix (for example, for custom shadows or depth rendering), you need to restore the depth direction yourself on demand through scripts

Of course, there will be some situations: for example, my depth needs to be used for participating in calculations, not for depth detection. At this time, you need to specify a correct projection matrix, and the requirement is a platform-independent projection matrix, like the following Same as example:

 // 无视平台的转换投影矩阵到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);
}

Since  the projection matrix returned by the Matrix4x4.Ortho function defaults to the range of [-1, 1] after clipping, and does not consider the reverse Z, if you want the final depth to be in the range of [0, 1], you need to do it yourself For conversion, since the depth calculation is independent of the platform,  GL.GetGPUProjectionMatrix() cannot be called directly  , and it will not work on the OpenGL platform

Guess you like

Origin blog.csdn.net/Jaihk662/article/details/126876203