Skip to main content
GameDev.net gamedev.net
🔒 Locked

Position from Depth

Started by Endemoniada Jan 13, 2014 at 11:32 PM 20 replies 6k views
Original Post
Endemoniada
Endemoniada

Hi guys, I know this has been discussed ad nauseam but I can't get it to work.

I'm using an SSAO shader which works fine using a position texture but not with position from depth reconstruction.

I'm following MJP's http://mynameismjp.wordpress.com/2010/09/05/position-from-depth-3/ and tried both the spotlight and directional versions:


// vertex shader
output.vpos=mul(input.position,InverseProjection);
 
// pixel shader
float3 viewRay=input.vpos.xyz;
 
// get position function (in pixel shader)
float3 getPosition(in float2 uv,in float3 viewRay)
{
 // old version works perfectly
 // return tex2D(PositionTexture,uv).xyz;
 
 // reconstruction doesn't work
 float depth=tex2D(DepthTexture,uv).x;
 return viewRay*depth;
}

Please help.

Promit
Promit

Well let's start with the obvious: either your view ray is wrong or your depth is wrong. Depth is the easier one to get and to test, so start with that. Then try testing the view ray at various points, eg center of viewport and corners.

SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Endemoniada
Endemoniada

Hi, I am almost certain I can rule out the depth. The depth map looks good, and the depth values work perfectly in this, as well as another, shader. It's a simple linear depth shader.

The viewRay is simply the input.position multiplied by the InverseProjection, which then gets multiplied by the depth (from the depth texture.)

I think I followed his example correctly and it's not working out for me. Maybe it's because I'm using it for something other than lighting, I don't know.

Thanks.

Ohforf sake
Ohforf sake

What exactly (which values) is input.position? It should be (x, y, 1.0, 1.0) with x, y either -1.0 or 1.0.

Are you sure that that the shader does:

input.vpos.xyz == output.vpos.xyz / output.vpos.w

I might be wrong but I think what happens is actually:

input.vpos.xyzw == output.vpos.xyzw

so you are probably missing the division by w.

Also, are you sure that the values in the DepthTexture are linear?

Endemoniada
Endemoniada

Thanks for trying to help.

input.position is the vertices that make up the full screen quad (-1.0 to 1.0)

The depth is linear, I'm doing it like this:


// depth vertex shader
output.vpos=mul(input.position,WorldView).xyz;
 
// depth pixel shader
output.depth=input.vpos.z/FarZ;

I tried the divide by w and it didn't work.

When I do the SSAO it looks like lots of tiny mirrors all distorted (for wont of a better explanation.)

Styves
Styves

That's your problem. You shouldn't use your full screen quad coordinates, that won't work. You need the frustum corners, passed in as vertex data, and output from your quad (thus interpolating it for the pixel shader).

Endemoniada
Endemoniada

I tried what you guys suggested and it's not working at all. Here is the implementation I'm working from. What I don't understand is why there are two different maths, one for point lights and spotlights and another for directional lights; it sounds silly to ask if there is a different math when using it for ssao; isn't computing the position independent of what you need it for ?


// G-Buffer vertex shader
// Calculate view space position of the vertex and pass it to the pixel shader
output.PositionVS = mul(input.PositionOS, WorldViewMatrix).xyz;

// G-Buffer pixel shader
// Divide view space Z by the far clip distance
output.Depth.x = input.PositionVS.z / FarClipDistance;

// Light vertex shader
#if PointLight || SpotLight
// Calculate the view space vertex position
output.PositionVS = mul(input.PositionOS, WorldViewMatrix);
#elif DirectionalLight
// Calculate the view space vertex position (you can also just directly map the vertex to a frustum corner to avoid the transform)
output.PositionVS = mul(input.PositionOS, InvProjMatrix);
#endif

