Operating with multiple z-buffers

If there can be two z-buffers, Z1 and Z2, I would like to only draw fragments which fail the depth test for Z1, but pass the depth test for Z2. If the depth in Z2 is at any point less than the depth in Z1, then no geometry could be drawn in that region as per the above rules. Any geometry which is drawn updates a third buffer, Z3, which could recursively partition the space between Z2 and Z3.

Is there any way to simulate this effect in OpenGL? A search on Google can only find a proposal of this capability back in 1996.

I am not aware of an OpenGL implementation that allows you to create multiple z-buffers (even stereo have one z-buffer)

What you can do to simulate this partially in hardware is to section a single window into 4 parts.

Pseudo code
{
Render in section 1
Read z-buffer of section 1
Read z-buffer of section 2
Read z-buffer of section 3
if z-test passes on section 2 and 3
{ Render to color buffer and z-buffer of section 4
}
}

Using glReadPixel and glDrawPixels may help!

V-man

Originally posted by V-man:
[b]
What you can do to simulate this partially in hardware is to section a single window into 4 parts.

Pseudo code
{
Render in section 1
Read z-buffer of section 1
Read z-buffer of section 2
Read z-buffer of section 3
if z-test passes on section 2 and 3
{ Render to color buffer and z-buffer of section 4
}
}

Using glReadPixel and glDrawPixels may help!
[/b]

Thanks for the suggestion! That’s an interesting idea…I still wonder how we can perform a single depth test using two different depth functions though. That is especially challenging since OpenGL assumes only one depth buffer. I think I should explain my dilemma in greater detail.

Basically what I have is a magic box whose interaction is very limited. It has a drawStuff() function which uses standard OGL begin/end routines to submit geometry (triangles, triangle strips, etc). DrawStuff() always outputs the same shapes, but we cannot assume anything about what it looks like aside from it always being processed in the same order. I can adjust the state of the OpenGL context, like enabling or disabling tests and such, but that’s the only interaction I can have.

The problem is that the magic box likes to generate a lot of unsorted and intersecting translucent geometry. I cannot fix this, it’s just what the box does. As we all know, alpha-blended surfaces need to be sorted back to front in order to render properly. The nature of the problem makes this impossible. So what I need to do it to come up with some way to make the final scene look as good as I can (and in real-time!).

It turns out that it is easy to produce a good approximation for the final scene without sorting anything. My method relies on the assumption that, after a few layers of clear surfaces, we can ignore anything else beyond aside from solids - presumably that after looking through several layers of translucency, the region is sufficiently opaque that it can be treated as such. Unfortunately, this method requires more than one depth buffer (one buffer for each level of blending precision). Here’s how it works:

Using the alpha test, draw only the opaque elements of the scene as one normally would. The depth information from this is stored in a depth buffer Z1.

Now, we will preserve Z1 and activate writing to a new buffer Z2. Writing to the color buffer is disabled. Using the alpha test again we will draw only the translucent shapes (the test against Z1 is always active, but is now read-only). Since we are maintaining depth information in Z2, we will end up with a buffer containing the depths of the “frontmost layer” of transparent fragments.

Next, we will preserve Z2 and activate a new buffer Z3. We will again draw the translucent shapes as above, passing the depth test for Z1 but also failing the test for Z2. Afterward, we will have a buffer with the depth information for the second layer of translucency.

We can continue this process for any amount of precision we desire (just a few should be enough in most cases). When finished, the color buffer only has information for the opaque shapes. We will iterate backwards through our list of z-buffers and blend in each level by drawing the scene with the depth function EQUAL.

OK; a bit long and poorly written I’m sure. Perhaps there is some way of accomplishing this with only one depth buffer…Any suggestions?

Go to NVIDIA’s developer webpage. There you can find a paper on drawing transparent surfaces correctly without sorting. If I remember correctly, the method required a GF3.

Originally posted by DFrey:
Go to NVIDIA’s developer webpage. There you can find a paper on drawing transparent surfaces correctly without sorting. If I remember correctly, the method required a GF3.

From the paper: “The technique presented here solves the problem of order dependence by using a technique we call depth peeling. Depth peeling is a fragment-level depth sorting technique described … by Diefenbach using a dual depth buffer.”

I knew it! OpenGL doesn’t have enough depth buffers!

It looks like the GF3 can make up for the lack of this, however. The only problem is that I need a general-purpose implementation which is hardware agnostic.

Use the stencil buffer.
clear color/stencil (to 0)/depth

disable color writes
enable depth writes/test
Set alpha test to allow only transparent geometry.
Draw stuff.

Set Stencil Func(GL_LESS,8,255)
Set Stencil Op(GL_KEEP,GL_KEEP,GL_REPLACE)
Draw stuff

You now have a one bit set (8) in the stencil buffer at the front layer transparent faces

Clear Depth
Set Stencil Func(GL_LESS,12,8)
Set Stencil Op(GL_KEEP,GL_KEEP,GL_REPLACE)
Draw stuff

