12

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.

Tristan
  • 3,058
  • 6
  • 40
  • 68
  • What precisions are you using for your floats in the shader? If you increase everything to `highp` to test, does that change the results? – Dan Hulme Aug 26 '15 at 13:03
  • 4
    How are you going from "screen space" to world-space by multiplying your inverse view matrix? Incidentally, that's not screen space either. What you have is clip-space. To go from clip-space to world-space should involve the inverse projection matrix. The depth (`ViewZ`) needs to be bias and scaled to [**-1**,**1**] as well, you have it in the range [**0**,**1**] right now. – Andon M. Coleman Aug 26 '15 at 13:05
  • @DanHulme I use standard precision, but the high precision seems to make no particular difference. – Tristan Aug 26 '15 at 18:17
  • @AndonM.Coleman Whoops, I did not realise I messed that up, the math is a bit foreign to me. I've updated the shader code to do the translations properly, from what I understand from your comments, but it still does not give the proper results. – Tristan Aug 26 '15 at 18:17

1 Answers1

25

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 (...).

Andon M. Coleman
  • 42,359
  • 2
  • 81
  • 106
  • I've tried to implement the changes you suggested, and I certainly understand the transformations involved in this process some more, but I can't seem to get it quite right. Do you have any suggestions as to what else might cause the calculations to come out incorrect? – Tristan Aug 27 '15 at 11:48
  • @TristanSeifert: Linearizing the depth buffer in the first place is generally unnecessary for this. If you sample the depth buffer, then put that in the range [**-1**, **1**] you may finally get the results you are looking for. You basically want the clip-space coordinate as close to what the projection matrix originally spit out as possible or you're not going to get the same results as your position buffer. You may need linearized depth for other techniques; this isn't one of them. – Andon M. Coleman Aug 27 '15 at 12:00
  • Yep, that did it. Thanks a bunch! – Tristan Aug 27 '15 at 12:04