// Light Pixel shader
#if PointLight || SpotLight
// Extrapolate the view space position to the  far clip plane
float3 viewRay = float3(input.PositionVS.xy * (FarClipDistance / input.PositionVS.z), FarClipDistance);
#elif DirectionalLight
// For a directional light, the vertices were already on the far clip plane so we don't need to extrapolate
float3 viewRay = input.PositionVS.xyz;
#endif

// Sample the depth and scale the view ray to reconstruct view space position
float normalizedDepth = DepthTexture.Sample(PointSampler, texCoord).x;
float3 positionVS = viewRay * normalizedDepth;

I'm going to work on something else and come back to this in a day or two. Thanks again for trying to help.

Ohforf sake
Ohforf sake

What I don't understand is why there are two different maths, one for point lights and spotlights and another for directional lights; it sounds silly to ask if there is a different math when using it for ssao; isn't computing the position independent of what you need it for ?


The primary difference is that pointlights and spotlights are local effects, confined to a specific region which is usually defined by a mesh (sphere or cone) and when you render that mesh, you need to extract the view ray from it's surface. Directional lights however effect the entire screen and thus work with a full screen quad. Hence the slightly different approach in the view ray computation.
In that sense, ssao falls into the "directional light category", because it effects the entire screen.

input.position is the vertices that make up the full screen quad (-1.0 to 1.0)

As I stated above, please post the exact values. As far as I understand your code, it should be 4 vectors with 4 components each:
(-1.0 -1.0 1.0 1.0)
( 1.0 -1.0 1.0 1.0)
( 1.0 1.0 1.0 1.0)
(-1.0 1.0 1.0 1.0)

Are you familiar with the difference between euclidean coordinates and projective coordinates?

For testing this stuff it is extremely helpfull to disable the SSAO effect and output the reconstructed position as color values.
Endemoniada
Endemoniada

Hello,

I was using z=0 for my input.position, I changed it to z=1 and now have the exact values listed in the post just above this one.

Things are shaping up, there is no hall of mirrors; the result is still off though, everything is darker and the edges are brighter. I feel like it wants to work.

I would like to output to color textures the position map and position reconstruction to compare them but I'm having problems, the position map (which I know is correct) comes out as a big colorful blur. The positions are in view space and I'm not sure how to scale them; I tried lots of scales and I can barely make out the geometry.

Thanks for helping, I appreciate it.

Ohforf sake
Ohforf sake
Just scaling the eye space position by a constant factor should suffice. You should end up with an image like this:

[attachment=19492:ViewSpaceColors.jpg]

As you can see, everything close to the camera (the origin of the eye space coordinate system) is black, everything to the right is red, everything to the top is green and everything in the back is blue, assuming you are using the DirectX convention of a left handed coordinate system with the camera looking at +z. If not, than you should get the same image without any blue.

If scaling by a factor doesn't work for your position map, then the coordinates in the position map are probably not eye space. They could be world space or eye space but without the camera position removed.

Maybe you can post your images for the position map and the position reconstruction so we can take a look at them.
Endemoniada
Endemoniada

Hello again,

I feel like I double-checked everything but I must be missing something.

urwh.jpg

Left side: position map (scaled by 0.01 for display) and correct ssao (multiplied by simple diffuse)

Right side: position reconstructed from depth (not scaled for display) and incorrect ssao (multiplied by simple diffuse)

The position maps look almost the same, so the reconstruction is almost working. There is still something wrong though.

The depth surface is D3DFMT_D24X8.

I don't know what else to try.

Thanks a lot for helping.

Ohforf sake
Ohforf sake
So apart from the minor differences, it's also off by a factor of 100?

Can you please post the entire shader code with all declarations for both, the z pass and the position reconstruction pass?
Endemoniada
Endemoniada

Here we go:

Application stuff:

eye=D3DXVECTOR3(0.0f, 40.0f, 0.0f);
look=D3DXVECTOR3(40.0f, 0.0f, 40.0f);
up=D3DXVECTOR3(0.0f, 1.0f, 0.0f);

