OpenGL position from depth is wrong

My engine is currently implemented using a deferred rendering technique, and today I decided to change it up a bit.

First I was storing 5 textures as so:

DEPTH24_STENCIL8 - Depth and stencil
RGBA32F - Position
RGBA10_A2 - Normals
RGBA8 x 2 - Specular & Diffuse

I decided to minimize it and reconstruct positions from the depth buffer. Trying to figure out what is wrong with my method currently has not been fun :/

Currently I get this:

which changes whenever I move the camera... weird

really simple

#version 150

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

out vec2 uv_f;

void main(){
uv_f = uv;
gl_Position = vec4(position, 1.0);
}


Where the fun (and not so fun) stuff happens

#version 150

uniform sampler2D depth_tex;
uniform sampler2D normal_tex;
uniform sampler2D diffuse_tex;
uniform sampler2D specular_tex;

uniform mat4 inv_proj_mat;
uniform vec2 nearz_farz;

in vec2 uv_f;

... other uniforms and such ...

layout(location = 3) out vec4 PostProcess;

vec3 reconstruct_pos(){
float z = texture(depth_tex, uv_f).x;
vec4 sPos = vec4(uv_f * 2.0 - 1.0, z, 1.0);
sPos = inv_proj_mat * sPos;

return (sPos.xyz / sPos.w);
}

void main(){
vec3 pos = reconstruct_pos();
vec3 normal = texture(normal_tex, uv_f).rgb;
vec3 diffuse = texture(diffuse_tex, uv_f).rgb;
vec4 specular = texture(specular_tex, uv_f);

... do lighting ...

PostProcess = vec4(pos, 1.0); // Just for testing
}


Rendering code

probably nothing wrong here, seeing as though it always worked before

this->gbuffer->bind();
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);

gl::Enable(gl::DEPTH_TEST);
gl::Enable(gl::CULL_FACE);

... bind geometry shader and draw models and shiz ...

gl::Disable(gl::DEPTH_TEST);
gl::Disable(gl::CULL_FACE);
gl::Enable(gl::BLEND);

... bind textures and lighting shaders shown above then draw each light ...

gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);

gl::Disable(gl::BLEND);

Rinse_and_repeat(); // not actually a function ;)


Why are my positions being output like they are?

• In your shader code you do 'inverse(proj_mat)'. Don't you use an view matrix? or it is inside the proj_mat ? And one note : It is better to inverse the matrix in your engine code , not in shader code. Nov 4 '13 at 12:19
• I have a uniform mat4 view_mat but I don't know how I would use this for my positions Nov 4 '13 at 12:34
• You could calculate the pixel position in world space or view space. If you are using world space then you'd need the inverse of the view matrix too. But it is not necessary to calculate the lights in world space since view space will do just fine for that. Nov 4 '13 at 12:43

Your positions are converted from WCS ( World Coordinate System) to NDC (Normalized Device Coordinates) in order to by saved inside the depth buffer. This is achieved by multiplying your coordinates with the view matrix to convert them to ECS (Eye Coordinate System) and then with the projection matrix. And at last they are divided by W component in order to be converted to NDC.
So in order to get them back to WCS you need to multiply the coordinates by the inverse(projectionMatrix * viewMatrix).

#version 150

uniform sampler2D depth_tex;

uniform mat4 inv_proj_view_mat;
uniform vec2 nearz_farz;

in vec2 uv_f;

... other uniforms and such ...

layout(location = 3) out vec4 PostProcess;

vec3 reconstruct_pos(){
float z = texture(depth_tex, uv_f).x;
vec4 sPos = vec4(uv_f * 2.0 - 1.0, z, 1.0);
sPos = inv_proj_view_mat * sPos;

return (sPos.xyz / sPos.w);
}

void main(){
vec3 pos = reconstruct_pos();

... do lighting ...

PostProcess = vec4(pos, 1.0); // Just for testing
}


OpenGL Transformation

UPDATE
Also as Lasse said in his comment you can do the lighting calculations in view space (ECS). You can achieve that by multiplying the position and light direction of each light with your view matrix. Keep in mind that if you use a scaling factor inside your view matrix you should multiply the light direction with the inverse(transpose(viewMatrix)) as you do with normals.

You seem not to have a remapping matrix. By adding it, it fixed my issues. I do not know if it helps you since I've not had a problem looking like that, but here it is anyway:

uniform mat4 RemapMatrix;
...
vec4 Viewspacepixel = (InvProj * RemapMatrix * vec4(FragScreenCoords, DepthSample, 1.0));


And in my camera code:

RemapMatrix = Matrix4.CreateScale (2) * Matrix4.CreateTranslation (-1, -1, -1);


What this remapping matrix does, is that it translates the coordinates back in -1..1 range from 0..1 range. Source and more info here.

Also, for optimization purposes I'd send the inverted projection matrix for the shader through uniform. Inverting the matrix is quite costly process and with your current implementation you do that on every fragment while you only need to do that only once for the scene.

• I do send it via uniform now, this was just before I wrote an inverse() function in my library :P about your answer: it doesn't help sorry Nov 4 '13 at 12:22
• It was just a guess anyway. Sorry it didn't help. Nov 4 '13 at 12:23

After a full day of testing and google searching I finally came upon the fix so I'm sharing it here :

uniform vec2 Resolution;//This is the render target size, i.e. what you feed into glViewport

vec2 screen;
screen.x = ( gl_FragCoord.x - Resolution.x / 2.0 ) / ( Resolution.x / 2.0 );
screen.y = ( (Resolution.y - gl_FragCoord.y) - Resolution.y / 2.0 ) / ( -Resolution.y / 2.0 );
float D = texture2D( DepthTextureSampler, Texcoord ).r;
D = D * 2.0 - 1.0;
vec4 WorldPos = InvMVP * vec4( screen.x, screen.y, D, 1.0);
WorldPos.xyz /= WorldPos.w;


InvMVP is computed as inverse( Projection * View ), no RemapMatrix thing needed ! One of the issues I had is that I think my matrix code is DirectX-like hence why the Y of the screen coord needs to be inverted(Resolution.y - gl_FragCoord.y). I suggest doing simple tests to figure out if the gl_FragCoord is in the right space. The depth for some reason needs to be brought back to -1..1 space, I didn't knew that either since my DX code never does that and works fine. Hope it helps.