Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 565
Felix Nawothnig Avatar asked Jan 25 '13 14:01

Felix Nawothnig


1 Answers

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;
like image 94
Nicol Bolas Avatar answered Nov 07 '22 04:11

Nicol Bolas