Implementing linear-z.

Started by
19 comments, last by bluntman 15 years, 10 months ago
What happens if you don't do the projection matrix trick and use the original code of:
finalPosition.z = finalPosition.z * finalPosition.w / farClipDistance;

This way you don't need any messy matrix tricks.
Advertisement
Quote:Original post by agi_shi
What happens if you don't do the projection matrix trick and use the original code of:
finalPosition.z = finalPosition.z * finalPosition.w / farClipDistance;

This way you don't need any messy matrix tricks.

From a theoretical point of view: It wouldn't work in general since it would violate OpenGL's depth clipping range. As a result you'll get geometry behind the camera become visible.

Here is the reason: The depth range in camera co-ordinates is from near clipping distance n up until far clipping distance f, hence [n,f]. Dividing these by f normalizes this range to [n/f,1] with n/f close to 0 since f>>n normally. But OpenGL clips in the range [-1,1] (as opposed to D3D which clips in [0,1] as it seems to be). That means in reverse that the depth range [-f,n/f] (in camera space) will accidentally also be visible.

The "matrix trick" is just a way to overcome this problem by virtually mapping [n,f] linearly to [-f,f], and that with minimal additional computations. Of course, one can do the same trick directly in the shader, but pays then with more runtime consumption. One can otherwise add another clipping plane, but pays then with loss of half of the depth buffer resolution.

Unfortunately, it seems that bluntman doesn't visit this thread very often, or else isn't answering due to any other reason, so someone else need to verify the theory. Perhaps I can spend some time to do so tomorrow.
Oh, thanks for clearing it up [smile]. I was a bit confused as to how things worked.
haegarr your method works!! The clip plane is in the correct place, everything works as expected.
Now comes the obvious.. The close and far depth resolution seems good, but I could do with more mid distance resolution!! I will try to work out how to implement my own depth fall off function, but any hints would be appriciated! I think I at least have more understanding of how the projection works in relation to the z-buffer now, thanks for the help everybody!

/edit
I spoke to soon! When I tried splitting my view frustum into near and far again I noticed it is not working quite right, just trying to work out the problem now.
Quote:Original post by haegarr
Quote:Original post by Ashkan
Linearized Depth Using Vertex Shaders

Well, if you would please take the time to read at least the OP, then you'll see that exactly the cited article was implemented and doesn't work "as is" for OpenGL. This entire thread is about to adapt the method to work well with OpenGL.


I sincerely apologize. I guess today is one of those days when everything I say or do just turns out wrong. Kind of makes me wonder what's next...

[Edited by - Ashkan on May 26, 2008 9:07:24 PM]
Well I'm trying to work through the math myself and can't seem to get the correct results:
haegarr you wrote:
z' := (f+n)/(f-n) - 2fn/(f-n)/z
as the default OpenGL transform after normalisation, but shouldn't the (f-n) bits still be (n-f)?
If I understand correctly it should be (without simplification):
z' := [z(f+n)/(n-f) + 2fn/(n-f)]/z
This is correct yes? I have been working through this calculation using the near plane expecting to get z'=-1 for when z=n but it doesn't seem to be working:
e.g:
n = 0.1
f = 1000
z = n = 0.1
z' = [0.1x(1000+0.1)/(0.1-1000) + 2x1000x0.1/(0.1-1000)]/-0.1
= (-0.1x1000.1/999.9 - 200/999.9)/-0.1
= (-0.10002 - 0.20002)/-0.1
= ~3
What am I missing?!
This morning it comes to my mind that I've forgotten that OpenGL's camera looks along the negative z axis. The near and far clipping distances as specified in glFrustum are positive values, but when used as co-ordinates in camera space they are to be negative. Hence, actually the clipping range is restricted by -n and -f. (I apologize not remembering this earlier.)

If the above conclusion is true, then the projection of z
z" = (f+n)/(f-n) + 2fn/(f-n)/z
will produce
z"(z=-n) = -1
z"(z=-f) = +1
at the limits. That is nice so far.

Still wanting a linear function
a*z+b
that fulfills OpenGL's clipping, we now get
z"(z=-n) = -a*n + b == -1
z"(z=-f) = -a*f + b == +1
what yields in
a = -2/(f-n)
b = -(f+n)/(f-n)
when being resolved. This differs in the sign of co-efficient a in comparison to my previous attempt.

Fitting the co-efficients into the matrix would yield in
[ 0 0 2/(f-n) (f+n)/(f-n) ]
for the 3rd row.

