Sign in to follow this  
l0calh05t

Retrieving eye-space coordinates from depth

Recommended Posts

l0calh05t    1796
I am currently attempting to implement some deferred shading, and I am using a depth texture as depth buffer for my MRT FBO. So I have post-projection z available in following passes. To get linear distance from the camera I currently apply the following formula: float a = zFar / ( zFar - zNear ); // = 1; if zFar = Infinity float b = zFar * zNear / ( zNear - zFar ); // = -zNear; if zFar = Infinity float dist = b / (depth - a); This seems to be correct so far (as far as I can tell). Now, how would I go about computing the eye-space position of the fragment? (I intend to do lighting in eye-space) Oh, and does anyone have any tips on improving precision for the normals? 3x8 bit causes objects like spheres to have tiny "pseudo-faces". Too bad only one type of texture format at a time is allowed for FBOs.

Share this post


Link to post
Share on other sites
polymorphed    272
Computing the eye space position of each fragment is easy.

Create a varying vector in the vertex shader and fragment shader.
Like this:

Vertex Shader:

varying vec3 eyePos;

void main ()
{
eyePos = vec3(gl_ModelViewMatrix * gl_Vertex);
gl_Position = ftransform();
}





Fragment Shader:

varying vec3 eyePos;

void main ()
{
// eyePos now contains the eye-space position for the fragment.
}





Quote:

Oh, and does anyone have any tips on improving precision for the normals? 3x8 bit causes objects like spheres to have tiny "pseudo-faces". Too bad only one type of texture format at a time is allowed for FBOs.


I don't think precision is the problem. If you're doing some form of normal mapping algorithm, you should interpolate the normal, bitangent (binormal) and tangent between vertices. This will create a smooth transition.


I hope this helps.

Share this post


Link to post
Share on other sites
l0calh05t    1796
Your code snippet won't work since I was talking about a fragment which was already rendered (of which I know the depth), not a fragment currently being rendered from given geometry.

And: I am already interpolating the normals but they are stored in a rgb8 texture before being used. (The "pseudo-faces" are a *lot* smaller than the actual faces of the sphere)

EDIT:

Here's what I'm currently doing (doesn't work):

I send eye-space coordinates of the four corners of the full-screen rectangle to the vertex shader via the second set of texture coordinates.

Vertex Shader:

void main(void)
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[1] = gl_MultiTexCoord1/gl_MultiTexCoord1.z;
}



Fragment Shader:

uniform float zNear;
uniform float zFar;

uniform sampler2D tex0; // color
uniform sampler2D tex1; // normals
uniform sampler2D tex2; // depth

void main(void)
{
float projectedDepth = texture2D(tex2,gl_TexCoord[0].st).r;

float a = zFar / ( zFar - zNear );
float b = zFar * zNear / ( zNear - zFar );
float depth = b / (projectedDepth-a);

vec3 position = gl_TexCoord[1].xyz * depth;

gl_FragColor = vec4(position,1.0);
}



When compared to outputting the eye-space coordinates directly (vec3(gl_ModelViewMatrix * gl_Vertex) as varying, output to rgb color), the result is *very* different, for reasons unknown to me.

[Edited by - l0calh05t on November 25, 2007 2:00:05 PM]

Share this post


Link to post
Share on other sites
polymorphed    272
Oh I see.

So, if I understand your question correctly: You want to be able to find the position of a fragment, and you have the eye-space matrix available, aswell as the final rasterized texture/buffer with depth values.

This does complicate the question and I'm not sure that I have an answer for your question anymore.

You should be able to do the matrix transformations in reverse to get back to the starting point (world space), and then transform forward to eye space again.
Or you might be able to transform right back to eye space.

I'm a bit tired right now so I can't understand code too well :) Hehe.
You might want to use uniform variables to make the code more readable.

Share this post


Link to post
Share on other sites
l0calh05t    1796
Interesting fact:
Judging from the pictures, it looked like x and y were about twice the real x and y coordinates (z is not visible, as it is always negative). Guess what: I added a factor of 0.5 and it turns out the result is now bit-exactly the same (again: except for the background). But *why* this is so makes no sense at all to me from a mathematical point of view.

EDIT:
When I only scale x and y by 0.5 I also get the same length for the vectors. Still doesn't make any sense to me, but the results look right...

[Edited by - l0calh05t on November 26, 2007 1:18:03 AM]

Share this post


Link to post
Share on other sites
hibread    152
Gday l0calh05t!

I'll try to assist you with both issues.

Regarding your equation for calculating depth based on the non-linear depth buffer; it looks correct. One thing you can do to speed up frag shader processing is to pre-calculate 'a' and 'b' client side and upload as uniform variables for use with 'dist = b/(depth - a)'. The other thing to keep in mind is the sign of the depth you get after that calculation. It'll be positive which goes against the general "-z into the screen" notion with opengl (although you can change this by playing with the matrices) so you may have to swap signs.

With regards to calculating the XYZ values of the fragment in eye-space, I use the following technique:

I am assuming here that when you perform your lighting passes, that you use bounding volumes (spheres/cones etc) to "select" fragments that require processing for the particular light source (although the technique would probably work for a fullscreen quad also). The space you use for rendering those bounding volumes is the same as it is for the other objects in your scene.

