12.5 - Color Blending¶
The previous lesson explained how to implement transparency by blending the color that is already in the “rendering target’s” color buffer with a new color value. This lesson explains all of the color blending options available in the graphics pipeline.
Blending (All the details)¶
When color blending is enabled with this command,
gl.enable(gl.BLEND)
the zbuffer rendering algorithm looks like this,
1 2 3 4 5 6 | void renderPixel(x, y, z, color) {
if (z < z_buffer[x][y]) {
z_buffer[x][y] = z;
color_buffer[x][y] = (color * percent1) + (color_buffer[x][y] * percent2);
}
}
|
where the values percent1
and percent2
are determined by a call to:
gl.blendFunc(where_to_get_percent1, where_to_get_percent2);
// or
gl.blendFunc(gl.SOURCE_FACTOR_ENUM, gl.DESTINATION_FACTOR_ENUM);
Remember that WebGL uses the term source
(or src
) to refer
to a surface that is being rendered, while it uses the term destination
(or dst
) to refer to the color buffer.
The percent1
and percent2
values are not scalars; they are 4-component
vectors. When the percentage of a color is taken, each component of the color is
potentially scaled differently. If the blending equation above is explicitly written
out, it looks like the following equation because both the multiplication and the addition
are component-wise vector operations. (The “dot” notation is swizzle notation.)
color_buffer[x][y] = vec4(color.r * percent1.r + color_buffer[x][y].r * percent2.r,
color.g * percent1.g + color_buffer[x][y].g * percent2.g,
color.b * percent1.b + color_buffer[x][y].b * percent2.b,
color.a * percent1.a + color_buffer[x][y].a * percent2.a);
The following table lists the options for the src
and dst
percentages.
Note that the percentages are not actual values. Rather, they are indicators of
where the percentage values are retrieved from (or how they are calculated). The
values in the table are specified using this notation:
color_buffer[x][y] --> (dst_red, dst_green, dst_blue, dst_alpha)
color --> (src_red, src_green, src_blue, src_alpha)
WebGL ENUM constant | (red, green, blue) % values | Alpha % |
---|---|---|
gl.ZERO | (0.0, 0.0, 0.0) | 0.0 |
gl.ONE | (1.0, 1.0, 1.0) | 1.0 |
gl.SRC_COLOR | (src_red, src_green, src_blue) | src_alpha |
gl.ONE_MINUS_SRC_COLOR | (1 - src_red, 1 - src_green, 1 - src_blue) | 1 - src_alpha |
gl.DST_COLOR | (dst_red, dst_green, dst_blue) | dst_alpha |
gl.ONE_MINUS_DST_COLOR | (1 - dst_red, 1- dst_green, 1- dst_blue) | 1 - dst_alpha |
gl.SRC_ALPHA | (src_alpha, src_alpha, src_alpha) | src_alpha |
gl.ONE_MINUS_SRC_ALPHA | (1 - src_alpha, 1- src_alpha, 1 - src_alpha) | 1 - src_alpha |
gl.DST_ALPHA | (dst_alpha, dst_alpha, dst_alpha) | dst_alpha |
gl.ONE_MINUS_DST_ALPHA | (1 - dst_alpha, 1 - dst_alpha, 1 - dst_alpha) | 1 - dst_alpha |
gl.CONSTANT_COLOR | (constant_red, constant_green, constant_blue) | constant_alpha |
gl.ONE_MINUS_CONSTANT_COLOR | (1 - constant_red, 1 - constant_green, 1 - constant_blue) | 1 - constant_alpha |
gl.CONSTANT_ALPHA | (constant_alpha, constant_alpha, constant_alpha) | constant_alpha |
gl.ONE_MINUS_CONSTANT_ALPHA | (1 - constant_alpha, 1 - constant_alpha, 1- constant_alpha) | 1 - constant_alpha |
gl.SRC_ALPHA_SATURATE | a = min(src_alpha, 1 - dst_alpha); (a,a,a) | 1.0 |
In the above table, for the percentages that use a constant color, constant_red
,
constant_green
, constant_blue
, and constant_alpha
, these values
are set using the gl.blendColor
function:
gl.blendColor(red, green, blue, alpha);
To complicate things further, the addition of the colors can be changed
to subtraction using the gl.blendEquation
function. The three options are:
gl.blendEquation(gl.FUNC_ADD);
gl.blendEquation(gl.FUNC_SUBTRACT);
gl.blendEquation(gl.FUNC_REVERSE_SUBTRACT);
which makes the pipeline’s calculation be one of:
color_buffer[x][y] = (color * percent1) + (color_buffer[x][y] * percent2);
color_buffer[x][y] = (color * percent1) - (color_buffer[x][y] * percent2);
color_buffer[x][y] = (color_buffer[x][y] * percent2) - (color * percent1);
The following pseudocode attempts to clarify color blending by showing how it might look in code format. (Color blending is implemented inside the graphics pipeline and can’t be modified.)
//-------------------------------------------------------------------------
vec3 getColorFactor(mode, src_color, dst_color, constant_color) {
switch (mode) {
case gl.ZERO: factor = (0.0, 0.0, 0.0);
case gl.ONE: factor = (1.0, 1.0, 1.0);
case gl.SRC_COLOR: factor = ( src_color.r, src_color.g, src_color.b);
case gl.ONE_MINUS_SRC_COLOR: factor = (1.0-src_color.r, 1.0-src_color.g, 1.0-src_color.b);
case gl.DST_COLOR: factor = ( dst_color.r, dst_color.g, dst_color.b);
case gl.ONE_MINUS_DST_COLOR: factor = (1.0-dst_color.r, 1.0-dst_color.g, 1.0-dst_color.b);
case gl.SRC_ALPHA: factor = ( src_color.a, src_color.a, src_color.a);
case gl.ONE_MINUS_SRC_ALPHA: factor = (1.0-src_color.a, 1.0-src_color.a, 1.0-src_color.a);
case gl.DST_ALPHA: factor = ( dst_color.a, dst_color.a, dst_color.a);
case gl.ONE_MINUS_DST_ALPHA: factor = (1.0-dst_color.a, 1.0-dst_color.a, 1.0-dst_color.a);
case gl.CONSTANT_COLOR: factor = (constant_red, constant_green, constant_blue);
case gl.ONE_MINUS_CONSTANT_COLOR: factor = (1.0-constant_red,
1.0-constant_green,
1.0-constant_blue);
case gl.CONSTANT_ALPHA: factor = (constant_alpha,
constant_alpha,
constant_alpha);
case gl.ONE_MINUS_CONSTANT_ALPHA: factor = (1.0-constant_alpha,
1.0-constant_alpha,
1.0-constant_alpha);
case gl.SRC_ALPHA_SATURATE: a = min(src_color.a, 1.0-dst_color.a);
factor = (a,a,a);
}
return factor;
}
//-------------------------------------------------------------------------
float getAlphaFactor(mode, src_color, dst_color, constant_color) {
switch (mode) {
case gl.ZERO: alpha_factor = 0.0;
case gl.ONE alpha_factor = 1.0;
case gl.SRC_COLOR alpha_factor = src_color.a;
case gl.ONE_MINUS_SRC_COLOR alpha_factor = 1.0-src_color.a;
case gl.DST_COLOR alpha_factor = dst_color.a;
case gl.ONE_MINUS_DST_COLOR alpha_factor = 1.0-dst_color.a;
case gl.SRC_ALPHA alpha_factor = src_color.a;
case gl.ONE_MINUS_SRC_ALPHA alpha_factor = 1.0-src_color.a;
case gl.DST_ALPHA alpha_factor = dst_color.a;
case gl.ONE_MINUS_DST_ALPHA alpha_factor = 1.0-dst_color.a;
case gl.CONSTANT_COLOR: alpha_factor = constant_alpha;
case gl.ONE_MINUS_CONSTANT_COLOR: alpha_factor = 1.0-constant_alpha;
case gl.CONSTANT_ALPHA: alpha_factor = constant_alpha;
case gl.ONE_MINUS_CONSTANT_ALPHA: alpha_factor = 1.0-constant_alpha;
case gl.SRC_ALPHA_SATURATE alpha_factor = 1.0;
}
return alpha_factor;
}
//-------------------------------------------------------------------------
vec4 percent1, percent2;
void blendFunc( source_enum, destination_enum ) {
percent1.rgb = getColorFactor(src_mode, src_color, dst_color, constant_color);
percent1.a = getAlphaFactor(src_mode, src_color, dst_color, constant_color);
percent2.rgb = getColorFactor(dst_mode, src_color, dst_color, constant_color);
percent2.a = getAlphaFactor(dst_mode, src_color, dst_color, constant_color);
}
//-------------------------------------------------------------------------
void renderPixel(x, y, z, color) {
if (z < z_buffer[x][y]) {
z_buffer[x][y] = z;
if (color_blending_is_enabled) {
dst_color = color_buffer[x][y];
src_color = color;
switch (blendEquation) {
case gl.FUNC_ADD: new_color = src_color * percent1 + dst_color * percent2;
case gl.FUNC_SUBTRACT: new_color = src_color * percent1 - dst_color * percent2;
case gl.FUNC_REVERSE_SUBTRACT: new_color = dst_color * percent2 - src_color * percent1;
}
color_buffer[x][y] = new_color;
} else { // color_blending_is_disabled
color_buffer[x][y] = color;
}
}
}
Experiments¶
Please experiment with the following WebGL program by selecting various combinations of blending factors. Note that the background color of a canvas impacts blending because the “destination” color is always the background color when the first change is made to a pixel.
The program renders 125 cubes with variations of color from black to white. The eight cubes at the corners have one or more color components fully saturated and have an alpha value of 1.0. The “interior” cubes have smaller and smaller alpha values, with the cubes in the center having the least alpha values. You can randomize the position, size, and rotation of the cubes, but their colors remain fixed.
This WebGL program performs no sorting. Therefore, accurate transparency is not possible from most camera angles.
There are some suggestions below for interesting blending combinations. Most blending combinations are not useful for general rendering but might be useful for specific scene scenarios.
Experiment with color blending.
Animate
gl.blendFunc(gl.ONE, gl.ZERO); | |
Source blending factor (Percentage of the rendered surface's color) |
Destination blending factor (Percentage of color already in the color buffer) |
gl.ZERO gl.ONE gl.SRC_COLOR gl.ONE_MINUS_SRC_COLOR gl.DST_COLOR gl.ONE_MINUS_DST_COLOR gl.SRC_ALPHA gl.ONE_MINUS_SRC_ALPHA gl.DST_ALPHA gl.ONE_MINUS_DST_ALPHA gl.CONSTANT_COLOR gl.ONE_MINUS_CONSTANT_COLOR gl.CONSTANT_ALPHA gl.ONE_MINUS_CONSTANT_ALPHA gl.SRC_ALPHA_SATURATE |
gl.ZERO gl.ONE gl.SRC_COLOR gl.ONE_MINUS_SRC_COLOR gl.DST_COLOR gl.ONE_MINUS_DST_COLOR gl.SRC_ALPHA gl.ONE_MINUS_SRC_ALPHA gl.DST_ALPHA gl.ONE_MINUS_DST_ALPHA gl.CONSTANT_COLOR gl.ONE_MINUS_CONSTANT_COLOR gl.CONSTANT_ALPHA gl.ONE_MINUS_CONSTANT_ALPHA |
CONSTANT_COLOR: red: green: blue: alpha: | |
Background: red: green: blue: alpha: | |
gl.FUNC_ADD
(src_color * src_factor + dst_color * dst_factor) gl.FUNC_SUBTRACT (src_color * src_factor - dst_color * dst_factor) gl.FUNC_REVERSE_SUBTRACT (dst_color * dst_factor - src_color * src_factor) |
Please verify that you understand the following blending settings and their results:
gl.blendFunc(gl.ONE, gl.ZERO)
The destination is “zeroed out” and the color buffer is set to the color of the surface being rendered. This is the default behaviour of the zbuffer algorithm when color blending is disabled.gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
This is the standard technique for transparency. The alpha value of a surface’s color determines how much of the surface contributes to the color. (This WebGL program preforms no sorting. Therefore the transparency is correct in some areas of the scene and incorrect in other areas.)gl.blendFunc(gl.CONSTANT_COLOR, gl.CONSTANT_COLOR)
gl.blendColor(0.0, 0.0, 0.0, 1.0)
Using a white background,(1.0, 1.0, 1.0, 1.0)
, this produces a stencil that contains a black pixel for every rendered surface.gl.blendFunc(gl.ONE_MINUS_DST_COLOR, gl.DST_ALPHA)
Using a black background,(0.0, 0.0, 0.0, 1.0)
, and looking at the “back side” of the cubes, this gives an interesting “ghosting” appearance – but only from a limited set of camera angles. The order the cubes are rendered greatly impacts the output image.
Separate Alpha Percentage¶
Controlling the alpha
component of the colors is a critical part of
color blending and in some situations the alpha component needs to be modified
differently than the red, green, and blue components. This is accomplished
using these two commands:
gl.blendFuncSeparate(enum src_factor, enum dst_factor, enum src_alpha, enum dst_alpha);
gl.blendEquationSeparate(enum equation_rgb_mode, enum equation_alpha_mode);
For example, this command,
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.CONSTANT_ALPHA);
translates into this internal blending equation,
dst_color = vec4( src_color.rgb * (src_color.a, src_color.a, src_color.a),
src_color.a * 0.0 )
+ vec4( dst_color.rgb * (1-src_color.a, 1-src_color.a, 1-src_color.a),
dst_color.a * constant_alpha )
Experiments¶
The following WebGL program is rather complex because of the number of options possible. Use the COLOR and ALPHA radio buttons to set which factors are being modified. Please experiment with this program.
Again, this WebGL program performs no sorting. Therefore, accurate transparency is not possible from most camera angles.
Experiment with color blending.
Animate
Set COLOR percentages Set ALPHA percentages |
|
gl.blendFuncSeparate( gl.ONE, gl.ZERO, gl.ONE, gl.ZERO) |
|
Source COLOR blending factors | Destination COLOR blending factor |
gl.ZERO gl.ONE gl.SRC_COLOR gl.ONE_MINUS_SRC_COLOR gl.DST_COLOR gl.ONE_MINUS_DST_COLOR gl.SRC_ALPHA gl.ONE_MINUS_SRC_ALPHA gl.DST_ALPHA gl.ONE_MINUS_DST_ALPHA gl.CONSTANT_COLOR gl.ONE_MINUS_CONSTANT_COLOR gl.CONSTANT_ALPHA gl.ONE_MINUS_CONSTANT_ALPHA gl.SRC_ALPHA_SATURATE |
gl.ZERO gl.ONE gl.SRC_COLOR gl.ONE_MINUS_SRC_COLOR gl.DST_COLOR gl.ONE_MINUS_DST_COLOR gl.SRC_ALPHA gl.ONE_MINUS_SRC_ALPHA gl.DST_ALPHA gl.ONE_MINUS_DST_ALPHA gl.CONSTANT_COLOR gl.ONE_MINUS_CONSTANT_COLOR gl.CONSTANT_ALPHA gl.ONE_MINUS_CONSTANT_ALPHA |
CONSTANT_COLOR: red: green: blue: alpha: | |
Background: red: green: blue: alpha: | |
gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD) gl.FUNC_ADD (src_color * src_factor + dst_color * dst_factor) gl.FUNC_SUBTRACT (src_color * src_factor - dst_color * dst_factor) gl.FUNC_REVERSE_SUBTRACT (dst_color * dst_factor - src_color * src_factor) |
Color Blending Observations¶
Some observations concerning the color blending equations:
- The color blending equation is two multiplications followed by an addition (or subtraction). The multiplications are always times percentages between 0.0 and 1.0. Therefore, the multiplications can only decrease a color value; they can never increase a color value.
- The color values can be forced to black by using zero for the percentages.
- The color values can never be forced to white. (Note that the CONSTANT_COLOR values are restricted to the range 0.0 to 1.0.)
- Since two color values are added together and each value can’t be greater
than 1.0, the maximum value for their summation is 2.0. However, the sum is
always clamped to the range
[0.0,1.0]
. The summation always increase a color value (unless they are both zero) because both operands are positive in the range[0.0,1.0]
. (The subtraction mode can decrease a color value and even make it negative. All negative values are clamped to 0.0.) - There is no blending option that will set a color component value to a specific value. If a specific color is needed, set the fragments’ color in a fragment shader; no blending is needed.
- If the final value of a pixel has an alpha value that is less than 1.0, it is blended with the color of the web page behind the canvas. Therefore, not only does the background color of the canvas affect blending, the color of the web page does as well.
Summary¶
Color blending can implement transparency when combined with the sorting of graphic primitives and the rendering of surfaces from back to front.
Color blending can produce other interesting rendering effects but the background color and the surface colors may need to be very specific to achieve a desired result.
Glossary¶
- source color
- A color value to be rendered for a surface.
- destination color
- A color value stored in a color buffer.
- color blending
- The color of a pixel is calculated as a combination of two colors: a “destination” color and a “source” color.
Self Assessment¶
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
- Correct. The source color’s amount is controlled by the source alpha’s value, and the remaining percentage is used for the color already in the color buffer.
gl.blendFunc(gl.ONE_MINUS_SRC_ALPHA, gl.SRC_ALPHA)
- Incorrect. The blending factors are reversed.
gl.blendFunc(gl.SRC_ALPHA, gl.DST_ALPHA)
- Incorrect.
gl.blendFunc(gl.SRC_COLOR, gl.DST_COLOR)
- Incorrect. This would add the colors, not blend them.
Q-417: Which of the following settings for blending produces transparency?
gl.blendFunc(gl.ONE, gl.ZERO)
- Correct. All of the source’s color is used, while none of the destination’s color is used.
gl.blendFunc(gl.ZERO, gl.ONE)
- Incorrect. This would leave the background color unchanged because the source color would never be used.
gl.blendFunc(gl.SRC_ALPHA, gl.DST_ALPHA)
- Incorrect.
gl.blendFunc(gl.SRC_COLOR, gl.DST_COLOR)
- Incorrect.
Q-418: Which of the following settings for blending produces the same results as having blending disabled?
- 2.0
- Correct. The addition of 1.0 and 1.0.
- 1.0
- Incorrect. Although the final result of a color component is always clamped to the range 0.0 to 1.0.
- 3.0
- Incorrect.
- 2.5
- Incorrect.
Q-419: What is the maximum color component value that can be calculated as a result of blending?
- -1.0
- Correct. The subtraction of 0.0 minus 1.0.
- 0.0
- Incorrect. Although the final result of a color component is always clamped to the range 0.0 to 1.0.
- -2.0
- Incorrect.
- -0.5
- Incorrect.
Q-420: What is the minimum color component value that can be calculated as a result of blending?