# [Public WebGL] determining depth of z-buffer

Thatcher Ulrich [email protected]
Mon Feb 7 04:29:23 PST 2011

```I made a visualizer for comparing different depth functions under
different conditions.

http://tulrich.com/geekstuff/log_depth_buffer_vis.html

Steve's Z-buffer page mentions something about SGI's Infinite Reality
claiming to get equivalent useful precision in 15 bits compared to
conventional at 24 bits, which makes me wonder what they did.  I'm
curious if anybody has specific documentation for that feature.

-T

On Thu, Feb 3, 2011 at 7:12 PM, Thatcher Ulrich <[email protected]> wrote:
> 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
>
> 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,
> http://www.gamasutra.com/blogs/BranoKemen/20090812/2725/Logarithmic_Depth_Buffer.php
>
> -T
>

-----------------------------------------------------------
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
-----------------------------------------------------------

```