The vertex shader for your lighting pass needs to send through to your frag shader a varying variable containing the eye-coordinates of the fragment that would be rendered if you were actually rendering the bounding volume. ie., varyingVolumeCoords = (gl_ModelViewMatrix * gl_Vertex).xyz. Now, considering the fact that the surface fragments location you wish to process falls on exactly the same ray (line) from the eye (0,0,0) to the varyingVolumeCoords.xyz, you can use the calculated depth to derive the surface XY values. (vec3 fragSurfaceLocation = vec3( varyingVolumeCoords.xy * surfaceDepth / varyingVolumeCoords.z, surfaceDepth);). That should be about it.


I've had to overcome the exact same issue with regards to 3x 8-bit normals not having enough precision. I've tried packing 2x 16-bit floats (fixed point) into 4x 8-bits and deriving the third component using z = +-sqrt( 1 - x^2 - y^2) later during the lighting pass. The problem ive had with that is when z was negative. Since that equation gives you 2 posible answers (positive and negative z), which one is it really? Many would argue that z should always be positive as the normal is facing you... but that seems not always the case when you store normals in non-perspective-projected eye-space while the rendered image has perspective projection. Not to mention when you have normals disturbed by per-pixel normal mapping etc. Check out the topic Deferred Shading - Deriving normal.z for further discussion.

What ive ended up using (atleast for now) is the spherical polar coordinate idea as it eliminates the "sign of z" issue. Spherical_coordinate_system. I convert the cartesian coords to spherical coords then pack Phi and Theta (you can assume Rho to be always 1) into one 32-bit buffer (2x8-bits each) with the range 0-1. Then unpack the data, reset the range and convert back to cartesian coords. I dont believe its quite as expensive as it sounds. I've done some basic tests between non-packed 3x8-bit cartesian normals vs packed spherical 4x8-bit and the performance seems nearly negligible. One reason could be that you do not seem to have to re-normalize the normal like you do with 3x8-bit.

The other benifit of using 16-bit fixed point floats (range 0-1) packed into 2x8-bits is you get the full 16-bits of precision. If you were able to use true 2x16-bit float buffers, you'd be reduced to 11-bits of precision (10 bits of mantissa and the sign bit) which is not "that" much greater than 8-bits.

I hope that solves the issues you are having!
Cheers!

Share this post


Link to post
Share on other sites
l0calh05t    1796
Interpolating the view space positions of the given volume/quad is pretty much what I'm doing right now (except that i'm passing them as texture coordinates to a fullscreen quad rendered in a 2D ortho projection). Do you have any idea why my x and y coordinates are too large by a factor of 2.0 (and not the z coordinate)?? It just makes no sense from a linear algebra point of view.

And about the normals:
Do you have any good coding for a [0..1] range to 2x8 bit? I only know of a good encoding for [0..1) range (1 isn't representable as it is mapped to 0, although in polar coordinates normalized to a [0..1] range they just happen to *be* the same :-) )
And, yeah, z can be negative even if no normal map is applied, that's why I discarded encoding only x and y.

EDIT:

Some pics

Precision problems:
Free Image Hosting at www.ImageShack.us

Lighting with two lights (and apparently correct, but unexplained coordinates):
Image Hosted by ImageShack.us<br/>

Share this post


Link to post
Share on other sites
l0calh05t    1796
Another update:
I implemented the normals in spherical coordinates and it did improve the precision quite a bit (see pic). But it also cost a good bit of performance.

Here's how I encode/decode them at the moment (both in the respective fragment shaders):

Encoding:

vec2 normalToSpherical(in vec3 n)
{
const float invTwoPi = 1.0/(2.0*3.14159265);
return invTwoPi*vec2(atan(length(n.xy),n.z), atan(n.y,n.x))+vec2(0.5,0.5);
}

vec2 packHalfToVec2i(const float value)
{
const vec2 bitSh = vec2(256.0, 1.0);
const vec2 bitMsk = vec2(0.0, 1.0 / 256.0);
vec2 res = fract(value * bitSh);
res -= res.xx * bitMsk;
return res;
}

[...]

vec2 sphNormal = normalToSpherical(vNormal);
gl_FragData[1] = vec4(packHalfToVec2i(sphNormal.x),
packHalfToVec2i(sphNormal.y));




Decoding:

vec3 normalFromSpherical(in vec2 s)
{
const float twoPi = 2.0*3.14159265;
s -= vec2(0.5,0.5);
s *= twoPi;
float sinP = sin(s.x);
float cosP = cos(s.x);
float sinT = sin(s.y);
float cosT = cos(s.y);

return vec3(sinP*cosT,sinP*sinT,cosP);
}

float unpackHalfFromVec2i(const vec2 value)
{
const vec2 bitSh = vec2(1.0 / 256.0, 1.0);
return (dot(value, bitSh));
}

[...]

vec3 normal = normalFromSpherical(
vec2(unpackHalfFromVec2i(mappedNormal.rg),
unpackHalfFromVec2i(mappedNormal.ba)));




Free Image Hosting at www.ImageShack.us



EDIT:
I think I "found" another bit of precision for the normals:
Because length(n.xy) is always positive, the angle Phi is always positive, so if we only scale it by invPi (instead of invTwoPi) and don't add 0.5, we just gained another bit of precision.

[Edited by - l0calh05t on November 27, 2007 2:51:28 PM]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this