Reconstruct World Position from Depth in GLSL

In modern graphics pipelines and deferred rendering, reconstructing 3D world space coordinates directly from the depth buffer avoids the high memory bandwidth cost of storing world positions in a separate geometry buffer. The inverse view-projection matrix plays the central role in this process by mathematically reversing the standard rendering transformation pipeline, translating 2D screen coordinates and non-linear depth values from Normalized Device Coordinates (NDC) back into absolute 3D world space within a GLSL shader.

The Forward vs. Inverse Transformation Pipeline

To understand the inverse process, consider standard forward rendering. A vertex position moves through coordinate systems in this order:

  1. World Space: The global coordinate system of the scene.
  2. View Space: Coordinates relative to the camera's position and orientation (via the View Matrix).
  3. Clip Space: Coordinates scaled and projected, incorporating perspective division limits (via the Projection Matrix).
  4. Normalized Device Coordinates (NDC): The coordinates resulting from dividing Clip Space coordinates by their \(W\) component, yielding a cube ranging from \([-1, 1]\) on the \(X, Y,\) and \(Z\) axes (in OpenGL).
  5. Screen/Window Space: The final pixel coordinates mapped onto the viewport.

The combined view-projection matrix (\(P \times V\)) flattens the 3D world into clip coordinates. Therefore, its algebraic inverse—the inverse view-projection matrix (\((P \times V)^{-1}\))—performs the exact opposite function. Given a coordinate in NDC, multiplying it by the inverse view-projection matrix restores the coordinate to homogeneous world space.

Step-by-Step Reconstruction Process

Reconstructing world position requires three discrete mathematical steps inside a fragment shader:

1. Derive NDC Coordinates

The fragment shader samples the depth texture using screen-space UV coordinates (ranging from \(0.0\) to \(1.0\)). These coordinates, along with the sampled depth value, must be remapped to the OpenGL NDC range of \([-1.0, 1.0]\).

vec2 uv = gl_FragCoord.xy / u_Resolution;
float depth = texture(u_DepthTexture, uv).r;

// Remap UV and depth from [0, 1] to [-1, 1]
vec4 ndc = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);

2. Apply the Inverse View-Projection Matrix

Multiplying the NDC coordinate vector by the inverse view-projection matrix transforms the coordinate back through the projection and view stages simultaneously.

vec4 worldSpaceHomogeneous = u_InverseViewProjection * ndc;

3. Perform the Perspective Divide

Because the projection matrix encodes perspective scaling into the \(W\) component, the matrix multiplication results in a homogeneous coordinate where \(W \neq 1.0\). Dividing the vector by its own \(W\) component yields the final Cartesian world position.

vec3 worldPosition = worldSpaceHomogeneous.xyz / worldSpaceHomogeneous.w;

Complete GLSL Implementation

Below is a modular GLSL function illustrating the complete reconstruction:

uniform mat4 u_InverseViewProjection;
uniform sampler2D u_DepthTexture;

vec3 reconstructWorldPosition(vec2 texCoord) {
    // Sample depth buffer (0.0 to 1.0)
    float depth = texture(u_DepthTexture, texCoord).r;

    // Convert Screen Space / UV to NDC (-1.0 to 1.0)
    vec4 ndcPos = vec4(
        texCoord * 2.0 - 1.0,
        depth * 2.0 - 1.0,
        1.0
    );

    // Unproject using the inverse view-projection matrix
    vec4 worldPos = u_InverseViewProjection * ndcPos;

    // Perspective divide
    return worldPos.xyz / worldPos.w;
}

Key Advantages and Considerations

  • Bandwidth Optimization: Rendering a standard deferred G-buffer requires writing position data to a dedicated RGB32F or RGB16F render target. Reconstructing position from depth reduces G-buffer bandwidth significantly, requiring only a single depth attachment.
  • Precision Constraints: Because the depth buffer distributes floating-point precision non-linearly (clustering precision near the near plane), small precision errors can occur at distant geometry. Using a 24-bit or 32-bit floating-point depth buffer, or implementing a reversed-\(Z\) depth buffer, mitigates potential precision loss during reconstruction.
  • Applicability: Beyond deferred shading, this technique is standard for screen-space ambient occlusion (SSAO), screen-space reflections (SSR), deferred decal rendering, and volumetric fog.