[Public WebGL] determining depth of z-buffer
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:
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 )
> 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
> 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:
>> 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
> 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:
More information about the public_webgl