Calculate clipspace.w from clipspace.xyz and (inv) projection matrix
Asked Answered
M

1

6

I'm using a logarithmic depth algorithmic which results in someFunc(clipspace.z) being written to the depth buffer and no implicit perspective divide.

I'm doing RTT / postprocessing so later on in a fragment shader I want to recompute eyespace.xyz, given ndc.xy (from the fragment coordinates) and clipspace.z (from someFuncInv() on the value stored in the depth buffer).

Note that I do not have clipspace.w, and my stored value is not clipspace.z / clipspace.w (as it would be when using fixed function depth) - so something along the lines of ...

float clip_z = ...; /* [-1 .. +1] */
vec2 ndc = vec2(FragCoord.xy / viewport * 2.0 - 1.0);
vec4 clipspace = InvProjMatrix * vec4(ndc, clip_z, 1.0));
clipspace /= clipspace.w;

... does not work here.

So is there a way to calculate clipspace.w out of clipspace.xyz, given the projection matrix or it's inverse?

Misusage answered 25/1, 2013 at 14:15 Comment(1)
We don't put answers in our questions. If you want to share your solution code with people, you write an answer to your question and put it there.Aeolis
A
11
clipspace.xy = FragCoord.xy / viewport * 2.0 - 1.0;

This is wrong in terms of nomenclature. "Clip space" is the space that the vertex shader (or whatever the last Vertex Processing stage is) outputs. Between clip space and window space is normalized device coordinate (NDC) space. NDC space is clip space divided by the clip space W coordinate:

vec3 ndcspace = clipspace.xyz / clipspace.w;

So the first step is to take our window space coordinates and get NDC space coordinates. Which is easy:

vec3 ndcspace = vec3(FragCoord.xy / viewport * 2.0 - 1.0, depth);

Now, I'm going to assume that your depth value is the proper NDC-space depth. I'm assuming that you fetch the value from a depth texture, then used the depth range near/far values it was rendered with to map it into a [-1, 1] range. If you didn't, you should.

So, now that we have ndcspace, how do we compute clipspace? Well, that's obvious:

vec4 clipspace = vec4(ndcspace * clipspace.w, clipspace.w);

Obvious and... not helpful, since we don't have clipspace.w. So how do we get it?

To get this, we need to look at how clipspace was computed the first time:

vec4 clipspace = Proj * cameraspace;

This means that clipspace.w is computed by taking cameraspace and dot-producting it by the fourth row of Proj.

Well, that's not very helpful. It gets more helpful if we actually look at the fourth row of Proj. Granted, you could be using any projection matrix, and if you're not using the typical projection matrix, this computation becomes more difficult (potentially impossible).

The fourth row of Proj, using the typical projection matrix, is really just this:

[0, 0, -1, 0]

This means that the clipspace.w is really just -cameraspace.z. How does that help us?

It helps by remembering this:

ndcspace.z = clipspace.z / clipspace.w;
ndcspace.z = clipspace.z / -cameraspace.z;

Well, that's nice, but it just trades one unknown for another; we still have an equation with two unknowns (clipspace.z and cameraspace.z). However, we do know something else: clipspace.z comes from dot-producting cameraspace with the third row of our projection matrix. The traditional projection matrix's third row looks like this:

[0, 0, T1, T2]

Where T1 and T2 are non-zero numbers. We'll ignore what these numbers are for the time being. Therefore, clipspace.z is really just T1 * cameraspace.z + T2 * cameraspace.w. And if we know cameraspace.w is 1.0 (as it usually is), then we can remove it:

ndcspace.z = (T1 * cameraspace.z + T2) / -cameraspace.z;

So, we still have a problem. Actually, we don't. Why? Because there is only one unknown in this euqation. Remember: we already know ndcspace.z. We can therefore use ndcspace.z to compute cameraspace.z:

ndcspace.z = -T1 + (-T2 / cameraspace.z);
ndcspace.z + T1 = -T2 / cameraspace.z;
cameraspace.z = -T2 / (ndcspace.z + T1);

T1 and T2 come right out of our projection matrix (the one the scene was originally rendered with). And we already have ndcspace.z. So we can compute cameraspace.z. And we know that:

clispace.w = -cameraspace.z;

Therefore, we can do this:

vec4 clipspace = vec4(ndcspace * clipspace.w, clipspace.w);

Obviously you'll need a float for clipspace.w rather than the literal code, but you get my point. Once you have clipspace, to get camera space, you multiply by the inverse projection matrix:

vec4 cameraspace = InvProj * clipspace;
Aeolis answered 25/1, 2013 at 21:24 Comment(2)
Thanks very much for the elaborate explanation (I fixed the name of my variable according to your remark). Your post exactly reflected my line of thought - I strongly felt that the projection matrix being 'normal' allowed w to be reconstructed (as long as the original vector was homogeneous) - but it was just intuition, I should have written down the dot products to realize it... I haven't actually tried it out yet (I just saw your reply) but it makes perfect sense so I consider my problem solved. Thanks again. :-)Misusage
For the record - between the time I asked my question yesterday and I found the answer here today someone wrote the second section on this page opengl.org/wiki/Compute_eye_space_from_window_space which deals with this topic... it's not exactly my problem but the math comes pretty close.Misusage

© 2022 - 2024 — McMap. All rights reserved.