[Public WebGL] determining depth of z-buffer

Thatcher Ulrich [email protected]
Thu Feb 3 10:12:43 PST 2011

On Thu, Feb 3, 2011 at 2:58 PM,  <[email protected]> wrote:
> I've interspersed my responses below:
>> I've used the two-pass trick and I like it.  In practice I found that
>> I had to leave like 10% overlap in z to avoid the possibility of a
>> visible crack, but this was around 10 years ago; maybe GPUs don't need
>> so much margin anymore.
>> Note that the ideal zmid value is NOT halfway between znear and zfar;
>> instead you want znear/zmid == zmid/zfar, so zmid = sqrt(znear*zfar)
> My "Learn to love your Z buffer" page:
>   http://www.sjbaker.org/steve/omniv/love_your_z_buffer.html

Yup, I know it well, I use it, I grok it.

> ...has the equation for how to calculate the value that's actually written
> into the Z buffer:
>  z_buffer_value = (1<<N) * ( a + b / z )
>  Where:
>     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.
> But if zFar is MUCH larger than zNear (as is almost always the case) -
> then 'a' is more or less 1.0 and b is more or less -zNear so the equation
> simplifies to:
>  z_buffer_value = (1<<N) * ( 1 - zNear / z )
> ...and (crucially) that doesn't depend on zFar!   It follows from this
> that for most 'normal' applications, you might as well stick zFar out
> somewhere near infinity...which is what I recommended as the solution to
> the problem that kicked off this thread.
> A handy way to think about Z precision is that (with a 24 bit Z buffer),
> the range at which the Z buffer is 1% accurate is Z=170,000*zNear and the
> range at which it's 5% accurate is Z=1 million * zNear.
>> For the case where you have distinct bands, Steve's scaling trick is
>> clever.  I suspect you can also get the same effect by playing with
>> the projection matrix.  And, there is gl.depthRange to kind of do the
>> same thing.  I've never used it but I think it's equivalent to playing
>> with the projection matrix.  The big drawback of course is it doesn't
>> help if your data is continuous throughout the z range.
>> I revisited this problem in Jan 2010, and the crazy thing is, there is
>> a simple hardware solution!  If, instead of writing a value based on z
>> or 1/z into the depth buffer, the hardware wrote:
>>   log(view_z / z_near) / log(z_far / z_near)
>> you would get constant relative precision throughout the z range, and
>> 16-bit z-buffers would be more or less adequate for planetary scale
>> rendering!  24 bits would be enough for pretty much any imaginable
>> purpose!
> I strongly disagree!
> That solution is generally called a 'W-buffer' - and I think Direct3D
> supports it, although OpenGL does not without extensions (or shader hacks
> that entail writing to gl_FragDepth).

It's totally different than W-buffer.

> However, it is not the panacea you imagine.  Suppose you want to draw the
> Earth and the space-shuttle in orbit around it.  24 bits of W gives you a
> one part in ~16 million precision.  The earth is 12,700 km in diameter -
> which means that if you scale it to fit within a 24 bit W-buffer you have
> a precision of roughly 1 meter.  Now try to draw a space-shuttle with a
> depth precision of only 1 meter!  It'll look like complete crap!!

The log function gives constant relative precision.  E.g.

  let z == distance from viewpoint in world space
  f(z) == the integer written into the z-buffer == (2^k - 1) * log(z /
zn) / log(zf / zn)

  let g(i) == the camera z value that gives a depth-buffer integer value of i
      == zn * exp((i / 2^k) * log(zf / zn))

Some examples for a 16-bit z-buffer, drawing planetary-scale models --
let's make the world coords in meters, set zn to 1 meter and zf to
100M meters (~16 Earth radii):

  let zn = 1
  let zf = 100e6
  f(zn) == 0
  f(zf) == 65535
  g(0) = 1
  g(1) = 1.000281
  g(2) = 1.000562
  g(65534) = 99971895.79
  g(65535) = 100000000.0

We can compute relative precision, and it turns out it is constant:

  rel(z) == how far to the next discrete z value, divided by z
     == (g(f(z) + 1) - z) / z
  rel(anything) ~= 0.000281

