Table of Contents

Inverse Depth

The engine renders with inverse depth: the near plane has a device depth of 1 and the far plane a device depth of 0. This is a fixed convention, there is no setting or shader permutation to change it. It is relevant for custom shaders, custom render passes and C++ code that works with projection matrices or screen space positions.

Value
Device depth at the near plane 1
Device depth at the far plane 0
Closer to the camera Larger device depth
Default depth clear value 0
Default depth comparison Greater
Depth comparison of materials GreaterEqual

In C++ ezGraphicsUtils::GetNearPlaneDeviceDepth() and ezGraphicsUtils::GetFarPlaneDeviceDepth() return the values for the near and far plane.

Custom Shaders

Common/CameraConstantsAccess.h provides functions for working with device depth. Prefer these over hard-coded values and comparisons:

Function Description
GetNearPlaneDeviceDepth() Returns 1.
GetFarPlaneDeviceDepth() Returns 0. Write this for background or sky geometry.
IsDeviceDepthCloser(a, b) a > b
IsDeviceDepthFarther(a, b) a < b
SelectClosestDeviceDepth(a, b) max(a, b)
SelectFarthestDeviceDepth(a, b) min(a, b)
ConvertDeviceDepthToLinearDepth(deviceDepth) Returns the camera-space Z value, not the distance from the camera.
ConvertLinearDepthToDeviceDepth(linearDepth) Requires a finite and positive value for perspective projections.
ReconstructCameraPositionFromDeviceDepth(ndcXY, deviceDepth) Works with all projections.
ReconstructWorldPositionFromDeviceDepth(ndcXY, deviceDepth) Works with all projections.
ReconstructPerspectiveCameraPositionFromDeviceDepth(ndcXY, deviceDepth) Assumes a centered perspective projection.
ReconstructPerspectiveCameraPositionFromLinearDepth(ndcXY, linearDepth) Assumes a centered perspective projection.

Take care of the following:

  • Use DepthTestFunc = CompareFunc_Greater or CompareFunc_GreaterEqual for regular geometry. See Shader Render State.
  • Configure comparison samplers for shadow maps with Greater.
  • Increasing a device depth moves it closer to the camera, which matters for manual depth biases.
  • Keep device depth and linear depth apart. To select the farther of two values, use min for device depth, but max for linear depth. See Pipeline/DownscaleDepth.ezShader for an example.
  • Depth comparisons that are stored explicitly in a Visual Shader are used as they are. They don't get adjusted to this convention.

Depth Bias

The DepthBias, SlopeScaledDepthBias and DepthBiasClamp render states, as well as the respective members of ezGALRasterizerStateCreationDescription, are passed to the graphics API as they are. Positive values therefore pull a surface towards the camera.

The higher level options are defined by their physical effect and take care of the depth direction themselves:

Option Effect of positive values
GetVertexDepthBias() in a material shader Pushes the surface away from the camera.
DepthBias parameter of a mesh decal material Pulls the surface towards the camera.
OcclusionDepthOffset of a lens flare Moves the occlusion sample away from the camera.
SortingDepthOffset of a mesh Increases the sorting distance. Doesn't affect the depth buffer.

ConstantBias and SlopeBias of shadow casting lights reduce shadow artifacts independently of the depth direction.

Cameras and Projection Matrices

ezCamera::SetCameraMode() takes physical distances with near < far, and GetNearPlane() and GetFarPlane() return those. Perspective cameras can use an infinite far distance, orthographic cameras require finite distances. ezCamera::GetProjectionMatrix() maps the near plane to device depth 1 and the far plane to 0.

The projection matrices passed to ezCamera::SetStereoProjection() must already use inverse depth and ezClipSpaceDepthRange::Default. Conventional matrices result in incorrect depth testing and position reconstruction. They are only used for a matching aspect ratio, other aspect ratios use a symmetric perspective projection instead.

The functions ezGraphicsUtils::CreatePerspectiveProjectionMatrix*() and CreateOrthographicProjectionMatrix() derive the depth direction from their Z plane arguments. To create a matrix for rendering directly, pass the far distance as fNearZ and the near distance as fFarZ:

const ezMat4 projection = ezGraphicsUtils::CreatePerspectiveProjectionMatrixFromFovY(fov, aspectRatio, farPlane, nearPlane);

Screen and World Space Conversion

  • ezGraphicsUtils::ConvertWorldPosToScreenPos() returns z = 1 at the near plane and z = 0 at the far plane. Points outside of the frustum can produce values outside of this range, also for points behind the camera. To determine whether a point is in front of the camera, test its camera-space Z value, not the sign of the returned depth.
  • ezGraphicsUtils::ConvertScreenPosToWorldPos() expects the same mapping. To create a picking ray at the near plane, use z = 1. The optional direction can only be computed with an inverse depth matrix. A depth that unprojects to infinity, such as 0 with an infinite far plane, returns a failure.
  • ezViewData::ComputePickingRay() and ComputeWorldSpacePos() use the near plane according to this convention.
  • ezGraphicsUtils::ComputeInterpolatedFrustumPlane() requires an inverse depth matrix for NearToFar. A lerp factor of 0 selects the near plane.
  • The depth arguments of the Draw2D functions of ezDebugRenderer are device depths. They only have an effect if a depth target is bound.

See Also