Blending operations
There are many different ways to blend colors. If you have used an image editor or drawing software before, you will have seen a large number of blending modes for your layers.
We will adopt the model used by OpenGL here and implement the commonly used mode for transparent objects. There are many different modes and you can find them in the full code for this section.
The bascic idea about transparency is, that we will use the fourth channel after RGB in the colors to signify opaqueness, usually called the alpha channel. If , the object is fully opaque and if , it is fully transparent.
The OpenGL model seems a bit complicated, but it boils down to the following:
The color already in the buffer is the destination color . The color that is written by the fragment shader is the source color . Each color is weighted by some factor/weight . Then both colors are combined using an operation . The final color that is written into the buffer is then:
OpenGL (and we) will use a slight modification, in that we allow the weights to work per component. So as before, we use the componentwise multiplication .
When all components in the weight vectors and are equal, this corresponds to the scalar version above.
To get our fully opaque rendering, we can use the following settings:
To get transparency we can think about what should happen depending on the alpha value. For now, we just think about drawing a new surface on top of existing ones. Transparency means, that we can see through this new object, so our weights should only be based on the alpha value of this new object, .
If the object is fully opaque, we only want to see its color. If it is fully transparent, we only want to see the destination. If it has a certain percentage of transparency, we expect the color to be an mix between both colors corresponding to that percentage. This sounds a lot like a linear interpolation between the two colors based on the !
We will add a new settings object to our pipeline.
function create_blend_options({
enabled = false,
source_function = BlendFunction.ONE,
destination_function = BlendFunction.ZERO,
blend_equation = BlendEquation.ADD,
constant_color = vec4(1.0, 1.0, 1.0, 1.0)
} = {}) {
return {
enabled,
source_function,
destination_function,
blend_equation,
constant_color
};
}
class Pipeline {
constructor({
...
blend_options = create_blend_options(),
}) {
...
this.blend_options = blend_options;
}
}
Like with the depth test, we add a variable enabled to disable blending if we don't need it. As we are mostly drawing opaque objects, we don't need the extra processing per pixel generally.
The constant_color field corresponds to some of the blending modes defined for OpenGL, but we don't use it in the example code.
We will now implement a subset of the blending and see how it looks by just overlaying some rectangles on an image. The solution is below
Exercise:
-
Implement the
get_blend_factorfunction in blending.js- The result is a vector
- The four different factors are given in the constants of
BlendFunction
-
Implement the
blend_colorsfunction in blending.js- Get the factors and with the
get_blend_factorfunction - Use the approriate blend function , depending on
blend_equation(in the example, this can only be addition) - Compute
- Get the factors and with the
Solution: