1
\$\begingroup\$

I've seen videos comparing the two but it looks like there's no difference and they don't explain what's the difference

\$\endgroup\$
5
\$\begingroup\$

Standard Alpha blending uses this formula:

result = foreground.alpha * foreground + (1 - foreground.alpha) * background

This is a linear interpolation between the foreground and background colours, using the foreground's alpha as the interpolation weight, effectively the same as:

result = lerp(background, foreground, foreground.alpha)

This means the colour of a texel is treated entirely independently from its transparency, which is intuitively simple, but has some perplexing implications: it's possible for an object to simultaneously be both "purple" and "invisible" (ie. rgba(0.5, 0, 1, 0)), as odd as that may sound.

Pre-multiplied Alpha blending uses a different formula:

result = foreground + (1 - foreground.alpha) * background

It's so-named because the multiplication of the first term by foreground.alpha has been baked-in ahead of time.

So you can see, anything we can do with conventional alpha blending, we can do with pre-multiplied alpha blending. You can take any texture set up for "standard" alpha blending, multiply the rgb channels by the alpha channel, and now you have an equivalent texture ready for pre-multiplied blending.

But pre-multiplied blending has some extra super-powers...

Super-Power #1: Pre-Multiplied Alpha can combine layer and additive blending

This is because the pre-multiplication means we're storing data in terms of:

  • how much colour to add to the background (rgb), and
  • how much to obscure the background (alpha).

By leaving the obscuring alpha factor at zero, any values in the RGB channels get applied additively on top of the background, without being dimmed themselves - a classic "ADD" blend mode. By setting alpha to something between zero and one, we can get blend modes that are intermediate between additive and "standard/layer" blending.

This can be used, for instance, to combine multiple different sprites into a single batched draw call, even when they want different blend modes that would ordinarily force us to separate the batches.

We can even make a single effect that combines both blending modes, like a fire particle system that transitions from bright additive flames to opaque obscuring smoke with a single material/texture sheet:

Fire-smoke particle effect using pre-multiplied alpha.

Rendering this same spritesheet with standard alpha blending makes the flames disappear entirely (since they have zero alpha in the texture sheet above). Changing the texture sheet to assign an alpha to the flames makes them look too solid under alpha blending, without the characteristic brightness of an additive effect:

The same particle system rendered with standard alpha blending Modified standard-blended fire

Super-Power #2: Pre-Multiplied Alpha blending works cheaply in either direction

Let's say we wanted to pre-compose some layered sprites - like a character made of several body part and clothing layers - into a sprite sheet with a transparent background, so we can later just stamp that pre-composed character into our scene as a single unit.

Here we don't yet know the colour of the background we're drawing over, so applying the standard blending formula using the simple lerp style will actually give us the wrong result when we later layer it onto the background.

To get the same result with standard blending as if we'd layered the image from bottom up, we have to use a slightly more complicated version of the formula:

result.rgb = ( foreground.rgb * foreground.alpha
              + middle.rgb * (1 - foreground.alpha) * middle.alpha)
              / (foreground.alpha * middle.alpha * (1 - foreground.alpha));

result.alpha = foreground.alpha * middle.alpha * (1 - foreground.alpha);

Note the division there, making the blend substantially more expensive to calculate, not natively supported in the GPU's blend stage, forcing us to implement the composition in our shader. When we can assume the background is opaque, the denominator comes out to 1 and we can skip it, but we can't do that and maintain correctness when blending transparency with transparency.

With pre-multiplied alpha though, there's no division required, and we can do this pre-composition directly in the native blending pipeline:

result.rgb = foreground.rgb + middle.rgb * (1 - foreground.alpha);

result.alpha = foreground.alpha * middle.alpha * (1 - foreground.alpha);

Much simpler! And the same formula works whether we're blending onto an opaque background or a transparent one.

Super-Power #3: No discoloured fringes

With regular alpha blending can suffer from discoloured fringes at the edges between opaque and transparent regions - particularly if it's drawn with bilinear filtering, or mipmapped to lower resolutions.

