GLSL Programming/GLUT/Transparent Textures

Map of the Earth with transparent water, i.e. the alpha component is 0 for water and 1 for land.

This tutorial covers various common uses of alpha texture maps, i.e. RGBA texture images with an A (alpha) component that specifies the opacity of texels.

It combines the shader code of the tutorial on textured spheres with concepts that were introduced in the tutorial on cutaways and the tutorial on transparency.

If you haven't read these tutorials, this would be a very good opportunity to read them.

Discarding Transparent FragmentsEdit

Let's start with discarding fragments as explained in the tutorial on cutaways. Follow the steps described in the tutorial on textured spheres and assign the image to the left to the material of a sphere with the following fragment shader (keep the same vertex shader):

varying vec4 texCoords;
uniform sampler2D mytexture;
float cutoff = 0.1;
 
void main(void) {
    vec2 longitudeLatitude = vec2((atan(texCoords.y, texCoords.x) / 3.1415926 + 1.0) * 0.5,
                                  (asin(texCoords.z) / 3.1415926 + 0.5));
 
    gl_FragColor = texture2D(mytexture, longitudeLatitude);
 
    if (gl_FragColor.a < cutoff)
        // alpha value less than user-specified threshold?
    {
        discard; // yes: discard this fragment
    }
}

(You could pass the cutoff variable as a uniform.)

If you start the application now, the fragment shader should read the RGBA texture and compare the alpha value against the threshold specified in the variable cutoff. If the alpha value is less than the threshold, the fragment is discarded and the surface appears transparent.

Since we can look through the transparent parts, it makes sense to not to enable backface culling as described in the tutorial on cutaways.

Alpha TestingEdit

OpenGL (non-ES) has a fixed-function feature, similar to the stencil buffer, to accept or discard a fragment based on its alpha value, through glAlphaFunc.

    glAlphaFunc(GL_GREATER, 0.1);
    glEnable(GL_ALPHA_TEST);

BlendingEdit

The tutorial on transparency described how to render semitransparent objects with alpha blending.

Let's remind that proper transparency support requires sorting the triangles by distance to the camera, as we have to disable depth test to write "behind" a transparent triangle. To avoid sorting triangles for a single spherical object, we use culling to draw the back faces first, and then the front faces.

So take an RGBA texture, the textured spheres shaders, and this OpenGL configuration:

glDisable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
glCullFace(GL_FRONT);
glutSolidSphere(1.0,30,30);
 
glCullFace(GL_BACK);
glutSolidSphere(1.0,30,30);

It should be mentioned that this particular texture image contains only alpha values of either 0 or 1. Thus, there are relatively few fragments that receive an alpha value in between 0 and 1 due to interpolation of the alpha values of neighboring texels. It is only for those fragments that the order of rendering is important. If one accepts potential rendering artifacts for these fragments, one might improve the performance of the shader by enabling the depth test:

glEnable(GL_DEPTH_TEST);

Note that all texels with an alpha value of 0 are black in this particular texture image. In fact, the colors in this texture image are “premultiplied” with their alpha value. (Such colors are also called “opacity-weighted.”) Thus, for this particular image, we should actually specify the blend equation for premultiplied colors in order to avoid another multiplication of the colors with their alpha value in the blend equation. Therefore, an improvement of the shader (for this particular texture image) is to employ the following blend specification:

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
Semitransparent globes are often used for logos and trailers.

Blending with Customized ColorsEdit

We should not end this tutorial without a somewhat more practical application of the presented techniques. To the left is an image of a globe with semitransparent blue oceans, which I found on Wikimedia Commons. There is some lighting (or silhouette enhancement) going on, which I didn't try to reproduce. Instead, I only tried to reproduce the basic idea of semitransparent oceans with the following shader, which ignores the RGB colors of the texture map and replaces them by specific colors based on the alpha value:

varying vec4 texCoords;
uniform sampler2D mytexture;
 
void main(void) {
    vec2 longitudeLatitude = vec2((atan(texCoords.y, texCoords.x) / 3.1415926 + 1.0) * 0.5,
                                  (asin(texCoords.z) / 3.1415926 + 0.5));
 
    gl_FragColor = texture2D(mytexture, longitudeLatitude);
 
    if (gl_FragColor.a > 0.5) // opaque 
    {
        gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); // opaque green
    }
    else // transparent 
    {
        gl_FragColor = vec4(0.0, 0.0, 0.5, 0.7); // semitransparent dark blue
    }
}

Of course, it would be interesting to add lighting and silhouette enhancement to this shader. One could also change the opaque, green color in order to take the texture color into account, e.g. with:

gl_FragColor = vec4(0.5 * gl_FragColor.r, 2.0 * gl_FragColor.g, 0.5 * gl_FragColor.b, 1.0);

which emphasizes the green component by multiplying it with 2 and dims the red and blue components by multiplying them with 0.5. However, this results in oversaturated green that is clamped to the maximum intensity. This can be avoided by halvening the difference of the green component to the maximum intensity 1. This difference is 1.0 - gl_FragColor.g; half of it is 0.5 * (1.0 - gl_FragColor.g) and the value corresponding to this reduced distance to the maximum intensity is: 1.0 - 0.5 * (1.0 - gl_FragColor.g). Thus, in order to avoid oversaturation of green, we could use (instead of the opaque green color):

gl_FragColor = vec4(0.5 * gl_FragColor.r, 1.0 - 0.5 * (1.0 - gl_FragColor.g), 0.5 * gl_FragColor.b, 1.0);

In practice, one has to try various possibilities for such color transformations. To this end, the use of numeric shader properties (e.g. for the factors 0.5 in the line above) is particularly useful to interactively explore the possibilities.

SummaryEdit

Congratulations! You have reached the end of this rather long tutorial. We have looked at:

  • How discarding fragments can be combined with alpha texture maps.
  • How the alpha test can be used to achieve the same effect.
  • How alpha texture maps can be used for blending.
  • How alpha texture maps can be used to determine colors.

Further ReadingEdit

If you still want to know more


< GLSL Programming/GLUT

Unless stated otherwise, all example source code on this page is granted to the public domain.
Back to OpenGL Programming - Lighting section Back to GLSL Programming - GLUT section
Last modified on 20 May 2012, at 22:35