Premultiplied alpha

Premultiplied alpha

  • Comments 14

Remember when you first figured out Santa Claus wasn't real? The growing doubt, tempered by the fact that all your friends believed in him, and surely they can't ALL be wrong, then the gradual realization that everybody was in fact wrong...

Well, I've got another one for you: the way most people do alpha blending is bogus!

At a fundamental level, alpha has no special meaning at all. Graphics cards manipulate numeric values, but what they do with these values is entirely determined by what shader code you write and which render states you set. The hardware works with Vector4 data types. Because these often represent colors, it is natural to use the first three components for red, green, and blue. That leaves a spare component, called alpha, which can be used for absolutely anything we like.

Some people use alpha to store shininess, or ambient occlusion, or a collision material ID. In the MotoGP particle system I used it to pass a per-particle random number into my vertex shader physics. But most often, alpha is used to represent transparency. It is important to understand that this is just a convention, and there are several different ways it can be done.

 

Conventional Alpha Blending

The majority of programmers, programs, programming APIs, file formats, etc, define transparency as:

  • RGB specifies the color of the object
  • Alpha specifies how solid it is

In math:

blend(source, dest)  =  (source.rgb * source.a) + (dest.rgb * (1 - source.a))

In code:

    RenderState.SourceBlend = Blend.SourceAlpha; 
    RenderState.DestinationBlend = Blend.InverseSourceAlpha; 

In this world, RGB and alpha are independent. You can change one without affecting the other. Even when an object is fully transparent it still has the same RGB as if it was opaque. Thus 100% transparency can be represented by many different color values.

There isn't really a direct physical analogy for this. I guess it's similar to how a magic cloak of invisibility works in a fantasy universe, where I can be wearing the same red sweater as I am right now, and it remains red even though it currently happens to be invisible.

 

Premultiplied Alpha Blending

Here's a different way to think about transparency:

  • RGB specifies how much color the object contributes to the scene
  • Alpha specifies how much it obscures whatever is behind it

In math:

blend(source, dest)  =  source.rgb + (dest.rgb * (1 - source.a))

In code:

    RenderState.SourceBlend = Blend.One; 
    RenderState.DestinationBlend = Blend.InverseSourceAlpha; 

In this world, RGB and alpha are linked. To make an object transparent you must reduce both its RGB (to contribute less color) and also its alpha (to obscure less of whatever is behind it). Fully transparent objects no longer have any RGB color, so there is only one value that represents 100% transparency (RGB and alpha all zero).

This is more like how light behaves in the real world. What is the RGB of my car windscreen? None: it is transparent, so has no color. How about my sunglasses? These have a fractional alpha value (letting some light some through, while blocking some) and also contribute some RGB for that nice rose-tinted glow.

To use premultiplied alpha, in addition to setting the appropriate renderstates, you must also convert your source graphics into premultiplied format. Drawing a non premultiplied color with premultiplied blending will not give sensible results!

To convert a non premultiplied color into premultiplied format:

color.rgb *= color.a

(hence why this is called "premultiplied" format)

Look at the blend equations for conventional vs. premultiplied alpha. If you substitute this color format conversion into the premultiplied blend function, you get the conventional blend function, so either way produces the same end result. The difference is that premultiplied alpha applies the (source.rgb * source.a) computation as a preprocess rather than inside the blending hardware.

I will write more about how to convert graphics into premultiplied format in a later post.

 

Why premultiplied r0x0rz

Premultiplied alpha is better than conventional blending for several reasons:

  • It works properly when filtering alpha cutouts (see below)

  • It works properly when doing image composition (stay tuned for my next post)

  • It is a superset of both conventional and additive blending. If you set alpha to zero while RGB is non zero, you get an additive blend. This can be handy for particle systems that want to smoothly transition from additive glowing sparks to dark pieces of soot as the particles age.

  • It plays nice with DXT compression, which only supports transparent pixels with an RGB of zero.

 

Premultiplied alpha cutouts

Remember how filtering can produce ugly fringes around the edges of alpha cutouts? Not a problem when using premultiplied alpha! Revisiting the example from my previous post:

tree  =  (0, 255, 0, 255)

border  =  (0, 0, 0, 0)

background  =  (0, 0, 255)

filtered  =  (tree + border) / 2  =  (0, 128, 0, 128)

With conventional alpha blending, the result is darker than we wanted:

result  =  lerp(background, filtered.rgb, filtered.a)  =  (0, 64, 128)

But premultiplied blending produces the right answer:

result  =  filtered.rgb + (background * filtered.a)  =  (0, 128, 128)

This works because texture filtering and premultiplied blending are both linear transforms, and are therefore associative:

