11
\$\begingroup\$

I'm working on a front-to-back renderer for a 2D engine using an orthographic projection. I want to use the depth buffer to avoid overdraw. I have a 16-bit depth buffer, a camera at Z=100 looking at Z=0, zNear is 1, and zFar is 1000. Each sprite rendered sets its Z co-ordinates to increasingly distant values, allowing depth test to skip rendering anything which is underneath.

However I'm aware the way Z positions end up with Z buffer values is non-linear. I want to make use of the full resolution of the 16-bit depth buffer, i.e. allowing 65536 unique values. So for every sprite rendered, I want to increment the Z position to the next position to correlate to the next unique depth buffer value.

In other words I want to turn an incrementing index (0, 1, 2, 3...) of the sprite being drawn in to the appropriate Z position for each sprite to have a unique depth buffer value. I'm not sure of the maths behind this. What is the calculation to do this?

Note I'm working in WebGL (basically OpenGL ES 2), and I need to support a wide range of hardware, so while extensions like gl_FragDepth might make this easier, I can't use it for compatibility reasons.

\$\endgroup\$
5
  • \$\begingroup\$ I can't imagine using the z buffer will offer you much of a performance gain (if any) after you have added all of the z buffer writing, computations and comparisons vs copying textures back to front, not to mention any alpha transparency/blending woes. \$\endgroup\$
    – Matt Esch
    Commented May 22, 2015 at 10:15
  • \$\begingroup\$ @MattEsch: The idea is that all those calculations are done in the GPU at blazingly high speeds, so it does make sense to do that. \$\endgroup\$ Commented May 22, 2015 at 10:48
  • \$\begingroup\$ @MattEsch: FWIW this is aimed at Intel integrated GPUs, which use system memory instead of dedicated GPU memory. This makes them pretty slow and liable to hitting fill-rate limits if overdrawing a lot of sprites. Intel recommended this approach to me as a way of working around it. Presumably their implementation of depth testing is well optimised and can save lots of fill-rate. It remains to be seen though, I haven't profiled it yet! \$\endgroup\$ Commented May 22, 2015 at 11:30
  • \$\begingroup\$ @PandaPajama block copying memory is actually really fast, so if you were just blitting textures onto a surface it would be very fast indeed. The first major overhead is getting data onto the GPU in the first place, which as Ashley points out can be more expensive on integrated GPUs. You find that even a lot of 3d games do a non-trivial amount of work on the CPU (like the bone animation) because uploading the data required to do those matrix computations in the first place is too expensive. \$\endgroup\$
    – Matt Esch
    Commented May 22, 2015 at 12:01
  • \$\begingroup\$ @MattEsch: There's only so much you can do with just blitting. Rotations, scaling and deformations come to mind, but also since you have pixel/vertex shaders, the limit of what you can do with hardware is much higher than what you can do with just blitting. \$\endgroup\$ Commented May 23, 2015 at 2:22

3 Answers 3

6
+50
\$\begingroup\$

Indeed, the values stored in the z-buffer are not linear to the actual z coordinates of your objects, but to their reciprocal, in order to give more resolution to what's near the eye than to what's closer to the back plane.

What you do is that you map your zNear to 0 and your zFar to 1. For zNear=1 and zFar=2, it should look like this

Zbuffer

The way to calculate this is defined by:

z_buffer_value = k * (a + (b / z))

Where

 k = (1 << N), maximum value the Z buffer can store
 N = number of bits of Z precision
 a = zFar / ( zFar - zNear )
 b = zFar * zNear / ( zNear - zFar )
 z = distance from the eye to the object

...and z_buffer_value is an integer.

Above equation is brought to you courtesy of this awesome page, which explains z-buffers in a really good way.

So, in order to find the necessary z for a given z_buffer_value, we just clear the z:

z = (k * b) / (z_buffer_value - (k * a))
\$\endgroup\$
3
  • \$\begingroup\$ Thanks for the answer! I'm a little confused how you got your final formula though. If I take z_buffer_value = k * (a + (b / z)) and simply rearrange to solve for z, then I get: z = b / ((z_buffer_value / k) - a) - how did you arrive at the different last formula? \$\endgroup\$ Commented May 22, 2015 at 10:39
  • \$\begingroup\$ @AshleysBrain: you take the denominator (v / k) - a => (v - k * a) / k, and collapse to (k * b) / (v - (k * a)). It's the same result. \$\endgroup\$ Commented May 22, 2015 at 10:43
  • \$\begingroup\$ Ah, I see. Thanks for the answer, it's working nicely! \$\endgroup\$ Commented May 22, 2015 at 11:27
-1
\$\begingroup\$

Maybe you should change You'r approach to something simpler. What I would do; Keep your Z depth thing, but keep a list of what you render. Order that list based on the z Depth value, and render objects in the order of the list.

Hope this can help. People always tell me to keep things simple.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Sorry, that's not much help. I am already doing that. The question is about what Z positions to choose. \$\endgroup\$ Commented May 21, 2015 at 23:09
-1
\$\begingroup\$

Since you already have a sorted list of things to render (front to back) do you really need to increment the Z index? Can't you use "less or equal" for the "checking function"? This way it would actually check if a specific pixel was already drawn or not.

\$\endgroup\$
1
  • \$\begingroup\$ "less or equal" will still cause absolutely everything to be overdrawn, since everything would always have an equal Z index and therefore pass depth test. \$\endgroup\$ Commented Jul 9, 2015 at 10:30

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .