Note on Alpha Blending
2024-04-20 #computer graphics, #alpha blending, 646 Words 2 Mins

I have worked in the field of computer graphics for many years, but I must admit, I found alpha blending and concepts like premultiplied alpha, back-to-front blending, and front-to-back blending confusing. While it's easy to find online resources that provide the formulas to use in different situations, truly understanding the underlying principles is crucial for handling more complex cases.

I want to express my gratitude to the book Advanced Graphics Programming Using OpenGL, which has a dedicated chapter that patiently explains how alpha blending works (starting from page 185).

My earlier confusion stemmed from conceptualizing alpha as opacity, similar to how we perceive transparency in real life. For example, imagining a semi-transparent red film, where red is the color and alpha represents the semi-transparency. This misconception made it difficult for me to grasp concepts like premultiplied alpha. In real life, an object's color is generally unrelated to its transparency – a very transparent red film should still appear red. However, in computer graphics, we have the concept of premultiplied alpha, where we multiply the alpha value with the color, causing a very transparent red to appear dark red or even black.

The correct way to conceptualize alpha is as the "amount of color" rather than opacity. In computer graphics, there is no true transparency; it's more akin to painting, where all pigments are fully opaque, differing only in color.

To create the illusion of semi-transparency, we must mix colors by taking a weighted average of existing colors, simulating the effect of light passing through multiple layers of color. When mixing colors, the only controllable factor is the amount of color, which corresponds to the alpha channel of an RGBA pixel.

Thus, the most basic form of alpha blending two colors is:

\begin{aligned} C_{new} &= C_{src} * A_{src} + C_{dst} * (1 - A_{src}) \\ A_{new} &= 1.0 \end{aligned}

This is a typical weighted average of two colors, assuming that both colors and the resulting color C_{new} are fully opaque. Here, only C_{src} has an alpha value and is used in the formula, while C_{dst} and C_{new} have an alpha of 1.0 (full amount). If C_{src} is the foreground, we determine how much of the foreground we want to see using this formula, without any actual transparency involved.

We can view each pixel as a small color bucket, where the amount of color poured into a single bucket cannot exceed 1.0 (overflow).

In the scenario of mixing two colors, we can think of all pixel buckets initially containing the background color C_{dst} at the full amount of 1.0. To make room for adding the foreground color of the amount A_{src}, we need to pour out some existing color. To ensure the resulting bucket is still full, the amount to pour out should be 1.0 - A_{src}.

If we want to blend more than two colors additively, we need to adjust the formula because we cannot assume that the resulting color of each step has no alpha (always a full bucket).

As we pour colors additively into this bucket, we need to leave some room for incoming colors. This is why we need to keep track of the amount of color already present in the bucket.

Let's label the colors we want to blend as C_1, C_2, C_3, and so on. The formula becomes:

\begin{aligned} C_{new} &= C_1 * A_1 + C_2 * A_2 * (1.0 - A_1) \\ A_{new} &= A_1 + A_2 * (1.0 - A_1) \end{aligned}

The weighted average of the two colors is still the same, but we now multiply C_2 by its A_2 because C_2 is not a full bucket.

The new alpha formula, A_{new} = A_1 + A_2 * (1.0 - A_1), guarantees that the new alpha never exceeds 1.0, preventing overflow.

Now, looking at the common equations found online for front-to-back alpha blending, they may not seem to match the above equation at first glance. However, in reality, they are equivalent. First, since it's front-to-back alpha blending, the destination color is the foreground, and the source is the background. Second, the following formula uses A_dst to store the (1.0 - A_1) term from the above equation. If we substitute C_{src} and A_{src} with C_2 and A_2, and C_{dst} and A_{dst} with C_1 and (1.0 - A_1), we should arrive at the same equation:

\begin{aligned} C_{dst} &= A_{dst} * (A_{src} * C_{src}) + C_{dst} \\ A_{dst} &= (1 - A_{src}) * A_{dst} \end{aligned}

Leave a Comment on Github