Premultiplied alpha in XNA Game Studio
It is possible to use premultiplied alpha with XNA Game Studio, but the bad news is we don't do much to help you with it.
Why not?
Yeah. Our bad. In fact one of my biggest regrets about the design of the XNA Framework is that we didn't do more to make this easier!
To use premultiplied alpha, you must do three things:
1 – Set The Blend State
Premultiplied alpha blending is configured like this:
graphicsDevice.RenderState.AlphaBlendEnable = true;
graphicsDevice.RenderState.SourceBlend = Blend.One;
graphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
Or if you are using SpriteBatch:
spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
graphicsDevice.RenderState.SourceBlend = Blend.One;
2 – Premultiply Your Colors
Premultiplied blending only works if all your color values are in premultiplied format. But most paint programs and image file formats do not use premultiplied alpha! So we must find a good place to apply this conversion.
For most games, both 2D and 3D, the flow of color data goes something like this:
- Color values are edited in a paint program, then saved into a .bmp or .png file, which is built into .xnb format by the Content Pipeline, then loaded into a Texture2D object.
- Other colors may be used to tint the texture. These can be specified as part of the vertex data, or passed as the color parameter to SpriteBatch.Draw.
- Still more colors may be computed on the fly (eg. realtime lighting).
- All these colors are passed into the shader, which could do all kinds of interesting things with them, but most often just multiplies them together.
- The shader produces a final combined color, which is passed to the alpha blending operation.
There are several options for where in this process we choose to convert from conventional to premultiplied format. Two in particular I think can be sensible depending on the situation:
| Convert colors at the end of the pixel shader |
Convert colors in a custom Content Processor |
| Add "result.rgb *= result.a" right before the end of all your shaders |
Write a PremultipliedAlphaTextureProcessor, which automatically converts all your textures to premultiplied format while they are being built |
| Requires custom pixel shaders |
Does not require custom shaders, so works with SpriteBatch, BasicEffect, etc. |
| Premultiplication happens after the shader has processed any tint colors, so tints are still specified in conventional, non-premultiplied format |
All runtime colors, including tint values, are specified in premultiplied format |
| Alpha blending is done with premultiplied colors, so image composition works properly, but texture filtering happens before the premultiply conversion, so alpha cutouts remain a problem |
All rendering uses premultiplied colors, so both image composition and alpha cutouts work nicely |
| Reasonably easy retrofit to existing code, or even just specific parts of that code, as long as you have custom shaders |
Affects everywhere you do color math, which may be a lot of places, so it can be a pain to retrofit if you don't plan this from the start |
3 – Use The Right Math
Any time you do computations on color values, you need to use the right math for the type of colors you are dealing with. Some things to bear in mind:
- Keep track of which data is in which format. Mixing premultiplied with conventional colors is a recipe for chaos!
- This distinction is only important for colors that have fractional or zero alpha. Opaque colors are the same in both formats, so nothing changes for computations that use RGB color without alpha.
- Conventional colors change opacity by leaving RGB alone while decreasing alpha. For instance to draw a half transparent orange sprite we would specify a tint of (255, 128, 0, 128). But with premultiplied colors, we must also decrease the RGB values, so that same tint would be specified as (128, 64, 0, 128).
- By far the most common color operation is tinting, which is done by multiplying two colors together (for instance this is how SpriteBatch.Draw combines its texture and color parameter). Color multiplication works the same for premultiplied and conventional colors, as long as both operands are in the same format.
- This means that SpriteBatch works as-is with premultiplied data.
- BasicEffect diffuse lighting and vertex coloring also use color multiplication, and therefore work correctly with premultiplied data.
- The BasicEffect specular lighting and fog computations will not work correctly with premultiplied data. If you want specular lighting or fog while using premultiplied alpha, you will have to write a custom shader.