GLSL Programming/Blender/Transparency
This tutorial covers blending of fragments (i.e. compositing them) using GLSL shaders in Blender. It assumes that you are familiar with the concept of front and back faces as discussed in the tutorial on cutaways.
More specifically, this tutorial is about rendering transparent objects, e.g. transparent glass, plastic, fabrics, etc. (More strictly speaking, these are actually semitransparent objects because they don't need to be perfectly transparent.) Transparent objects allow us to see through them; thus, their color “blends” whith the color of whatever is behind them.
Blending
editAs mentioned in the description of the “OpenGL ES 2.0 Pipeline”, the fragment shader computes an RGBA color (i.e. red, green, blue, and alpha components in gl_FragColor
) for each fragment (unless the fragment is discarded). The fragments are then processed in the “Per-Fragment Operations”. One of them is the blending stage, which combines the color of the fragment (as specified in gl_FragColor
), which is called the “source color”, with the color of the corresponding pixel that is already in the framebuffer, which is called the “destination color” (because the “destination” of the resulting blended color is the framebuffer).
Blending is a fixed-function stage, i.e. you can customize it but not program it. The way it is customized, is by specifying a blend equation. You can think of the blend equation as this definition of the resulting RGBA color:
vec4 result = SrcFactor * gl_FragColor + DstFactor * pixel_color;
where pixel_color
is the RGBA color that is currently in the framebuffer and result
is the blended result, i.e. the output of the blending stage. SrcFactor
and DstFactor
are customizable RGBA colors (of type vec4
) that are multiplied component-wise with the fragment color and the pixel color. The values of SrcFactor
and DstFactor
are specified in Blender's Python API with this function:
bge.types.KX_BlenderMaterial.setBlending(
{code for SrcFactor
},{code for DstFactor
})
The possible codes for the two factors are summarized in the following table (see also the Blender documentation):
Code | Resulting Factor (SrcFactor or DstFactor )
|
---|---|
bge.logic.BL_ONE |
vec4(1.0)
|
bge.logic.BL_ZERO |
vec4(0.0)
|
bge.logic.BL_SRC_COLOR |
gl_FragColor
|
bge.logic.BL_SRC_ALPHA |
vec4(gl_FragColor.a)
|
bge.logic.BL_DST_COLOR |
pixel_color
|
bge.logic.BL_DST_ALPHA |
vec4(pixel_color.a)
|
bge.logic.BL_ONE_MINUS_SRC_COLOR |
vec4(1.0) - gl_FragColor
|
bge.logic.BL_ONE_MINUS_SRC_ALPHA |
vec4(1.0 - gl_FragColor.a)
|
bge.logic.BL_ONE_MINUS_DST_COLOR |
vec4(1.0) - pixel_color
|
bge.logic.BL_ONE_MINUS_DST_ALPHA |
vec4(1.0 - pixel_color.a)
|
As discussed in “Vextor and Matrix Operations”, vec4(1.0)
is just a short way of writing vec4(1.0, 1.0, 1.0, 1.0)
. Also note that all components of all colors and factors in the blend equation are clamped between 0 and 1.
Alpha Blending
editOne specific example for a blend equation is called “alpha blending”. In Blender it is specified this way:
mat.setBlending(bge.logic.BL_SRC_ALPHA, bge.logic.BL_ONE_MINUS_SRC_ALPHA)
mat
is a material of type bge.types.KX_BlenderMaterial
(see the Blender documentation; for the complete code, see below). This sets SrcFactor
to vec4(gl_FragColor.a)
and DstFactor
to vec4(1.0 - gl_FragColor.a)
. Thus the blend equation becomes:
vec4 result = vec4(gl_FragColor.a) * gl_FragColor + vec4(1.0 - gl_FragColor.a) * pixel_color;
This uses the alpha component of gl_FragColor
as an opacity. I.e. the more opaque the fragment color is, the larger its opacity and therefore its alpha component, and thus the more of the fragment color is mixed in the result and the less of the pixel color in the framebuffer. A perfectly opaque fragment color (i.e. with an alpha component of 1) will completely replace the pixel color.
This blend equation is sometimes referred to as an “over” operation, i.e. “gl_FragColor
over pixel_color
”, since it corresponds to putting a semitransparent layer of the fragment color with a specific opacity on top of the pixel color. (Think of a layer of colored glass or colored semitransparent plastic on top of something of another color.)
Due to the popularity of alpha blending, the alpha component of a color is often called opacity even if alpha blending is not employed. Moreover, note that in computer graphics a common formal definition of the transparency of a color is 1 − opacity of that color.
Premultiplied Alpha Blending
editThere is an important variant of alpha blending: sometimes the fragment color has its alpha component already premultiplied to the color components. (You might think of it as a price that has VAT already included.) In this case, alpha should not be multiplied again (VAT should not be added again) and the correct blending is:
mat.setBlending(bge.logic.BL_ONE, bge.logic.BL_ONE_MINUS_SRC_ALPHA)
which corresponds to:
vec4 result = vec4(1.0) * gl_FragColor + vec4(1.0 - gl_FragColor.a) * pixel_color;
Additive Blending
editAnother example for a blending equation is:
mat.setBlending(bge.logic.BL_ONE, bge.logic.BL_ONE)
This corresponds to:
vec4 result = vec4(1.0) * gl_FragColor + vec4(1.0) * pixel_color;
which just adds the fragment color to the color in the framebuffer. Note that the alpha component is not used at all; nonetheless, this blending equation is very useful for many kinds of transparent effects; for example, it is often used for particle systems when they represent fire or something else that is transparent and emits light. Additive blending is discussed in more detail in the tutorial on order-independent transparency.
Shader Code
editHere is a simple shader which uses alpha blending with a green color with opacity 0.3. Note that you have to set the Viewport Shading to Texture in the menu of the 3D View for blending to be active.
import bge
cont = bge.logic.getCurrentController()
VertexShader = """
void main()
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
"""
FragmentShader = """
void main()
{
gl_FragColor = vec4(0.0, 1.0, 0.0, 0.3);
// fourth component (alpha) is important:
// this is semitransparent green
}
"""
mesh = cont.owner.meshes[0]
for mat in mesh.materials:
shader = mat.getShader()
if shader != None:
if not shader.isValid():
shader.setSource(VertexShader, FragmentShader, 1)
mat.setBlending(bge.logic.BL_SRC_ALPHA,
bge.logic.BL_ONE_MINUS_SRC_ALPHA)
Usually, semitransparent meshes should be rendered with deactivated writing to the depth buffer after all the opaque meshes were rendered. In this way, none of the fragments of opaque objects (which should shine through semitransparent objects) will fail the depth test because of a semitransparent object in front of it. (The depth test is discussed in the section on the “Per-Fragment Operations”.) In order to activate this technique in Blender, you have to select the object in the 3D View and then go to a Properties window and choose the Material tab. Create a new material if you haven't yet. In the Material tab check Transparency and select Z Transparency.
This setting should also render triangles sorted from back to front. This is necessary because rendering transparent meshes with deactivated writing to the depth buffer does not always solve all problems. It works perfectly if the order in which fragments are blended does not matter; for example, if the fragment color is just added to the pixel color in the framebuffer with additive blending, the order in which fragments are blended is not important; see the tutorial on order-independent transparency. However, for other blending equations, e.g. alpha blending, the result will be different depending on the order in which fragments are blended. (If you look through almost opaque green glass at almost opaque red glass you will mainly see green, while you will mainly see red if you look through almost opaque red glass at almost opaque green glass. Similarly, blending almost opaque green color over almost opaque red color will be different from blending almost opaque red color over almost opaque green color.) In order to avoid artifacts, Z Transparency renders triangles from back to front. An alternative is to use additive blending or (premultiplied) alpha blending with small opacities (in which case the destination factor DstFactor
is close to 1 and therefore alpha blending is close to additive blending).
Including Back Faces
editThe previous shader doesn't render the “inside” of the object. However, since we can see through the outside of a transparent object, we should also render the inside. As discussed in the tutorial on cutaways, the inside can be rendered by deactivating back-face culling. However, if we just deactivate culling, we might get in trouble: as discussed above, it often matters in which order transparent fragments are rendered but without any culling, overlapping triangles from the inside and the outside might be rendered in a random order which can lead to annoying rendering artifacts. Therefore, it is important to activate sorting of triangles by selecting Z Transparency as described in the previous section.
Summary
editCongratulations, you made it through this tutorial! One interesting thing about rendering transparent objects is that it isn't just about blending but also requires knowledge about back-face culling and the depth buffer. Specifically, we have looked at:
- What blending is and how it is specified in Blender.
- How a scene with transparent and opaque objects is rendered.
Further Reading
editIf you still want to know more
- the OpenGL pipeline, you should read the description of the “OpenGL ES 2.0 Pipeline”.
- about per-fragment operations in the OpenGL pipeline (e.g. blending and the depth test), you should read “Per-Fragment Operations”.
- about back-face culling, you should read the tutorial on cutaways.
- about how to specify blending in Blender, you will find the function in this Blender class and the constants on this page.