Alpha-blending and the Z-buffer.


By Steve Baker

Introduction - Bad news. REALLY Bad News.

Shock! - Horror!...

The Z buffer doesn't work for transparent polygons.

The problem is that the Z buffer prevents OpenGL from drawing pixels that are behind things that have already been drawn. Generally, that's pretty convenient - but when the thing in front is translucent, you NEED to see things that are behind it.

A First Quick Fix.

The first fix - upon which all the other fixes depend is to make sure you draw all your opaque polygons before you draw any translucent ones. This is easy to do in most applications and solves most of the problems. The only thing that remains is when you try to render one translucent polygon behind another.

For many applications there are so few translucent objects that this is "good enough".

Another Good Trick

Quite often, alpha-blended polygons are used with textured alpha to make 'cutout' objects. If you want to draw something complicated like a tree, you probably can't afford a polygon for each leaf and branch - so you use an alpha texture map and a photo of a tree.

The point is that this polygon may well have no partially translucent pixels - there are lots of utterly opaque ones in the middle of the tree - and lots of utterly transparent ones around the outside. In principle, there shouldn't be a problem with Z buffering...but there is because by default, even the totally transparent pixels will write to the Z buffer.

Fortunately, OpenGL has a function that can prevent pixels with a specified set of alpha values from writing the colour or Z buffers. For example:


     glAlphaFunc ( GL_GREATER, 0.1 ) ;
     glEnable ( GL_ALPHA_TEST ) ;

This will only allow pixels with alpha values greater than 0.1 to write to the colour or Z buffers. You have to use this with care though - bear in mind that if your texture filter is set to one of the LINEAR or MIPMAP modes (eg GL_LINEAR_MIPMAP_LINEAR) then even if the top level texture map contains only 0.0 and 1.0, intermediate values will creep in during the filtering process.

However, this is another thing that will reduce the number of problems associated with Z-buffered rendering of alpha-blended polygons.

Disabling Z-write for Translucent Polygons.

This is a technique that many people advocate - unfortunately it doesn't really help. The theory is that if a translucent polygon doesn't write to the Z buffer then subsequent polygons that are written behind it will not be occluded.

If all the translucent polygons have the same colour, then this does actually work - but for normal glBlendFunc settings and polygons of differing colours, the order that polygons are blended into the frame buffer also matters.

Consider two polygons, one red, the other blue - rendered against a green background. Both are 50% transparent. The red one is in front, the blue one is behind, the green background is behind that.

The final colour should be 50% red, 25% green and 25% blue.

Look at the various possible options, and the colour after each rendering step:

So you see that no matter whether you enable or disable the Z buffer, the colour only comes out right if you render FAR to NEAR - which means that you have to sort your polygons as a function of depth.

There are other algorithms entailing use of "destination alpha" (which doesn't work with most PC-based hardware) - but they suffer from similar problems.

Depth Sorting

The next thing that most people consider is to sort the translucent polygons as a function of Z depth.

To be perfect - even sorting them isn't enough. You may have to split polygons up on-the-fly to get *perfect* rendering. Consider this pathalogical case:

 
                  /\/\
                 /  \ \
                 \   \/
                 /\   \
                /  \   \
               /   /\   \
          ____/   /__\___\__
         |   /   /          |
         |__/   /___________|
           /___/        \___\
 
There is no way to sort this to make it work.

Some people suggest that if you stick with 'convex' objects, then you don't need to sort at the polygon level at all - that's equally easy to disprove:

 
                  /\/\
                 /  \ \
                 \   \/
                 /\   \
                /  \  /\
               /\  /\/  \
          ____/  \/__\___\__
         |   /   / |        |
         |__/   /__|________|
           /___/        \___\
 
Each object is made of two quads. If you sort by object, it fails, if you sort by quads - it works (just) - providing the right sort criteria.

This *looks* like an unlikely situation - but it's really not.

                      / /
                   __/_/__
                  |       |
                  |       |
                 /|       |
              L / |  C    |
               / /|       |
     _________/ /_|_______|_____
    /        /_/        T       \
   /_____________________________\
    | ||          |_______|  || |
    | ||         /         \ || |
    | ||        /___________\|| |
    | ||        ||         |||| |
    | ||        ||         |||| |
Polygon 'T' is a tabletop, 'C' is the back of a chair that's sitting on the floor behind the table. 'L' is a bolt from a laser gun fired from behind, above and to the right that has just evaporated a bowl of flowers sitting on the table. (Just as well - I can't draw those in ASCII art!).

Now, if you can imagine that the chair has an alpha-texture for the cane chair back - it's a glass topped table and the laser bolt is also alpha-textured. It's impossible to render this properly without splitting up at least one polygon.

How to Sort.

Worse still, if you decide to split and sort polygons (or just to sort and hope that the pathalogical overlap case doesn't show up), what key do you sort on? The center of the polygon? The nearest vertex? The furthest?

Look what can happen when a translucent green blob alien (B) stands in front of a window (A)...Here is a plan view of the two polygons and our eye:


                      \ A
                       \
                        \
     eye                 \
                         \\
                        B \
                           \
 
(Using a different slope makes this clearer - but I'm stuck with '\' characters!)

In this example, the center of polygon 'A' is closer to the eye than the center of polygon B - but it is BEHIND B! How about sorting by the nearest vertex? Nope - A is still in front. How about by the furthest? Nope - A still comes out "in front". (this example is more convincing with better artwork!) You have to look at the 'span' of A against the 'span' of B...which does bad things to some sort algorithms when you give them the table/chair/laser example. Some sort algorithms never terminate when given that input because L>T, T>C but C>L !!!

Conclusions.

The upshot of this is simply that you WILL get errors for any acceptable realtime algorithm. It's just a matter of what you are prepared to tolerate. In my "Tux - A Quest for Herring" game, I render all alpha polygons after all solid ones, I'm careful to set glAlphaFunc sensibly - but I don't sort my alpha polygons. Nobody ever complained about sorting problems with alpha polygons...but if you dive into the lake and look up through the surface of the water, you'll notice that there are no trees up there!