D3DXMatrixLookAtLH(&view_matrix, &eye, &look, &up);
D3DXMatrixPerspectiveFovLH(&projection_matrix, pi/4.0f, aspect_ratio, 10.0f, 160.0f);

D3DXMatrixMultiply(&world_view_projection, &view_matrix, &projection_matrix);

D3DXMatrixInverse(&inv_projection_matrix, NULL, &projection_matrix);

// note: world matrix is identity so WorldView is really View and WorldViewProjection is really ViewProjection

Direct view space position map shader (D3DFMT_A32B32G32R32F format):

float4x4 WorldView;
float4x4 WorldViewProjection;

struct VS_INPUT
{
 float4 position : POSITION;
};

struct VS_OUTPUT
{
 float4 position : POSITION;
 float3 viewpos : TEXCOORD;
};

VS_OUTPUT vertexShader(VS_INPUT input)
{
 VS_OUTPUT output;

 output.position=mul(input.position,WorldViewProjection);
 output.viewpos=mul(input.position,WorldView).xyz;

 return output;
}

float4 pixelShader(VS_OUTPUT input) : COLOR
{
 return float4(input.viewpos,0);
}

Here is the linear depth shader (D3DFMT_R32F format):

#define FarZ 160.0f

float4x4 WorldView;
float4x4 WorldViewProjection;

struct VS_INPUT
{
 float4 position : POSITION;
};

struct VS_OUTPUT
{
 float4 position : POSITION;
 float3 viewpos : TEXCOORD;
};

VS_OUTPUT vertexShader(VS_INPUT input)
{
 VS_OUTPUT output;

 output.position=mul(input.position,WorldViewProjection);
 output.viewpos=mul(input.position,WorldView).xyz;

 return output;
}

float4 pixelShader(VS_OUTPUT input) : COLOR
{
 return float4(input.viewpos.z/FarZ, 0, 0, 0);
}

This is the shader where I would use the view space position from depth reconstruction, it's a full screen quad (-1, -1, 1, 1) to (1, 1, 1, 1):

#define Screen_Width 1680.0f
#define Screen_Height 1050.0f

float4x4 InvProjection;

static const float2 pixel_size={1.0f/Screen_Width, 1.0f/Screen_Height};

texture Texture0;
sampler2D Sampler0=sampler_state
{
 // depth
 Texture=<Texture0>;
};

struct VS_INPUT
{
 float4 position : POSITION;
 float2 uv : TEXCOORD;
};

struct VS_OUTPUT
{
 float4 position : POSITION;
 float2 uv : TEXCOORD;
 float3 viewpos : TEXCOORD1;
};

VS_OUTPUT vertexShader(VS_INPUT input)
{
 VS_OUTPUT output;

 output.position=float4(input.position.x-pixel_size.x,input.position.y+pixel_size.y,1,1); // set zw to 1 for assurance
 output.uv=input.uv;
 output.viewpos=mul(input.position,InvProjection).xyz;

 return output;
}

float4 pixelShader(VS_OUTPUT input) : COLOR
{
 float3 viewRay=input.viewpos;

 // get position for something useful (doesn't really work)
 float depth=tex2D(Sampler0,uv).x;
 float3 viewPos=viewRay*depth;

 // if, on the other hand, I get position directly from position map (created above) the position is accurate and working
 // Sampler1 would be the position map:
 // float3 viewPos=tex2D(Sampler1,uv).xyz;

 ...
}

Once again, I really appreciate your help. I hope I didn't omit anything important, if you need any other code just ask. Thanks again.
Ohforf sake
Ohforf sake
In the last shader (depth reconstruction), you are missing the division by w again. You can only skip that if you know that w is 1.0. This is usually the case for rotation, scale and translation but not for projection. It should look like this:

 //snip

