Search code examples
c#vectordirect3dprojection

Relationship between projected and unprojected Z-Values in Direct3D


I've been trying to figure this relationship out but I can't, maybe I'm just not searching for the right thing. If I project a world-space coordinate to clip space using Vector3.Project, the X and Y coordinates make sense but I can't figure out how it's computing the Z (0..1) coordinate. For instance, if my nearplane is 1 and farplane is 1000, I project a Vector3 of (0,0,500) (camera center, 50% of distance to far plane) to screen space I get (1050, 500, .9994785)

The resulting X and Y coordinates make perfect sense but I don't understand where it's getting the resulting Z-value.

I need this because I'm actually trying to UNPROJECT screen-space coordinates and I need to be able to pick a Z-value to tell it the distance from the camera I want the world-space coordinate to be, but I don't understand the relationship between clip space Z (0-1) and world-space Z (nearplane-farplane).

In case this helps, my transformation matrices are:

World = Matrix.Identity;

//basically centered at 0,0,0 looking into the screen
View = Matrix.LookAtLH(
  new Vector3(0,0,0), //camera position
  new Vector3(0,0,1), //look target
  new Vector3(0,1,0)); //up vector

Projection = Matrix.PerspectiveFovLH(
  (float)(Math.PI / 4), //FieldOfViewY
  1.6f, // AspectRatio
  1, //NearPlane
  1000); //FarPlane

Solution

  • Standard perspective projection creates a reciprocal relationship between the scene depth and the depth buffer value, not a linear one. This causes a higher percentage of buffer precision to be applied to objects closer to the near plane than those closer to the far plane, which is typically desired. As for the actual math, here's the breakdown:

    The bottom-right 2x2 elements (corresponding to z and w) of the projection matrix are:

    [far / (far - near)        ]  [1]
    [-far * near / (far - near)]  [0]
    

    This means that after multiplying, z' = z * far / (far - near) - far * near / (far - near) and w' = z. After this step, there is the perspective divide, z'' = z' / w'.

    In your specific case, the math works out to the value you got:

    z = 500
    z' = z * 1000 / (1000 - 999) - 1000 / (1000 - 999) = 499.499499499...
    w' = z = 500
    z'' = z' / w' = 0.998998998...
    

    To recover the original depth, simply reverse the operations:

    z = (far / (far - near)) / ((far / (far - near)) - z'')