The second layer now has two bits set, 4+8=12 (note that due to the mask of 8, only the first bit from the first part takes part in the stencil comparison)

Clear Depth
Set Stencil Func(GL_LESS,14,4)
Set Stencil Op(GL_KEEP,GL_KEEP,GL_REPLACE)
Draw stuff

Clear Depth
Set Stencil Func(GL_LESS,15,2)
Set Stencil Op(GL_KEEP,GL_KEEP,GL_REPLACE)
Draw stuff

We are now four transparency layers into the screen (we have appropriate depth information for that) and can start recursively working our way back.

enable color writes
set depth function to GL_EQUAL
Set Stencil Func(GL_GREATER,14,255)
Set Stencil Op(GL_KEEP,GL_KEEP,GL_REPLACE)
Draw stuff
disable color writes

We have now drawn the backmost transparent layer

set depth function to GL_LESS (or GL_LEQUAL)
Set Stencil Func(GL_EQUAL,14,255)
Set Stencil Op
(GL_KEEP,GL_KEEP,GL_KEEP)
Draw stuff

We now have the depth information for the second backmost layer in the depth buffer

enable color writes
set depth function to GL_EQUAL
Set Stencil Func(GL_GREATER,12,255)
Set Stencil Op(GL_KEEP,GL_KEEP,GL_REPLACE)
Draw stuff
disable color writes

set depth function to GL_LESS (or GL_LEQUAL)
Set Stencil Func(GL_EQUAL,12,255)
Set Stencil Op
(GL_KEEP,GL_KEEP,GL_KEEP)
Draw stuff

enable color writes
set depth function to GL_EQUAL
Set Stencil Func(GL_GREATER,8,255)
Set Stencil Op(GL_KEEP,GL_KEEP,GL_REPLACE)
Draw stuff
disable color writes

set depth function to GL_LESS (or GL_LEQUAL)
Set Stencil Func(GL_EQUAL,8,255)
Set Stencil Op
(GL_KEEP,GL_KEEP,GL_KEEP)
Draw stuff

enable color writes
set depth function to GL_EQUAL
Set Stencil Func(GL_GREATER,0,255)
Set Stencil Op(GL_KEEP,GL_KEEP,GL_REPLACE)
Draw stuff
disable color writes

set depth function to GL_LESS (or GL_LEQUAL)
Set Stencil Func(GL_EQUAL,0,255)
Set Stencil Op
(GL_KEEP,GL_KEEP,GL_KEEP)
Draw stuff

That’s it, you have now four layers of transparency, fully sorted. Of course you’ve done a lot of passes and I haven’t sorted out yet where the opaque stuff fits into the picture. But I think this should work in all possible geometry cases.

[This message has been edited by zeckensack (edited 03-14-2002).]

[This message has been edited by zeckensack (edited 03-15-2002).]

Originally posted by zeckensack:
clear color/stencil (to 0)/depth

Set Stencil Func(GL_LESS,8,255)
Set Stencil Op(GL_KEEP,GL_KEEP,GL_REPLACE)
Draw stuff

I realize that I’m no expert on the stencil buffer, but I can’t see how this first iteration accomplishes anything.

If the stencil buffer is initialized to zero and our reference value is 8 (the mask is all ones so we can ignore it), then the stencil test will always fail because GL_LESS requires that the ref. (8) is less than the value already in the buffer (0) in order to pass. So it looks like only GL_KEEP is used in the failure condition; that will keep the buffer in it’s initialized state.

Could you please shed some more light on this?

[This message has been edited by Decimal Dave (edited 03-15-2002).]

Doh, just reread that section in the spec. I had assumed that the test worked the other way round (ie GL_LESS -> buffer value<masked reference value). You know, I worked this one out only with a little reference to the spec as the problem sounded quite interesting to me. So I still can’t guarantee it. But if my ideas from yesterday (which puzzle me quite a bit right now) hold, simply reversing all stencil functions (GL_LESS -> GL_GREATER; GL_GREATER -> GL_LESS; GL_EQUAL->GL_EQUAL) should work.

Addendum:
On second thought, I think the first pass is bogus. Replace (GL_GREATER,8,255) with (GL_ALWAYS,8,255). Otherwise, a random layer would win this pass, not the frontmost one.

Ok, I’m currently whipping up a test app with only transparent surfaces and a max depth complexity of 5. I’ll just test it out and see whether my theories hold.

As I’ve just seen, the whole idea is garbage. It was all based on an assumption I made that just didn’t hold true. The order of peeling into and out of the depth buffer is completely random. I wanted to guard against this by just allowing a single rewrite per pixel per pass but as it turns out, that first pixel to be written is not necessarily in the right depth layer.

Sorry for wasting your time with this.

I’m not sure it was totally wrong; it might be possible to isolate individual layers using the stencil buffer. The issue of pixels being drawn in any layer in any order is problematic, however.

Does anyone know of some other crazy depth/stencil buffer tricks that might help?

[This message has been edited by Decimal Dave (edited 03-16-2002).]