blend(filter(a, b),  c)  ==  filter(blend(a, c),  blend(b, c))

  • Any chance we can get some images for comparison? All these verbose descriptions and maths are a bit dry to try to read without any eye candy!

  • can't wait for the next post already, i guess that this will help solving alpha blend issue on deferred rendering.

  • great, great info "The Shawn".  me and my graphic guy are hoping that your next post helps solve our deferred rendering alpha problems.

    You got us on the edge of our seats!  =D

  • You didn't mention one thing - using premultiplied alpha increases DXT5 compression artifacts (DXT5 texture alpha channel is compressed with greater fidelity than the RGB channels). So it's not so great for textures with smooth alpha gradients. You may find some description and comparison pictures here: http://kriscg.blogspot.com/2009/11/premultiplied-alpha.html.

  • "Remember when you first figured out Santa Claus wasn't real?"

    Whaaat? No I'm not listening, this cant be true!!!

  • Well I hope your happy, you ruined Christmas for Timmy

  • Hi Shawn, thanks a lot for your massive help

    Here comes a question about this you wrote:"It is a superset of both conventional and additive blending. If you set alpha to zero while RGB is non zero, you get an additive blend"

    My understanding is that you can change normal alpha blend to additive in the same spriteBatch, just by playing with alpha? Am I right?

    And if yes, I don't quite get how to do it. I tried change, color alpha tint in my spriteBatch, or change the premultAlpha content processes. ALl I get is a non visible picture (indeed the alpha is set to 0)

    How do you make that?

  • Hi Shawn

    I am really struggling with managing both normal and additive blending with premultiplied alpha.

    Could you give us an example?

  • hey shawn,

    Thanks for such good information.

    I read whole article But i have little bit different thing that I have difault background(white) so blending for small red circle(actually image size 32*32 so corner require blending to hide) texture is as you told

                   D3DDevice.SetTexture(0, CurrentTexture);

                   D3DDevice.SetRenderState(RenderState.AlphaBlendEnable, true);

                   D3DDevice.SetRenderState(RenderState.SourceBlend, Blend.SourceAlpha);

                   D3DDevice.SetRenderState(RenderState.DestinationBlend, Blend.InverseSourceAlpha);

    now when i load background image then my red circle get stop blendign and showing me rectangle with white corners.

    What would you suggest to eliminate white corners or do blending with bkgroung image too if that exist?

  • Hi Shawn. Thanks for your post. It has got me thinking...

    If we go back to your post (Texture filtering: alpha cutouts) where the problem is that we have a green pixel and a transparent pixel and want the result where we are 50% between them, then for me the correct answer is (0, 255, 0, 128). The colour shouldn't change. We just introduce some transparency. The result of the LERP that the filtering hardware does is to produce a pre-multiplied alpha result (0, 128, 0, 128) is the pre-multiplied equivalent of (0, 255, 0, 128) that I would expect between fully opaque green and fully transparent.

    I've played around with the numbers on paper and it just so happens that if input colours are in pre-multiplied format then the result of the LERP texture filter will also be correct, as we expect, and pre-multiplied. Then with this result we take one of the source and add this to (1 - src alpha) dest colour, where the dest colour is also in pre-mult alpha format.

    So what we get is this:

    background  =  (0, 0, 255, 255)

    tree        =  (0, 255, 0,   255)

    border      =  (0, 0, 0,   0)

    filtered  =  (tree + border) / 2  =  (0, 128, 0, 128) (Same as before although we acknowledge that this is a pre-multiplied alpha colour)

    result  =  filtered + ( ( 1 - filtered alpha ) * background )

           = (0, 128, 0, 128) + (0, 0, 128, 128)

    = (0, 128, 128, 255)

    Basically the texture filter only really works for LERPs between pre-multiplied values, unless of course you want to do something funcky, like blend between 100% transparent red to 100% opaque blue and have it go a bit purple in the middle or something, in which case non-pre-multiplied is the way to go. But as far as I know we are not utilising this kind of effect with any of our graphics in game. But who knows tomorrow! :)

  • > Basically the texture filter only really works for LERPs between pre-multiplied values

    Exactly right.  Which is why premultiplied is a better choice than interpolative blending!

    Ok, being pedantic, the filtering hardware can work for other color formats than premultiplied. Strictly speaking, it just requires that the blend function be a linear operation (which premultiplied blending is, but interpolative blending is not). There are various other blend functions, such as multiplicative values used in lightmapping, that are also linear and so can be filtered without problems.

  • Having thought a little more about this the reason that lerping used in standard texture filtering is because each of the two colour components are weighted by (in terms of) their respective alpha values. Basically without multiplying through by the alpha we're lerping apples and pears when we lerp RGB. It's not until the weightings are applied (multiplying through by their respective alpha values) that both colours are in the same space and so a LERP or other blending operation makes sense. At least that's how I see it right now! :)

  • reminds me of Tom Forsyth's post from 2006. a lot.

    home.comcast.net/.../blog.wiki.html

  • I used separate alpha calculation instead of using shaders.For example:

    RenderState.SourceBlend = Blend.SourceAlpha;

    RenderState.DestinationBlend = Blend.InverseSourceAlpha;

    RenderState.SourceBlendAlpha = Blend.One;

    RenderState.DestinationBlendAlpha = Blend.InverseSourceAlpha;

    It works well.But when I try to handle following states:

    RenderState.SourceBlend = Blend.SourceAlpha;

    RenderState.DestinationBlend = Blend.One;

    RenderState.SourceBlendAlpha = Blend.One;

    RenderState.DestinationBlendAlpha = Blend.One;

    The result is totally wrong,can somebody tell me the reason?I think it should be right in math.

Page 1 of 1 (14 items)
Leave a Comment
  • Please add 1 and 3 and type the answer here:
  • Post