Alpha blending

From DmWiki

Alpha blending is a technique for creating translucent objects.

Textures can contain a fourth color channel in addition to the RGB color channels. This fourth channel is called alpha and contains the opacity of the texture at that point - a value of 0.0 represents complete transparency, while 1.0 represents complete opacity.

When alpha blending is enabled, textures are translucent, allowing whatever is behind them in the frame buffer to show through. The degree of transparency is controlled by the alpha value of the texture.

Table of contents

Math

There are three kinds of texture you can use with alpha blending when done "manually". It just so happens that a normal ARGB texture is the worst format for efficiency in this case. The three formats are:

  • Normal
  • Static
  • Precomputed

Normal is a regular ARGB texture as you would find in any 32bit color image.

Static is used for the background only. Usually as a final frame buffer. The alpha channel of the background is always at its maximum level since it'll never be alpha blended on another image.

Precomputed is where the alpha percentage is applied to each of the RGB channels before-hand and the alpha value is inverted (1.0 - alpha). The reason for this will be apparent in the following sections.

Normal Onto Normal

We will talk about the most general form of alpha blending. It is also the most complicated. This is where you alpha blend a source image onto another image. The resulting image can again be used in another alpha blending operation. So the computations get a little difficult.

We will define the source image as S[A], S[R], S[G], S[B]. Each of these can have values from 0.0 to 1.0. Later, we will convert them to 0-255 byte values and show some optimizations. The destination is likewise labelled D[A], D[R], D[G], D[B]. The result of the source applied to the destination is called R[A], R[R], R[G], R[B]. The result and the destination will always be the same format.

Here are the equations.

// Get final alpha channel
FA = S[A] + D[A] - S[A]*D[A]

// Get percentage of source alpha compared to final alpha
if (FA==0) SA=0
else SA = S[A]/FA

// Destination percentage is just the additive inverse.
DA = 1.0-SA

R[A] = FA
R[R] = S[R]*SA + D[R]*DA
R[G] = S[G]*SA + D[G]*DA
R[B] = S[B]*SA + D[B]*DA


Byte equations:

// Get final alpha channel.
FA = S[A] + ((255-S[A])*D[A])/255

// Get percentage (out of 255) of source alpha compared to final alpha
if (FA==0) SA = 0
else SA = S[A]*255/FA

// Destination percentage is just the additive inverse.
DA = 255-SA

R[A] = FA
R[B] = (S[B] * SA + D[B] * DA)/255
R[G] = (S[G] * SA + D[G] * DA)/255
R[R] = (S[R] * SA + D[R] * DA)/255

The division by FA can't be removed. You could find a multiplicative inverse and use some fixed point arithmetic. But no matter what, you need a division. There's no escaping it.

See next section (Normal Onto Static) on different methods to remove the division by 255. Here's an inexact alternative that should be just as good.

// Get final alpha channel.
FA = S[A]+1 + ((256-S[A])*D[A])>>8

// Get percentage (out of 256) of source alpha compared to final alpha
if (FA==0) SA = 0
else SA = (S[A]<<8)/FA

// Destination percentage is just the additive inverse.
DA = 256-SA

R[A] = FA
R[B] = (S[B] * SA + D[B] * DA)>>8
R[G] = (S[G] * SA + D[G] * DA)>>8
R[R] = (S[R] * SA + D[R] * DA)>>8

Normal Onto Static

We start off with our normal equations from the last section. But this time, D[A] is always 1.0 (and so is R[A] by definition). This gives:

IA = 1.0-S[A]
R[A] = 1.0
R[R] = S[A]*S[R] + IA*D[R]
R[G] = S[A]*S[G] + IA*D[G]
R[B] = S[A]*S[B] + IA*D[B]

This is a lot simpler. No divisions and two multiplications per channel.

For byte values, we get:

IA = 255 - S[A]
R[A] = 255
R[R] = (S[A]*S[R] + IA*D[R])/255
R[G] = (S[A]*S[G] + IA*D[G])/255
R[B] = (S[A]*S[B] + IA*D[B])/255

We get divisions again. The reason is that our values are X/255 * Y/255 = XY/65025. We need our values in the range of 0-255, not 0-65025. So we divide by 255 to bring the range back down. It may look a little backwards (dividing instead of multiplying) but that's because we're dealing with integers and not fractional values. If we were using floating point values, we would indeed multiply to get the range of 0-255.

Dividing by 255 is not attractive. It's very slow. We could divide by 256 instead (shift right by 8). But this means that no resulting image would ever be 100% opaque anymore. You could also use the following equation:

t = (alpha * pixel) + 128; ((t>>8)+t)>>8

This will divide properly by 255 with proper rounded results.

