26

So, I've got an imposter (the real geometry is a cube, possibly clipped, and the imposter geometry is a Menger sponge) and I need to calculate its depth.

I can calculate the amount to offset in world space fairly easily. Unfortunately, I've spent hours failing to perturb the depth with it.

The only correct results I can get are when I go:

gl_FragDepth = gl_FragCoord.z

Basically, I need to know how gl_FragCoord.z is calculated so that I can:

  • Take the inverse transformation from gl_FragCoord.z to eye space
  • Add the depth perturbation
  • Transform this perturbed depth back into the same space as the original gl_FragCoord.z.

I apologize if this seems like a duplicate question; there's a number of other posts here that address similar things. However, after implementing all of them, none work correctly. Rather than trying to pick one to get help with, at this point, I'm asking for complete code that does it. It should just be a few lines.

genpfault
  • 51,148
  • 11
  • 85
  • 139
geometrian
  • 14,775
  • 10
  • 56
  • 132
  • Have you written a vertex shader as well? or only a fragment shader? – Michael Slade Apr 22 '12 at 04:08
  • 2
    I won't give you code on general principle, but I can give you [this link to the OpenGL Wiki](http://www.opengl.org/wiki/Compute_eye_space_from_window_space). As well as this link to a tutorial of mine on [impostors and depth that shows how to do this as well](http://www.arcsynthesis.org/gltut/Illumination/Tut13%20Deceit%20in%20Depth.html). Transforming the depth back is trivial. – Nicol Bolas Apr 22 '12 at 05:08
  • Michael: yes, but it's just a pass through shader. As it happens, the calculations are done in world space, so I can calculate eye space in the fragment program. Nicol, I had already seen that page. I implement it as: vec4 clip_pos = gl_ProjectionMatrix * vec4(eye_pos,1.0); float ndc_depth = clip_pos.z / clip_pos.w; gl_FragDepth = (((clip_far-clip_near) * ndc_depth) + clip_near + clip_far) / 2.0; Unfortunately, the depth appears to fall outside the depth range, even though there's no offsetting. Thanks, – geometrian Apr 22 '12 at 05:28
  • 1
    @IanMallett: What are `clip_near` and `clip_far`? Because those sound suspiciously like the near and far clip distances used to compute the perspective projection matrix. – Nicol Bolas Apr 22 '12 at 05:58
  • They are. Shouldn't they be? I had tried using gl_DepthRange and in the example, but the values, preliminarily, didn't seem to be correct on my card. – geometrian Apr 22 '12 at 07:43
  • Nevermind; I'm just an idiot. gl_DepthRange is 0.0 to 1.0 by default. I set the variables to that instead and it worked. Thanks! – geometrian Apr 23 '12 at 19:49

2 Answers2

36

For future reference, the key code is:

float far=gl_DepthRange.far; float near=gl_DepthRange.near;

vec4 eye_space_pos = gl_ModelViewMatrix * /*something*/
vec4 clip_space_pos = gl_ProjectionMatrix * eye_space_pos;

float ndc_depth = clip_space_pos.z / clip_space_pos.w;

float depth = (((far-near) * ndc_depth) + near + far) / 2.0;
gl_FragDepth = depth;
geometrian
  • 14,775
  • 10
  • 56
  • 132
  • Shouldn't "gl_DepthRange.far" and "gl_DepthRange.near;" be replaced with the near and far ranges of the projection matrix instead of the clip plane values? – Tara Sep 21 '15 at 01:00
  • Not at all. gl_DepthRange is not the same thing as the near and far plane values used to calculate the projection matrix. I might be misunderstanding your point though. – Tara Sep 21 '15 at 01:38
  • @Dudeson The clip planes simply determine, via the projection matrix, which values will map outside the NDC (and therefore be clipped by the hardware). We're completely done with them after the projection matrix is specified. Meanwhile, `gl_DepthRange.(ne|f)ar` is the range of the depth buffer, and is needed to convert from NDC to the depth buffer. HTH – geometrian Sep 21 '15 at 02:36
  • Exactly. I'm sorry, it seems I didn't realize you're converting from NDC to the depth buffer (in which case usually `(ndc.z + 1.0) * 0.5` is sufficient). I thought you were converting from linear depth values to the depth buffer. – Tara Sep 21 '15 at 03:01
  • @Dudeson Note that for the usual values of `gl_DepthRange`, the penultimate line in the source is exactly this :) – geometrian Sep 21 '15 at 03:05
  • That's exactly what I meant. `(ndc.z + 1.0) * 0.5` is just faster to compute. – Tara Sep 21 '15 at 03:32
  • It is worth mentioning that gl_FragCoord.z and gl_FragDepth are in window/screen-space. If you do not write gl_FragDepth in the fragment shader, it is automatically calculated in the fixed-function pipeline from the gl_Position you write in the vertex shader. gl_FragDepth = gl_FragCoord.z = (((far-near) * clip_space_pos.z / clip_space_pos.w) + near + far) / 2.0. When calculating your own gl_FragDepth you have to do the perspective divide and screen transform on your own to write a window/screen-space z-value. – Bim Feb 22 '18 at 14:23
7

For another future reference, this is the same formula as given by imallett, which was working for me in an OpenGL 4.0 application:

vec4 v_clip_coord = modelview_projection * vec4(v_position, 1.0);
float f_ndc_depth = v_clip_coord.z / v_clip_coord.w;
gl_FragDepth = (1.0 - 0.0) * 0.5 * f_ndc_depth + (1.0 + 0.0) * 0.5;

Here, modelview_projection is 4x4 modelview-projection matrix and v_position is object-space position of the pixel being rendered (in my case calculated by a raymarcher).

The equation comes from the window coordinates section of this manual. Note that in my code, near is 0.0 and far is 1.0, which are the default values of gl_DepthRange. Note that gl_DepthRange is not the same thing as the near/far distance in the formula for perspective projection matrix! The only trick is using the 0.0 and 1.0 (or gl_DepthRange in case you actually need to change it), I've been struggling for an hour with the other depth range - but that is already "baked" in my (perspective) projection matrix.

Note that this way, the equation really contains just a single multiply by a constant ((far - near) / 2) and a single addition of another constant ((far + near) / 2). Compare that to multiply, add and divide (possibly converted to a multiply by an optimizing compiler) that is required in the code of imallett.

the swine
  • 10,713
  • 7
  • 58
  • 100
  • Thanks for the useful info. Note that your formula `(1.0 - 0.0) * 0.5 * f_ndc_depth + (1.0 + 0.0) * 0.5`, when you factor the terms, becomes the same as `(f_ndc_depth + 1.0) * 0.5` that other people were sharing in the other comments in here. – R. Navega Feb 25 '21 at 04:53