This is something I stumbled upon that’s worth sharing. If you’re not a programmer, especially not a Windows programmer, it probably won’t matter to you. (I promise the next post will be on a completely different subject.)
I’m adding some graphical capabilities to the game engine I work on. The engine uses GDI and DirectX, depending on the user’s settings. (It would use SDL, now that it uses the more permissive zlib license rather than that GPL garbage, but apparently in SDL you can’t do simple things like rotate sprites.) So far I’ve managed to bend GDI to my will and allow affine transforms via PlgBlt(), so I can rotate and scale sprites. I figured out how to alpha-blend something at half opacity (not sure if DirectX will be as helpful on that one). But another requested feature was additive blending, e.g. for an explosion or something, which I wasn’t sure was possible.
Quick digression: Alpha-blending at partial opacity is actually really easy. The documentation for AlphaBlend() is completely misleading, in that it says the constant alpha takes the place of per-pixel alpha. I’ve found this not to be true. Set the constant alpha in BLENDFUNCTION to your desired opacity, multiplied by 255 of course.
Now as for additive blending, this is so simple I can’t believe I didn’t think of it sooner. AlphaBlend() only supports the one blend mode, AC_SRC_OVER, which at first glance seems pretty limited. But then I remembered, AlphaBlend() requires you to pre-multiply the RGB components of each pixel in the source image (in this case a sprite) that you’ll be blending. That is, if a pixel is half opacity its RGB components should also be halved, in advance. The reason for this is that it then has to do a lot less work when blending the sprite.
In other words, the blend works like this:
Dst.red = (1 – Src.alpha * ConstAlpha) * Dst.red + Src.red * ConstAlpha
That’s with the color components mapped to a 0-1 range rather than 0-255, obviously. I wish the documentation on AlphaBlend() made this clear, because it would have saved me a lot of grief. Microsoft’s actual documentation on this is frustratingly bad, and misleading. As I mentioned already, they’re downright vague on how ConstAlpha works and imply that if you set it to anything other than 255, the alpha values of your source will be ignored; that simply isn’t true. This one simple formula is all you need.
Now in additive blending, we want:
Dst.red = Dst.red + Src.red * ConstAlpha
That’s when it hit me: If alpha is set to 0 throughout the source image, these two formulas become identical, so this should produce an additive blend. I tried that, and it did.
I never thought of this before because at alpha=0, you expect the source color not to have an impact at all. If it weren’t for the pre-multiplying of colors, that would be true. But because pre-multiplication is required, AlphaBlend() basically expects that your “invisible” pixels have red=0, green=0, and blue=0 already, and its only purpose in using alpha is to tell how much it should darken the destination pixel.
Other blending modes are of course a whole other animal. Multiplicative blends aren’t possible with AlphaBlend(), though DirectX has more options for that sort of thing. I should also note that using this form of additive blending for wide-area lighting effects is likely impractical, though not appreciably more so than using “straight” alpha blending to darken things.