IA = 255 - S[A]
T[R] = (S[A]*S[R] + IA*D[R]) + 128
T[G] = (S[A]*S[G] + IA*D[G]) + 128
T[B] = (S[A]*S[B] + IA*D[B]) + 128
R[A] = 255
R[R] = ((T[R]>>8)+T[R])>>8
R[G] = ((T[G]>>8)+T[G])>>8
R[B] = ((T[B]>>8)+T[B])>>8

If you're not too concerned about exact results, you could use something in between. If you add one to either one of the factors being multiplied, you can then shift right by 8 and you'll get something that is just as good visually.

Here is one example where the alpha channel is increased by 1. We could also have left the alpha values the same and increased by 1 the RGB of both the source and destination instead. However, the following equations should be good enough for most uses.

SA = S[A] + 1
IA = 256 - SA
R[A] = 255
R[R] = (SA*S[R] + IA*D[R])>>8
R[G] = (SA*S[G] + IA*D[G])>>8
R[B] = (SA*S[B] + IA*D[B])>>8

Precomputed Onto Static

This should be the most used operation. It is the most efficient and should be enough for most tasks.

Now we'll look again at the equations from the previous section.

IA = 1.0 - S[A]
R[A] = 1.0
R[R] = S[A]*S[R] + IA*D[R]
R[G] = S[A]*S[G] + IA*D[G]
R[B] = S[A]*S[B] + IA*D[B]

Note how the source alpha is multiplied by each of the source RGB values. We can precompute this and store it in our source image and use that instead. So to precompute an image (from Normal to Precomputed), we use the following equations:

S[R] = S[R]*S[A]
S[G] = S[G]*S[A]
S[B] = S[B]*S[A]
S[A] = 1.0 - S[A]

Make sure the alpha is calculated last. The equations all use the orginal alpha value.

So our alpha blending equations now become:

R[A] = 1.0
R[R] = S[R] + S[A]*D[R]
R[G] = S[G] + S[A]*D[G]
R[B] = S[B] + S[A]*D[B]

This eliminates three multiplications and is much simpler. Here's the same thing using byte values.

Precomputing image:

S[R] = (S[R]*S[A])/255
S[G] = (S[G]*S[A])/255
S[B] = (S[B]*S[A])/255
S[A] = 255 - S[A]

Optimized inexact version:

SA = S[A] + 1
S[R] = (S[R]*SA)>>8
S[G] = (S[G]*SA)>>8
S[B] = (S[B]*SA)>>8
S[A] = 255 - S[A]


Alpha blending:

R[A] = 255
R[R] = S[R] + (S[A]*D[R])/255
R[G] = S[G] + (S[A]*D[G])/255
R[B] = S[B] + (S[A]*D[B])/255

Optimized inexact alpha blending:

SA = S[A]+1
R[A] = 255
R[R] = S[R] + (SA*D[R])>>8
R[G] = S[G] + (SA*D[G])>>8
R[B] = S[B] + (SA*D[B])>>8

Precomputed Onto Precomputed

This should be the second most used equations.

We'll just show the equations.

R[A] = S[A]*D[A]
R[R] = S[R] + S[A]*D[R]
R[G] = S[G] + S[A]*D[G]
R[B] = S[B] + S[A]*D[B]

Byte equations:

R[A] = (S[A]*D[A])/255
R[R] = S[R] + (S[A]*D[R])/255
R[G] = S[G] + (S[A]*D[G])/255
R[B] = S[B] + (S[A]*D[B])/255


Normal Onto Precomputed

IA = 1-S[A]
R[A] = IA*D[A]
R[R] = S[A]*S[R] + IA*D[R]
R[G] = S[A]*S[G] + IA*D[G]
R[B] = S[A]*S[B] + IA*D[B]

Byte equations:

IA = 255-S[A]
R[A] = (IA*D[A])/255
R[R] = (S[A]*S[R] + IA*D[R])/255
R[G] = (S[A]*S[G] + IA*D[G])/255
R[B] = (S[A]*S[B] + IA*D[B])/255

Precomputed Onto Normal

There's no easy way to do this. Convert the source back to a normal image and use the equations for Normal Onto Normal. You could figure out the math on your own and get some equations that converts directly, but they are not much better.

To convert back an image from precomputed to normal:

if S[A]==1.0 then 
  S[A] = 0
  S[R] = 0
  S[G] = 0
  S[B] = 0
else
  IA = 1.0 - S[A]
  S[B] = S[B]/IA
  S[G] = S[G]/IA
  S[R] = S[R]/IA
end if


Byte equations:

if S[A]==255 then 
  S[A] = 0
  S[R] = 0
  S[G] = 0
  S[B] = 0
else
  IA = 255 - S[A]
  S[B] = S[B]*255/IA
  S[G] = S[G]*255/IA
  S[R] = S[R]*255/IA
end if

See also:


DevMaster navigation