This is caused by the lack of weighted blending that Roy T. explains in detail in the other answer.

Look closely at the edges of this sprite: you'll notice in all but the top-left version, the colour shifts just at the fall-off from opaque to transparent - getting a dark shadow or a light whispy halo.

Example of four different exports of the same transparent sprite, with a range of fringing artifacts under standard blending

(Gem sprite by Kenney)

The reason is that many authoring programs will output fully-transparent texels as back or white, or some other colour chosen by the compressor. When blending between two texels along this edge, the alpha results are correct, but the colour results are wrong: the adjacent texel we're blending with isn't a colour the artist painted there (they left it transparent/empty), but one the tool filled in.

This is where the "simultaneously invisible and purple" issue comes back to bite us. There are dragons lurking in those unseen regions!

We can often fix this by bleeding out the colour we want to average with into the transparent regions, which is what I did to remove the artifact in the top-left case.

Because this is a widespread problem, game engines can often fix this automatically - for example, in Unity checking the "Alpha is Transparency" box in a texture's import settings will usually eliminate fringing artifacts, doing this colour bleed or something like it under the hood. (I left the box unchecked for the example above, so Unity was forced to take the PNG's saved colour values literally)

But if you use pre-multiplied alpha, storing plain black in the transparent regions, this problem goes away. Now instead of meaning "an invisible black monster lurks here" a black transparent pixel means "absorb no light, add no light," exactly what we want from a transparent region. And it blends correctly with adjacent texels - a 50% blend of "absorb all light, add 100% green light" with transparent black yields "absorb 50% of light, add 50% green light," a correct reduction of opacity without distorting the colour.

This is of particular significance for texture formats like DXT1, which can only store 1 bit of alpha as transparent black. So you get perfectly correct blending if you interpret the texture as premultiplied alpha, but you have dark fringes with standard alpha blending that you can't fix with colour bleeding - since there's no way to store colour and transparency in the same texel in such a format.

\$\endgroup\$
3
\$\begingroup\$

Both normal (post-multiplied) alpha blending and pre-multiplied alpha blending work perfectly... That is until you start blending adjacent colors within the texture or cross-fading between textures inside your shader.

See the below images. In the first column we have a square which is filled with red and semi-transparent green. In the second column we see the result if the colors are averaged (for example, if we're using bilinear texture filtering to draw a sample halfway between these two differently-colored texels). The first row uses post-multiplied-alpha while the second row uses pre-multiplied alpha.

enter image description here

Why does this happen? In post-multiplied alpha blending, the alpha component is only taken into account after blending. This means that we computed the average of the RGB of the left and the RGB of the right values regardless of how transparent the image is. After that we take the average of the alpha channels to compute the translucency.

So a red image and a green image combined will result in a green-brown image, even if the green image was so faint as to be nearly transparent.

In pre-multiplied alpha blending, the RGB channels are always multiplied with the alpha channel. This means that the RGB channels in an almost transparent image weigh less than the RGB channels of an opaque image. This results in the correct weighted blending as seen in the second row.

Note that this blending artifact does not apply to layering a sprite over a background, pre-multiplied or no. In that case, the blending function correctly takes alpha into account either way for a correct weighted blend. It's just the naive interpolation between RGB channels in texture filtering / mipmapping that needs the extra weighted blending correction offered by pre-multiplication.

I recommend reading this article for a more in-depth answer and how to use pre-multiplied alpha (you need to make some changes to your graphics pipeline).

\$\endgroup\$
  • 2
    \$\begingroup\$ It seems worth mentioning that the blending of colors that you're talking about here is when the colors of a texture blend with each other (in mip-map generation or when sampling with bilinear/trilinear/aniso filtering turned on), and not when they blend with previously-drawn pixels in the framebuffer. \$\endgroup\$ – Victor T. Mar 17 '17 at 23:38
  • \$\begingroup\$ @VictorT. I actually didn't know that, thanks for that info :). \$\endgroup\$ – Roy T. Mar 18 '17 at 19:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.