Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting a depth texture sample to a distance

I'm currently reading from a depth texture in a postprocess depth of field shader using the following GLSL code:

vec4 depthSample = texture2D(sDepthTexture, tcScreen);
float depth = depthSample.x * 255.0 / 256.0 +
              depthSample.y * 255.0 / 65536.0 +
              depthSample.z * 255.0 / 16777216.0;

And then converting the depth value to a view space distance based on the near and far plane distances:

float zDistance = (zNear * zFar) / (zFar - depth * (zFar - zNear));

This all seems to work fairly well, however I am interested to know how to do the above calculation based only on the current projection matrix without needing separate zNear and zFar values.

My initial attempt involved multiplying (vec4(tcscreen.x, tcScreen.y, depth, 1.0) * 2.0 - 1.0) by the inverse of the projection matrix, dividing the result through by w, then taking the resulting z value as the distance, but this didn't seem to work. What's the correct approach here?

Also, when using oblique frustum clipping to shift the near plane onto a chosen clipping plane is the near plane distance now potentially different for every pixel? And if so then does this mean that any shaders that calculate a distance from a depth texture need to be aware of this case and not assume a constant near plane distance?

Thanks!

like image 520
Richard Viney Avatar asked Jul 20 '09 11:07

Richard Viney


2 Answers

It turns out that I had forgotten to negate the final Z value to get a positive distance in front of the near plane (OpenGL camera looks down -Z). For future reference the GLSL code for getting the distance in front of the near plane is:

float depth = /* sampled from depth texture as in the original question */ ;

vec4 screenPos = vec4(tcScreen.x, tcScreen.y, depth, 1.0) * 2.0 - 1.0;
vec4 viewPosition = projectionMatrixInverse * screenPos;

float z = -(viewPosition.z / viewPosition.w);

If you wanted a world space position instead (like SuperPro was using) then that can be found by combining the view and projection matrices and then using the inverse of that matrix rather than just using the projection matrix inverse.

Because only the Z and W components of viewPosition are needed the above GLSL for computing viewPosition can be simplified somewhat. Two dot products will suffice instead of a full matrix multiplication, and there's no need to feed the full inverse projection matrix into the shader as only the bottom two rows are needed:

vec2 viewPositionZW = vec2(
    dot(projectionMatrixInverseRow2, screenPos),
    dot(projectionMatrixInverseRow3, screenPos)
);

float z = -(viewPositionZW.x / viewPositionZW.y);

The performance of this is a little worse than using the near and far distances, presumably because of the extra dot products, I got a ~5% reduction. The near and far distance math can also be optimized by feeding (zNear * zFar) and (zFar - zNear) in as constants, but I didn't see any measurable improvement by doing this.

Interestingly, when you combine the above with a projection matrix that is using oblique frustum clipping I can't get anything sensible out of it, but I do get reasonable output when using the near and far distance equation with the same projection matrix, albeit with what appears to be some distortion of the depth values (though this could just be due to the loss of depth precision inherent in oblique frustum clipping). If anyone can shed some light on what exactly is going on mathematically here I'd appreciate it, though maybe that should be in a different question.

like image 126
Richard Viney Avatar answered Nov 18 '22 08:11

Richard Viney


I use the following code in a lightning shader, in order to compute the lightning direction. Wold position is also calculated by multiplying the screen position with the inverse of projection matrix.

Unfortunately HLSL:

float depth = tex2D(DepthMapSampler, PSIn.TexCoord).r;

float4 screenPos;
screenPos.x = PSIn.TexCoord.x*2.0f-1.0f;
screenPos.y = -(PSIn.TexCoord.y*2.0f-1.0f);
screenPos.z = depth;
screenPos.w = 1.0f; 

float4 worldPos = mul(screenPos, xViewProjectionInv);
worldPos /= worldPos.w;

Works fine, so I suppose Worldposition is correct!

like image 23
SuperPro Avatar answered Nov 18 '22 07:11

SuperPro