VS_OUTPUT vertexShader(VS_INPUT input)
{
 VS_OUTPUT output;

 float4 clipSpacePos = float4(input.position.x,input.position.y,1,1); // set zw to 1 for assurance
 output.position=clipSpacePos - pixel_size;
 output.uv=input.uv;

 float4 projectiveViewPos = mul(clipSpacePos,InvProjection);
 output.viewpos=projectiveViewPos.xyz / projectiveViewPos.w;

 return output;
}

 //snip

Apart from that I can spot no other problems with this. Maybe you can give this a shot.
Endemoniada
Endemoniada

Ohforf, it didn't work. I even made a whole new program, completely stripped down to a simple deferred renderer with one point light. Once again, it works perfectly with the position values explicitly stored and fails when trying to reconstruct from depth. I also tried a different reconstruction method, it must be equivalent because it has the exact same result.

Maybe my depth buffer is wrong ? Perhaps I'm not clearing it or using the z-buffer correctly ? Could it be render/sampler states ? I can't think of anything else.

I don't know what else to try and I can't find any working samples.

Thanks again.

Styves
Styves

You should've used the search function, you can find plenty of resources for solving this problem. Most of which go back to this very helpful set of blog posts by MJP:

http://mynameismjp.wordpress.com/2009/03/10/reconstructing-position-from-depth/

http://mynameismjp.wordpress.com/2009/05/05/reconstructing-position-from-depth-continued/

http://mynameismjp.wordpress.com/2010/09/05/position-from-depth-3/

Again, you can't simply use the direction from your screen quad vertices. This won't give you the right results. You need the position from the frustum corners. All the details are in these blog posts by MJP. smile.png

Ohforf sake
Ohforf sake

Again, you can't simply use the direction from your screen quad vertices. This won't give you the right results. You need the position from the frustum corners.


I'm pretty sure that transforming the vertices of the clip space far plane by the inverse projection matrix yields the eye space position of the far frustum corners. I agree that there are better/faster ways of doing this, but his approach should give the right results.

"it didn't work" is sadly not much to go on. Does the reconstruction give you positions on the far plane for pixels where no geometry is rendered? Does it give you positions on the near plane where the geometry intersects the near plane? Do the reconstructed positions at the borders of the frame stretch out at an angle that corresponds to the FOV?
Those are all things you can check quite easily.
Endemoniada
Endemoniada

You should've used the search function, you can find plenty of resources for solving this problem. Most of which go back to this very helpful set of blog posts by MJP:

http://mynameismjp.wordpress.com/2009/03/10/reconstructing-position-from-depth/

http://mynameismjp.wordpress.com/2009/05/05/reconstructing-position-from-depth-continued/

http://mynameismjp.wordpress.com/2010/09/05/position-from-depth-3/

Again, you can't simply use the direction from your screen quad vertices. This won't give you the right results. You need the position from the frustum corners. All the details are in these blog posts by MJP. smile.png

I read those blog posts, I'm using the code from the third post. Here is what MJP writes: "Like I said in the post the frustum corners are only useful if you want to save a little bit of vertex calculation for a full screen quad. You can get the same results just by using the inverse of your projection matrix, which is easier and less error-prone. And since you have only 4 verts per quad, the extra cost is negligable."... so I don't think you use the frustum corners.

Endemoniada
Endemoniada

Ohforf's method does indeed work, I'm sorry for saying it didn't. In fact, it's the only method I've found that reconstructs the position faithfully. The reconstructed position maps are indistinguishable from the raw position maps, and it works great for deferred point lights.

Unfortunately, I couldn't get SSAO to work properly using position from depth. I tried two completely different implementations, both work perfectly when passing an actual position map (D3DFMT_A32B32G32R32F). I think the problem is where it samples from a small offset of the main point.


// ambient occlusion pixel shader
 
// main point is working
float3 p=getPosition(input.uv);
 
// I think this is where the problems lie
float3 p1=getPosition(input.uv+small_offset);

But like I wrote, it works perfectly using an actual position map. I don't get it.

Thanks for all the help.

knighty
knighty

If you divide your linear depth by FarZ, why don't you multiply the reconstructed depth by the same value? huh.png

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.