So at zn == 1 meter, the precision is 0.2 millimeters and at zf ==
100,000 km, the precision is 28 km.  The precision at 100 meters is
2.8 cm, the precision at 100km is 28m.  Etc.

If that's not good enough, shrink the zf/zn range, go to 24 bits, or
indulge in the other hacks we've been talking about.

> In fact, just consider a "normal" outdoor scene.  At ground level, the
> horizon is maybe 8km away - so 24 bits of W would give you (on paper)
> about a half-millimeter of precision everywhere.  That might be OK...but
> actually, accumulated round-off error throughout the graphics chain would
> probably erode that to more like 3mm - and that's pretty nasty.
> The reason we want this funny screwed up Z-buffer format is in order to
> have good precision near the camera where you can see it - and relatively
> poor precision out at the horizon where you don't.
>> I did some experiments at the time using WebGL and the gl_FragDepth
>> feature, and it appears to work great in practice.  However,
>> gl_FragDepth didn't work at all on one of my machines, and apparently
>> it is no longer valid in WebGL.  The other problem is that if you used
>> it, the driver would need to disable any hardware hierarchical z,
>> which would be bad for performance.
> Yes - that's true.  Even without hierarchical Z, most hardware will do a Z
> buffer test BEFORE running the fragment shader - with huge savings when
> things are hidden behind nearer objects - but if you write to gl_FragDepth
> then that optimization is turned off.
>> I went looking for corroboration of my results and discovered that
>> Brano Kemen had recently written a couple of great blog posts on
>> Gamasutra exploring the same phenomenon:
>> http://www.gamasutra.com/blogs/BranoKemen/20090812/2725/Logarithmic_Depth_Buffer.php
>> http://www.gamasutra.com/blogs/BranoKemen/20091231/3972/Floating_Point_Depth_Buffers.php
>> His conclusion is that you can get the positive effects of a log depth
>> buffer by using a floating-point depth buffer, and running the values
>> backwards (1 == near, 0 == far).
> Yes - but at the price of an extra divide per pixel.

What?  I think this is just a projection matrix tweak, plus using the
opposite z-buffer func.

>> I wonder why log depth buffer wasn't written into the original OpenGL
>> spec?  Did nobody discover it before Brano Kemen in 2009?  Or was it
>> just too expensive to have to do a log() on every pixel?
> WAY too expensive!
> What we have is (essentially) a reciprocal-Z buffer instead of a
> logarithmic Z.  The shapes of those curves are fairly similar - so the
> reciprocal-Z approach is almost as good...and it saves one divide per
> vertex and one divide per pixel - back then, a 24 bit divide circuit would
> fill an entire chip and horribly limit your clock rate.
> Remember, this stuff pre-dates OpenGL by quite a long way.  The original
> Silicon Graphics "Geometry Engine" in the Personal-IRIS (probably the
> first hardware accelerated 3D engine with a Z-buffer that you could
> actually buy) did Z just like modern WebGL does.
> Before that, hardware 3D didn't do Z buffering at all - the (~$1,000,000)
> flight simulator graphics hardware of that era generally used
> depth-sorting tricks (specifically "separating planes" - which are akin to
> BSP trees) to kinda-sorta solve the ordering problem without using a depth
> buffer of any kind.
> Those early SGI machines used "IrisGL" - which was the progenitor of
> OpenGL.  The first OpenGL implementation was running on SGI "Onyx"
> hardware that was dual-purpose IrisGL/OpenGL, so the Z buffer arrangements
> were the same.  It's really only in the last 10 years that doing a divide
> per-pixel has been considered a "do-able thing" without massive loss of
> efficiency.
> Later SGI machines used some kind of a lookup table to somewhat linearize
> Z without making it completely linear (which, as I said, is undesirable).
> It was a nice compromise between a pure Z-buffer and a pure W-buffer - but
> sadly that trick hasn't made it into PC hardware.

I'm curious if they knew about this log math and elected not to use
it, or just weren't aware of it.  I had never heard of it until a year
ago.  The earliest reference I can find on the web is Brano Kemen's
post from 2009-08-12,


You are currently subscribed to [email protected]
To unsubscribe, send an email to [email protected] with
the following command in the body of your email:
unsubscribe public_webgl

More information about the public_webgl mailing list