openglglsldepth-bufferdeferred-rendering

World-space position from logarithmic depth buffer


After changing my current deferred renderer to use a logarithmic depth buffer I can not work out, for the life of me, how to reconstruct world-space depth from the depth buffer values.

When I had the OpenGL default z/w depth written I could easily calculate this value by transforming from window-space to NDC-space then perform inverse perspective transformation.

I did this all in the second pass fragment shader:

uniform sampler2D depth_tex;

uniform mat4 inv_view_proj_mat;

in vec2 uv_f;

vec3 reconstruct_pos(){
    float z = texture(depth_tex, uv_f).r;
    vec4 pos = vec4(uv_f, z, 1.0) * 2.0 - 1.0;
    pos = inv_view_proj_mat * pos;

    return pos.xyz / pos.w;
}

and got a result that looked pretty correct:

Cube rendered with correct world-space position reconstruction

But now the road to a simple z value is not so easy (doesn't seem like it should be so hard either).

My vertex shader for my first pass with log depth:

#version 330 core
#extension GL_ARB_shading_language_420pack : require

layout(location = 0) in vec3 pos;
layout(location = 1) in vec2 uv;

uniform mat4 mvp_mat;

uniform float FC;

out vec2 uv_f;
out float logz_f;
out float FC_2_f;

void main(){
    gl_Position = mvp_mat * vec4(pos, 1.0);

    logz_f = 1.0 + gl_Position.w;

    gl_Position.z = (log2(max(1e-6, logz_f)) * FC - 1.0) * gl_Position.w;

    FC_2_f = FC * 0.5;
}

And my fragment shader:

#version 330 core
#extension GL_ARB_shading_language_420pack : require

// other uniforms and output variables

in vec2 uv_f;
in float FC_2_f;

void main(){
    gl_FragDepth = log2(logz_f) * FC_2_f;
}

I have tried a few different approaches to get back the z-position correctly, all failing.

If I redefine my reconstruct_pos in the second pass to be:

vec3 reconstruct_pos(){
    vec4 pos = vec4(uv_f, get_depth(), 1.0) * 2.0 - 1.0;
    pos = inv_view_proj_mat * pos;

    return pos.xyz / pos.w;
}

This is my current attempt at reconstructing Z:

uniform float FC;

float get_depth(){
    float log2logz_FC_2 = texture(depth_tex, uv_f).r;
    float logz = pow(2, log2logz_FC_2 / (FC * 0.5));
    float pos_z = log2(max(1e-6, logz)) * FC - 1.0; // pos.z
    return pos_z;
}

Explained:

log2logz_FC_2: the value written to depth buffer, so log2(1.0 + gl_Position.w) * (FC / 2)

logz: simply 1.0 + gl_Position.w

pos_z: the value of gl_Position.z before perspective devide

return value: gl_Position.z

Of course, that's just my working. I'm not sure what these values actually hold in the end, because I think I've screwed up some of the math or not correctly understood the transformations going on.

What is the correct way to get my world-space Z position from this logarithmic depth buffer?


Solution

  • In the end I was going about this all wrong. The way to get the world-space position back using a log buffer is:

    1. Retrieve depth from texture
    2. Reconstruct gl_Position.w
    3. linearize reconstructed depth
    4. translate to world space

    Here's my implementation in glsl:

    in vec2 uv_f;
    
    uniform float nearz;
    uniform float farz;
    
    uniform mat4 inv_view_proj_mat;
    
    float linearize_depth(in float depth){
        float a = farz / (farz - nearz);
        float b = farz * nearz / (nearz - farz);
        return a + b / depth;
    }
    
    float reconstruct_depth(){
        float depth = texture(depth_tex, uv_f).r;
        return pow(2.0, depth * log2(farz + 1.0)) - 1.0;
    }
    
    vec3 reconstruct_world_pos(){
        vec4 wpos =
            inv_view_proj_mat *
            (vec4(uv_f, linearize_depth(reconstruct_depth()), 1.0) * 2.0 - 1.0);
    
        return wpos.xyz / wpos.w;
    }
    

    Which gives me the same result (but with better precision) as when I was using the default OpenGL depth buffer.