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;