@bluntman: Does this match the problem you've observed?
Quote:Original post by bluntman
Well I'm trying to work through the math myself and can't seem to get the correct results:
haegarr you wrote:
z' := (f+n)/(f-n) - 2fn/(f-n)/z
as the default OpenGL transform after normalisation, but shouldn't the (f-n) bits still be (n-f)?
If I understand correctly it should be (without simplification):
z' := [z(f+n)/(n-f) + 2fn/(n-f)]/z
This is correct yes? I have been working through this calculation using the near plane expecting to get z'=-1 for when z=n but it doesn't seem to be working:
e.g:
n = 0.1
f = 1000
z = n = 0.1
z' = [0.1x(1000+0.1)/(0.1-1000) + 2x1000x0.1/(0.1-1000)]/-0.1
= (-0.1x1000.1/999.9 - 200/999.9)/-0.1
= (-0.10002 - 0.20002)/-0.1
= ~3
What am I missing?!

You've hit the sign problem I've mentioned in my previous post (to be correct, its an outcome of that problem). While the rows of the projection matrix in my first post were okay, I've made a mistake then during making a stand-alone equation of it. The correct formula would be
z' := ( (f+n)/(n-f)*z + 2fn/(n-f) ) / ( -z ) = (f+n)/(f-n) + 2fn/(f-n)/z
Notice please that the 4th row of the matrix contains a "minus 1", so that division by -z is done. I then incorporated the sign into the denominators.

EDIT: Please bear in mind that I make all this on a pure theoretical basis. Err, well, one can say also that I want you do the beta testing ;) So you're absolutely right to check my writings...

[Edited by - haegarr on May 27, 2008 3:37:11 AM]
Thanks alot for the help so far haergarr! The revised equations do appear to work correctly (I switched their signs and multiply by w in the vertex shader), the clip planes are in the correct places now, both near and far.
I have run into another problem though: the interpolation between vertices of the depth value does not seem to be linear with depth! I guess it could be an accuracy problem, I am using a 32bit depth buffer, but shaders under some profiles perform calculations at 24 bit iirc. That said, my shaders are running on the Cg gp4 profile, which I would guess has 32bit floats at least!
I tweaked my shader to output the final (projected, normalized) value of z/w + 1.0 to visualise the values being used in the z buffer and this is the result I got:
Free Image Hosting at www.ImageShack.us
The banding is not compression artifacts, it is the 24bit color output from the vertex shader, you can see how the bands are distorted. They run across a perfect sphere of quads so should appear smooth.
If I rotate the camera further to the left:
Free Image Hosting at www.ImageShack.us
You can see another mesh that should be behind the grey sphere is starting to show through where the interpolation is becoming most distorted. The mesh that is showing through is more highly tesselated, therefore its interpolations are less distorted relative to the sphere's.
Heres a shot from close to the surface (I have upped the contrast to make the problem obvious):
Free Image Hosting at www.ImageShack.us
You can see the boundaries of the polygons, and how the z/w value is NOT being interpolated linearly with respect to actual depth!
Any ideas? Is this one of the problems the non-linear z buffer is meant to overcome? Is the math still not quite right?

edit/
Arg! I think I might have just worked out the problem: it occured to me when I went back to look at the last image, that the relation between the image colors and depth is only correct if the color is interpolated in the same manner as the depth. I figure one interpolation is much the same as the next, but I guess OpenGL seperately interpolates each element of the projected v (v') and then performs the z/w per pixel to get the normalized value. I have subverted that by multiplying the z by w at the vertex stage. Interpolating z'w is not the same as interpolating z' and w seperately then multiplying (by RHW):
e.g. (my rough workings to prove that to myself!)
za = 0.1 zb = 0.2
wa = 5 wb = 100
za*wa = 0.5
zb*wb = 20
i = interpolation (0 <= i <= 1) = 0.5
interp( zawa zbwb i ) = (20 - 0.5) * 0.5 + 0.5 = 9.75 + 0.5 = 10.25 <<<<
interp( za zb i ) = (0.2 - 0.1) * 0.5 + 0.1 = 0.15
interp( wa wb i ) = (100 - 5) * 0.5 + 5 = 47.5 + 5 = 52.5
== 52.5 * 0.15 = 7.875 <<<<

If this is the problem then I can't immediately see how to fix it :/

[Edited by - bluntman on May 27, 2008 5:23:05 AM]
When I read the original article, it made me wonder what effect the linearized Z would have on the GPU's perspective correction.

Is linearized Z really compatible with how a GPU does perspective correction? I must admit that I haven't bothered to go through the math, but I would have thought not.

This topic is closed to new replies.

Advertisement