Cheap tricks for OpenGL transparency

Alec Jacobson

September 05, 2012

weblog/

I've been experimenting with some hacks using basic OpenGL commands to achieve high/good-quality transparency (alpha-blending). I'll compare 4 methods.
  1. Traditional: For this one (and all the others) we turn on alpha blending:
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    and then just use a normal depth test without back face culling in a single pass:
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_LEQUAL);
    // render teapot with alpha = alpha
    

    single pass teapot

    single pass teapot Notice two things. The handle is not visible in the second picture and we have zebra stripe artifacts in the first picture due to alpha blending without sorting. Sorting is slow and view-dependent and won't even guarantee correct results. So instead we'll see how far we can get with cheap tricks.
  2. GL_GREATER: This is a 3-pass attempt.
    double f = 0.75; // Or some other factor
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_LEQUAL);
    // render teapot with alpha = f*alpha
    
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_GREATER);
    // render teapot with alpha = alpha
    
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_LEQUAL);
    // render teapot with alpha = (alpha-f*alpha)/(1.0-f*alpha);
    
    This often produces reasonable results but the idea is a bit strange. It's sort like depth peeling from opposite ends. Hoping that if we only have two layers, we'll catch both of them.

    three pass teapot greater

    three pass teapot greater Notice now we have the handle showing up in the second image, but we still have ordering artifacts. Admittedly a lot of these would go away with back face culling, but then we don't get two faced rending (blue inside, gold outside).
  3. ALWAYS: This method will seem like a step back because it introduces more ordering artifacts due to the two-face rendering, but the concept is a bit simpler.
    double f = 0.75; // Or some other factor
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_ALWAYS);
    // render teapot with alpha = f*alpha
    
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_LEQUAL);
    // render teapot with alpha = (alpha-f*alpha)/(1.0-f*alpha);
    
  4. The idea is again the same that by taking two passes we first pick up a little bit of everything albeit not sorted in the correct order. Then we finish with one pass that ensures that the top layer is correct.

    three pass teapot greater

    three pass teapot greater We're getting artifacts from the ordering in the first image, but I'll show in a second that these are just coming from not handling the two-face rendering correctly. What we see in both cases is that the bottom of the teapot shows clearly through the bottom.
  5. QUINTUPLE PASS: This is the final solution that I arrived at. It involves five passes, which using display lists should be OK for many real-time applications.
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_LESS);
    // render teapot with alpha = 0, to prime the depth buffer
    
    glEnable(GL_CULL_FACE);
    glCullFace(GL_FRONT);
    glDepthFunc(GL_ALWAYS);
    // render teapot with alpha = f*alpha
    
    glEnable(GL_CULL_FACE);
    glCullFace(GL_FRONT);
    glDepthFunc(GL_LEQUAL);
    // render teapot with alpha = (alpha-f*alpha)/(1.0-f*alpha)
    
    
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK;
    glDepthFunc(GL_ALWAYS);
    // render teapot with alpha = f*alpha
    
    // There's a trade off here. With culling enabled then a perfectly
    // opaque object (alpha=1) may be wrong. With it disabled, ordering
    // artifacts may appear
    // glEnable(GL_CULL_FACE);
    // glCullFace(GL_BACK);
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_LEQUAL);
    // render teapot with alpha = (alpha-f*alpha)/(1.0-f*alpha)
    
    The idea is that first we prepare the depth buffer with the final depth values, then we use the previous method but just to show the inside "back-facing" surface, then we do the same to show the front.

    three pass teapot greater

    three pass teapot greater
  6. Now we get two very nice renderings that even participate with the background well. We can continuously blend our alpha value to 1 and expect to see a perfectly correct opaque rendering. We can notice some small ordering artifacts in the top image near the handle. If we know we want a transparent object then we can flip back face culling on for the last pass to get rid of this. But as noticed this will give an incorrect result when alpha ≈ 1.