Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting World Position from Depth Buffer Value

Tags:

c++

opengl

I've been working on a deferred renderer to do lighting with, and it works quite well, albeit using a position buffer in my G-buffer. Lighting is done in world space.

I have tried to implement an algorithm to recreate the world space positions from the depth buffer, and the texture coordinates, albeit with no luck.

My vertex shader is nothing particularly special, but this is the part of my fragment shader in which I (attempt to) calculate the world space position:

// Inverse projection matrix
uniform mat4 projMatrixInv;
// Inverse view matrix
uniform mat4 viewMatrixInv;

// texture position from vertex shader
in vec2 TexCoord;

... other uniforms ...

void main() {
    // Recalculate the fragment position from the depth buffer
    float Depth = texture(gDepth, TexCoord).x;
    vec3 FragWorldPos = WorldPosFromDepth(Depth);

    ... fun lighting code ...
}

// Linearizes a Z buffer value
float CalcLinearZ(float depth) {
    const float zFar = 100.0;
    const float zNear = 0.1;

    // bias it from [0, 1] to [-1, 1]
    float linear = zNear / (zFar - depth * (zFar - zNear)) * zFar;

    return (linear * 2.0) - 1.0;
}

// this is supposed to get the world position from the depth buffer
vec3 WorldPosFromDepth(float depth) {
    float ViewZ = CalcLinearZ(depth);

    // Get clip space
    vec4 clipSpacePosition = vec4(TexCoord * 2.0 - 1.0, ViewZ, 1);

    // Clip space -> View space
    vec4 viewSpacePosition = projMatrixInv * clipSpacePosition;

    // Perspective division
    viewSpacePosition /= viewSpacePosition.w;

    // View space -> World space
    vec4 worldSpacePosition = viewMatrixInv * viewSpacePosition;

    return worldSpacePosition.xyz;
}

I still have my position buffer, and I sample it to compare it against the calculate position later, so everything should be black:

vec3 actualPosition = texture(gPosition, TexCoord).rgb;
vec3 difference = abs(FragWorldPos - actualPosition);
FragColour = vec4(difference, 0.0);

However, what I get is nowhere near the expected result, and of course, lighting doesn't work:

image of issue

(Try to ignore the blur around the boxes, I was messing around with something else at the time.)

What could cause these issues, and how could I get the position reconstruction from depth working successfully? Thanks.

like image 522
Tristan Avatar asked Aug 26 '15 12:08

Tristan


1 Answers

You are on the right track, but you have not applied the transformations in the correct order.

A quick recap of what you need to accomplish here might help:

  1. Given Texture Coordinates [0,1] and depth [0,1], calculate clip-space position

    • Do not linearize the depth buffer
    • Output: w = 1.0 and x,y,z = [-w,w]
  2. Transform from clip-space to view-space (reverse projection)

    • Use inverse projection matrix
    • Perform perspective divide
  3. Transform from view-space to world-space (reverse viewing transform)

    • Use inverse view matrix

The following changes should accomplish that:

// this is supposed to get the world position from the depth buffer
vec3 WorldPosFromDepth(float depth) {
    float z = depth * 2.0 - 1.0;

    vec4 clipSpacePosition = vec4(TexCoord * 2.0 - 1.0, z, 1.0);
    vec4 viewSpacePosition = projMatrixInv * clipSpacePosition;

    // Perspective division
    viewSpacePosition /= viewSpacePosition.w;

    vec4 worldSpacePosition = viewMatrixInv * viewSpacePosition;

    return worldSpacePosition.xyz;
}

I would consider changing the name of CalcViewZ (...) though, that is very much misleading. Consider calling it something more appropriate like CalcLinearZ (...).

like image 73
Andon M. Coleman Avatar answered Nov 19 '22 03:11

Andon M. Coleman