Drawing alpha-tested foliage nicely (WARNING: image heavy)

Started by
37 comments, last by DonnieDarko 16 years, 8 months ago
Hey there. I came across this little trick a couple of weeks back which has been a god-send for me when rendering alpha-tested foliage. Basically it allows alpha-tested foliage to look as good as alpha-blended foliage without the sorting artifacts. I still see a lot of commercial and not-so-commercial games using straight ugly alpha-tested foliage that I figure I'm not the only one this could help. Anyway, ideally we want to draw our trees using alpha-blending so we get nice smooth edges. However, we need to draw the surfaces from farthest to closest or we get artifacts: So usually we use alpha-testing as we can't really afford the time to sort all those thousands of surfaces we're using to draw our trees with. Here's the same scene using alpha-testing with a cut-off of 0.5: As you can see the problem with alpha-tested foliage is the hard edges. The foliage tends to shimmer when moving as well. But at least it's simple and fast. This is what most games seem to use. We can combine both alpha-blending and alpha-testing in the same pass but then we get fringes which can be unsightly: Now here's the scene rendered using the trick I came across: As you can see, we get all the benefits of alpha-blended foliage with the simplicity of alpha-tested foliage, and sure looks nicer than either. Here's the trick (using OpenGL lingo). First render all your foliage using standard alpha-testing, but with a fairly high alpha cut-off:
    glBlendFunc( GL_ONE, GL_ZERO );
    glEnable( GL_ALPHA_TEST );
    glAlphaFunc( GL_GREATER, 0.75 );
    ...draw foliage...
Now redraw your foliage using alpha-blending but with a low alpha-test cut-off, depth-writes turned off, and depth testing set to less-than:
      glAlphaFunc( GL_GREATER, 8.0 / 255.0 );
      glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
      glDepthMask( GL_FALSE );
      glDepthFunc( GL_LESS );
      ...draw foliage again...
      glDisable( GL_ALPHA_TEST );
      glDepthMask( GL_TRUE );
      glDepthFunc( GL_LEQUAL );
As you can see there is the cost of drawing all your foliage twice, but I think the image quality makes it worth it. You don't need to disable depth writing after each tree - draw all your organic foliage using the alpha-testing pass, then do it all again with the alpha-blending pass. Here's another comparison shot using a pine-tree (alpha-blended, alpha-tested, both): Static screenshots don't really do it justice. It looks nicer in motion. Anyway, hope that helps someone as it sure helped me. Apologies if this is a known trick. If that's the case, I figure there are some who don't know it.
Advertisement
Nice pictures! Here's a link to a similar technique. Unfortunately the extra pass is always the first to go when I get around to optimizing because it uses up so much fill rate.
Does anyone know if this method can be combined with the ones above? It's for billboards but might apply here.
0xa0000000
I saw a cool trick in GPU Gems 2 that dealt with the same problem, but I was just skimming in a bookstore and I don't remember what it was. Anyone have the book handy? It was right near the beginning -- I believe it was the grass chapter.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
The most robust solution I've seen is to use alpha-to-coverage which is a simple flag enable. It isn't perfect (it's effectively sub-pixel "screen-door" anti-aliasing) and needs to be used with MSAA, but it gives you N levels of free sorting per-pixel when N is the number of MSAA samples.

I've seen the technique in the OP before actually and it's usable, however it has several artifacts that you can actually see in the post. In particular note the left side of the top middle branch in the left-most image on the bottom row of images. (... Wow! Hopefully you can decode that.)

Still it may be preferable to typical alpha testing which looks even worse in many situations.

[Edit] The trick they use in Gems 2 is also a "screen-door" effect, but at the pixel level rather than sub-pixel so it won't look as good as alpha-to-coverage, and is more expensive to boot.
I think the artifact you refer to are actually present on other images and using other technique, they seem to be texture wrapping artifact.

That is if you talk about the pixels in the air?
Nice. In your second pass, would it not make a little more sense to render with alpha LEQUAL 0.75?
BTW, I think Tom Forsyth described this technique about 5 years ago on the DX or GA mailing list. As with anything computer graphics related, its pretty hard to invent something that hasn't been done before :-(
So it was a known technique. Wish I'd found it before now. I definitely get the impression it's not a widely known trick because there are a lot of games that have ugly aliased alpha-tested foliage. But then, as jamesw says, that could simply be a case of not wanting to have to draw it all twice.
Quote:I've seen the technique in the OP before actually and it's usable, however it has several artifacts that you can actually see in the post. In particular note the left side of the top middle branch in the left-most image on the bottom row of images. (... Wow! Hopefully you can decode that.)

As Kern_d mentioned, it looks like a wrapping artifact. The models I used aren't the best.
Quote:Nice. In your second pass, would it not make a little more sense to render with alpha LEQUAL 0.75?

Give it a go. I haven't analysed the technique much, just enough to verify it works. I didn't invent the trick, just encountered it being used by an old game and thought I'd share it as it helped me a lot.
Quote:The most robust solution I've seen is to use alpha-to-coverage which is a simple flag enable.

I did play with alpha-to-coverage a while ago but wasn't very impressed. I can't recall if I used antialiasing with it or not. For me personally I found the trick I described here produced nicer results. I might go revisit a2c with antialiasing now though and see if it makes a difference.
alpha-to-coverage doesnt even work at all without anti-aliasing--it is only activated if AA is enabled.
Quote:Original post by PlayerX
As Kern_d mentioned, it looks like a wrapping artifact. The models I used aren't the best.

Fair enough, although I'm pretty sure artifacts could occur depending on the "threshold" values that you choose. At the very least banding near those thresholds. Now of course whether a degenerate case can be constructed (I'm almost positive that it can) and whether it comes up in practice are two different things :)

Quote:Original post by PlayerX
I did play with alpha-to-coverage a while ago but wasn't very impressed. I can't recall if I used antialiasing with it or not. For me personally I found the trick I described here produced nicer results. I might go revisit a2c with antialiasing now though and see if it makes a difference.

Yeah A2C will do absolutely nothing without MSAA enabled since it relies on writing the alpha value to the MSAA sample "coverage", which in turn uses the MSAA hardware to store and sort the N samples, averaging them at the end (if you use a standard resolve... in D3D10 you could actually do whatever you want with the samples after the fact).

This topic is closed to new replies.

Advertisement