Search code examples
c++openglglm-math

Why z-coordinate is not normalized by glm::ortho() projection?


I'm trying to better understand how glm::ortho works, the source code (v 0.9.7) looks like this:

template <typename T>
GLM_FUNC_QUALIFIER tmat4x4<T, defaultp> ortho
(
    T left,
    T right,
    T bottom,
    T top,
    T zNear,
    T zFar
)
{
    tmat4x4<T, defaultp> Result(1);
    Result[0][0] = static_cast<T>(2) / (right - left);
    Result[1][1] = static_cast<T>(2) / (top - bottom);
    Result[2][2] = - static_cast<T>(2) / (zFar - zNear);
    Result[3][0] = - (right + left) / (right - left);
    Result[3][1] = - (top + bottom) / (top - bottom);
    Result[3][2] = - (zFar + zNear) / (zFar - zNear);
    return Result;
}

Everything is alright and works perfectly, but one thing is worrying me. Why does the resulting matrix normalize coordinates 'x' and 'y' to [-1,1] (it concerns points inside the view area), but not 'z' (depth)?

We could get rid of minus before Result[3][2] statement, and then we would have also z-values in range [-1,1] (Ortho projection in wikipedia).

Instead, all points in front of camera has z-value lower than -1, but why? Is this because of some optimization issues? Or is there any other reason to make things less intuitive?


Solution

  • It does map into the [-1, 1] interval for z. However, the values that are mapped to this range are between -zNear and -zFar.

    Extracting the part of the transformation for z:

    z --> -2 * z / (zFar - zNear) - (zFar + zNear) / (zFar - zNear) =
          (-2 * z - zFar - zNear) / (zFar - zNear)
    

    and inserting -zNear and -zFar:

    -zNear --> (2 * zNear - zFar - zNear) / (zFar - zNear) =
               (zNear - zFar) / (zFar - zNear) =
               -1
    -zFar --> (2 * zFar - zFar - zNear) / (zFar - zNear) =
              (zFar - zNear) / (zFar - zNear) =
              1
    

    Why do we want to map the negative z values? The common policy in OpenGL is that, after the model/view transformations have been applied, you're in the "eye coordinate" system. In this coordinate system, the "camera" is at the origin, and pointing down the negative z-axis. So the visible z values are negative, and the points at distance zNear and zFar from the eye point (the origin) are at -zNear and -zFar.

    Now, particularly when using the programmable pipeline, nothing forces you to follow this policy. You can define and apply your transformation in any way you want, as long as they end up producing NDC (Normalized Device Coordinates) in the correct range. Many people still use similar coordinate systems as the ones that were used in the fixed pipeline, and GLM was written to supports this.

    Going one step farther, you may wonder why the eye coordinate system was defined this way, with the camera pointing down the negative z-axis. The main reason is that this gives you a right-handed coordinate system. Right-handed coordinate systems are pretty much standard in many applications, as well as in geometry. Therefore, most people prefer using a right-handed coordinate systems.

    Once you decided to use a right-handed coordinate system, much of this pretty much falls into place. Having the x-axis go left to right is standard in almost any coordinate system people want to use. The y-axis is more ambiguous, going top to bottom in some graphics systems/libraries, bottom to top in others. In math/geometry, the y-axis is mostly drawn bottom to top, which is what OpenGL is using.

    With the x-axis going left to right, and the y-axis bottom to top, the z-axis has to point out of the screen for x/y/z to form a right-handed coordinate system. And with the positive z-axis coming out of the screen, you look in the direction of the negative z-axis when you're looking into the screen.

    There's an alternate way of explaining the same thing: The native OpenGL coordinate system (NDC) is left-handed. If you want to specify coordinates in a right-handed coordinate system, the handedness has to be flipped somewhere in the pipeline. The common approach is to flip the coordinate system as part of the projection transform. This is why the z-coordinate is treated differently from the x- and y-coordinates in the projection transform. Inverting the z-coordinates flips the coordinate system from right-handed to the native left-handed OpenGL coordinate system.