Alpha Blending: To Pre or Not To Pre
Alpha Blending is a small--but important--part of virtually every 3D application. Conceptually, alpha-blending is used to communicate the transparency of a surface. Generally, consumer applications (games) tend to use RGB to communicate the color of the underlying surface, relying on the alpha channel to indicate the "opaquness" of that color. More specifically, when alpha blending is enabled in the pipeline, developers tend to use this form for their blending:
DestinationColor.rgb = (SourceColor.rgb * SourceColor.a) + (DestinationColor.rgb * (1 - SourceColor.a));
In old, fixed functionality, this would be known as "SourceAlpha, InvSourceAlpha"; also known as "post-multipled alpha." However, this form of alpha blending suffers from a critical flaw: it results in the wrong color in many cases! The simplest of these cases can be illustrated with a simple, two pixel image:
Consider the above image, whose resolution is 2x1 pixels. The artist wanted to communicate that there was a red, opaque pixel adjacent to a green pixel that would just give the slightest tinge of green to objects behind it. However, something interesting happens when we generate the next mipmap level, the 1x1 level. The result is probably surprising; the resulting mipmapped texel is this:
As we approach this mipmap level, we will get a very different result than when working with the 2x1 level--entirely because we decided to use postmultiplied level. You can see this in the following images:
Enter Pre-multiplied alpha
With pre-multiplied alpha, we multiply the texture components by the alpha component first, before storage. We also modify the blend function, changing SourceColor.a to One:
DestinationColor.rgb = (SourceColor.rgb * One) + (DestinationColor.rgb * (1 - SourceColor.a));
Using pre-multiplied alpha, our original texture would look like this:
And the 1x1 mipmap level of this texture would look like this:
This is much more reasonable. We've still lost some information (and note that if the green component were small enough, or if our precision is too low the green will drop out entirely), but we've preserved the intent of the higher resolution mipmap. For comparison, here are the images again, with the addition of our pre-multiplied blender:
Transitioning from our post-multiplied world
Conveniently, the conversion from a post-multiplied alpha pipeline to one utilizing pre-multiplied alpha is trivial. At texture save time, or asset baking time, or even load time, multiply each non-alpha channel by alpha. That is:
OutputTexture.rgb = InputTexture.rgb * InputTexture.a; OutputTexture.a = InputTexture.a;
And don't forget to modify your "alpha blending enable" to use One for the Source Alpha value. If we plug in pre-multiplied alpha to the original blending equation, it's easy to see that switching to pre-multiplied alpha gives us the exact same result:
DestinationColor.rgb = ((SourceColor.rgb * SourceColor.a) * One) + (DestinationColor.rgb * (1 - SourceColor.a));
So you might be asking: If the result is the same, why bother with pre-multiplied alpha? The reason is texture filtering. When you take samples from a texture, unless you have disabled texture filtering, the hardware is blending neighboring texels together and returning a weighted average as a result. With traditional post-multiplied alpha, this result will be incorrect. If your browser supports WebGL, you can play with pre- and post- multiplicative blending below:
Enable Blending
Enable Filtering
Pre-multiplied Alpha
Post-multiplied Alpha
2x1 texture
1x1 texture
NOTES:
For more information about pre-multiplied alpha, check out Shawn Hargreaves' MSDN blog
If you have questions or would like to discuss this blog post, be sure to visit this thread in our popular DevTalk forums.