OpenGL Programming/Transparency

Normal.
Transparency.
Transparency, view from top.

Introduction

edit

Opaque objects are easy to draw in computer graphics. If we look at a specific pixel on the screen, there can be zero, one or more objects in the scene that overlap with that pixel. Thanks to the z-buffer, you don't have to worry about which object is the one on top. You just start drawing the first object at that pixel, remember its depth, and when you get to the next object, you check the second objects depth against the one from the previous object, and if it is less, you overwrite the pixel with the color of the second object, otherwise you just do nothing.

With semi-transparent objects, things are more difficult. The GPU cannot just choose to overwrite the color of a pixel or keep it as it is based on the depth value. Instead, the GPU needs to mix the color of the transparent object with the color of that which is behind it. But it cannot know the color of the object that is behind it if it hasn't drawn that object yet. The z-buffer is useless here; the order in which you draw objects is suddenly very important.

There are various solutions to this problem. The obvious solution is to sort all objects from furthest to nearest (or is it?). But sorting can be very costly, especially since it has to be redone every time the scene changes, including every time the camera moves. Luckily, there are some techniques which avoid having to do that, but they all have some limitations.

We can use the accumulation buffer to simulate transparency. Imagine for example a scene with opaque objects, and with a 50% transparent pane of colored glass. The trick we will use is that we render this scene twice: once where we draw all objects, including the glass, as if they all were completely opaque. The second time we render only the opaque objects, and skip rendering the transparent objects. Using the accumulation buffer, we calculate the average of the two frames. The result will be that where the glass pane is, 50% of the light of the objects behind it shines through.

By averaging more than two frames in the accumulation buffer, or by multiplying each frame by a different value, you can easily vary the amount of transparency. However, the limitation of this technique is that it doesn't work correctly when you have two or more transparent objects directly behind each other. In real life, two 50% transparent glass panes would only result in transmitting 25% of the light from the objects behind them. However, with the accumulation buffer trick, you would still see 50% from the objects behind them.

Order-independent transparency using the accumulation buffer

edit

The usual way to make objects transparent is to change the alpha channel in its texture. So, starting with a very simple fragment shader that justs looks up the texture, we introduce a configurable cutoff value for the alpha channel. If the alpha value is lower than the cutoff value, we just don't draw that fragment, otherwise we draw it as if it was opaque:

varying vec2 texcoord;
uniform sampler2D texture;
uniform float alpha_cutoff;

void main(void) {        
  vec4 color = texture2D(texture, texcoord);

  // Don't draw pixels with an alpha value lower than the cutoff
  if(color.a < alpha_cutoff)
    discard;

  gl_FragColor = color;
}

Then, we draw the scene twice. Once with a cutoff value of 1/3. Objects with a transparency of 50% will be drawn in this first pass. The second time we use a cutoff value of 2/3. Objects with a transparency of 50% will not be drawn in this pass. Then we get the average of the two passes from the accumulation buffer and send it to the screen.

glUniform1f(uniform_alpha_cutoff, 1.0 / 3.0);
draw_scene();
glAccum(GL_LOAD, 0.5);

glUniform1f(uniform_alpha_cutoff, 2.0 / 3.0);
draw_scene();
glAccum(GL_ACCUM, 0.5);

glAccum(GL_RETURN, 1);
glSwapBuffers();

Exercises

edit
  • Modify the code above so it still only draws the scene twice, but so that it works for objects that are 80% transparent.
  • Modify the code above so that it draws the scene more than twice, such that it works for all objects that are 25%, 50% or 75% transparent.
  • Try implementing this technique in any of the previous tutorials.
  • Can you combine this technique efficiently with anti-aliasing, motion blur and/or depth-of-field?
  • Sorting objects seems like the perfect approach. However, objects can have strange shapes, and there may not be a distinct order between two objects. Think for example of two interlocking rings. But what about sorting all the individual triangles instead?