I am trying to draw spheres using a billboard, so I have a normal map with a z component that I am sampling in my fragment shader. I just need to add this z component to the depth of my fragment to get proper 3D spheres...

How do you determine the current fragment's z-depth, add my offset and then set gl_FragDepth?

I've found lots of confusing contradictory hints and partially working bits of code all over the 'net, but no definitive clear answer.


final result (no shadowing) http://www.youtube.com/watch?v=88idbFlvQV0


I just need to add this z component to the depth of my fragment to get proper 3D spheres...

First of all, this will not work. Unless your spheres are very small (relative to the camera), simply adding to the depth will not create a proper impostor. If the sphere is towards the edge of the screen, then you will not get a proper visualization effect. A sphere becomes oblong towards the edge of the screen, and the "up" direction for a particular point on the sphere isn't the same everywhere.

You have to use ray tracing to render a proper sphere impostor for spheres of a decent size. And if your spheres are always too small to notice, then you won't notice the lack of depth bias either.

Also, I'm not sure what good biasing the depth will do. It will certainly make interpenetrations more realistic, but that's about it. The bump-mapped lighting will do far more for your visuals than bumping the depth of the fragment.

However, if you insist on doing this without raytracing (a version with raytracing is available here), the first step is to take the current Z value and transform it back into clip-space. This will help linearize it. This has been asked and answered. That gets us back to clip space. But you need to take that clip-space position and go back to camera space. So you need the inverse projection matrix. Given that, you do this:

 vec4 cameraSpacePosition = invProjMat * vec4(0, 0, clipDepth, gl_FragCoord.w);

Next, you bias the camera space Z position with a camera space offset. It's important that the offset be in camera space; otherwise, it doesn't work.

Once you have that, you just perform the transformation back to window space:

cameraSpacePosition.z += bias;
vec4 clipPos = projMat * vec4(cameraSpacePosition, 1.0);
float ndcDepth = clipPos.z / clipPos.w;
gl_FragDepth = ((gl_DepthRange.diff * ndcDepth) +
    gl_DepthRange.near + gl_DepthRange.far) / 2.0;
  • 1
    \$\begingroup\$ Thank you. You are of course spot on on your criticism, but it I have a large number of small balls to draw. The problem I've found however is performance; with flat impostors, integrated intel cards can push 10K or more at 60fps, which was stunning and why I took that root. Computing the z in the fragment shader with all that matrix math etc only knocked it down to 50 fps. But setting the gl_FragDepth - even to a fixed value rather than computing a real value - knocked the fps down to ~5! I speculate that the compiler puts an early z cull only if your fragment shader doesn't compute z :( \$\endgroup\$
    – Will
    Aug 31 '11 at 12:24
  • \$\begingroup\$ @Will: Of course writing to the depth turns off early z-tests; how else could it possibly work? It doesn't know what your depth value is going to be, so any tests it might do are wrong. Honestly, if the balls you're drawing aren't big enough for perspective to be an issue (and thus requiring raytracing), then depth isn't going to be much of an issue for them as well. \$\endgroup\$ Aug 31 '11 at 18:14
  • \$\begingroup\$ I was rather impressed that there was an early z cull on the cards at all. Unfortunately my billboards fight each other for z as you make small rotations of the camera as there are lots of intersections. I feel so close to a good solution and yet so far. I say 'small' meaning less than 64x64. I can't afford to turn the scene into a mesh unfortunately. \$\endgroup\$
    – Will
    Aug 31 '11 at 18:43
  • \$\begingroup\$ The line vec4 cameraSpacePosition = invProjMat * vec4(0, 0, clipDepth, gl_FragCoord.z); should be vec4 cameraSpacePosition = invProjMat * vec4(0, 0, clipDepth, gl_FragCoord.w);. \$\endgroup\$
    – plasmacel
    Dec 8 '19 at 2:21
  • \$\begingroup\$ Thinking more about it, I still have a feeling that something is not okay with it. Wouldn't be invProjMat * vec4(0, 0, clipDepth, 1.0 / gl_FragCoord.w); more appropriate? gl_FragCoord.w is computed as 1.0 / gl_Position.w, where gl_Position is in clip space. Now we unproject from clip space back to camera space, so we need clipDepth and clipW = 1.0 / gl_FragCoord.w. \$\endgroup\$
    – plasmacel
    Dec 8 '19 at 15:12

According to the GLSL specification (both 1.10 and 4.20) gl_FragCoord holds the window relative coordinates of the fragment, from which z is the depth that gets written into the depth buffer (if not changing gl_FragDepth). So you can just write

gl_FragDepth = gl_FragCoord.z + depthFromTexture;
  • \$\begingroup\$ The z depth is not linear, its log. The spec doesn't describe how to decode and encode values. \$\endgroup\$
    – Will
    Aug 30 '11 at 12:21
  • 1
    \$\begingroup\$ @Will Oh yes, forgot about the non-linearity, but the decoding only depends on you projection matrix. Actually gl_FragCoord.w contains the negative reciprocal of the eye-space depth. Maybe you can use this and then retransform it into the non-linear [0,1] range? \$\endgroup\$ Aug 30 '11 at 12:33
  • \$\begingroup\$ don't you need to know the near and far too? I mean, I've been struggling to work this out myself so I was rather holding out for a definitive answer :/ \$\endgroup\$
    – Will
    Aug 30 '11 at 14:59
  • \$\begingroup\$ @Will Yes, as the eye space depth is in, well, eye space. So you need the near and far values for transforming it back into post projective space, or maybe you can use the values of the projection matrix directly, as they are encoded in there. Maybe this is a bit of help (assuming you compute your shader projection matrix similarly). \$\endgroup\$ Aug 30 '11 at 15:27
  • 1
    \$\begingroup\$ @Will: The specification very much does describe how to decode and encode them. \$\endgroup\$ Aug 30 '11 at 18:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.