Transparency

introduction

It is possible to have colours with semi-transparency within OpenGL. When specifying a colour you need to introduce one more component to the colour to indicate the level of transparency. A value of 1.0 is opaque and a value of 0 is completely transparant. In the following statement a transparency of 0.5 is set for the colour white.

       GL11.glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
        

The transparcy component is called alpha. The colour components are specified in the order Red, Green, Blue, Alpha.

Transparency permits the colours behind the object you are drawing to appear blended with the colours of your object. By default no transparency, or blending, is enabled for OpenGL. So even if you specify an alpha component within a colour it will be ignored. You need to enable blending in OpenGL for transparency to work by writing.

        GL11.glEnable(GL11.GL_BLEND);
        

It is always best to have blending disabled when you do not need it. To do this

        GL11.glDisable(GL11.GL_BLEND);
        

blending function

There is a great deal of fexibility in the way that colours are blended when using transparency. The colours are blended pixel by pixel and so is a time consuming operation.

When working with blending there are always two colours to be blended.

  1. destination : the colour already in the color buffer.
  2. source : the colour of the object you are drawing

The blending function can be generally expressed by

final buffer colour = source colour * source blending factor + destination colour * destination blending factor
          

To specify the blending factors you need to execute the following method with appropriate parameters.

      GL11.glBlendFunc(sourceFactor,destintationFactor)
        

The following table lists the available parameters for this function. The second column specifies wheather the parameter is applicable as the source factor, the destination factor or either. The last column are the factors as applied to the R G B A components of the source or destinations. For example the destination components are (dR, dG, dB, dA) and the source componet are (sR, sG, sB, sA).


parameterSuitable forComputed Factor
GL_ZEROsource or destination(0, 0, 0, 0)
GL_ONEsource or destination(1, 1, 1, 1)
GL_DST_COLORsource(dR, dG, dB, dA)
GL_SRC_COLORdestination(sR, sG, sB, sA)
GL_ONE_MINUS_DST_COLORsource(1-dR, 1-dG, 1-dB, 1-dA)
GL_ONE_MINUS_SRC_COLORdestination(1-sR, 1-sG, 1-sB, 1-sA)
GL_SRC_ALPHAsource or destination(sA, sA, sA, sA)
GL_ONE_MINUS_SRC_ALPHAsource or destination(1- sA, 1- sA, 1- sA, 1- sA)
GL_DST_ALPHAsource or destination(dA, dA, dA, dA)
GL_ONE_MINUS_DST_ALPHAsource or destination(1- dA, 1- dA, 1- dA, 1- dA)
GL_SRC_ALPHA_SATURATEsource(f, f, f, 1) where f = min(sA, 1 - dA)

For example if we included in our code the following

            GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
        

Then the colour componets written to the buffer are listed here as red, green , blue, alpha.


    red     =   sR * sA  +  dR * (1 - sA)
    green =   sG * sA  +  dG * (1 - sA)
    blue   =   sB * sA  +  dB * (1 - sA)
    alpha =   sA * sA  +  dA * (1 - sA)
           

The above is a very common blend function for a semi-transparent object.

Exercise: Above is an interactive animation where you can choose the source and destination parameters for the blend function. Try a number of combination and try and understand the result you see. Also Try the following.

sourcedestination
GL_ZEROGL_ONE
GL_ZEROGL_ZERO



working with many transparent objects

When working with many objects in a 3D scene it is usual to have depth testing enabled. Each object as it is drawn is tested against the value in the depth buffer. If it is closer than the value in the depth buffer then it contributes its depth to the depth buffer. This is fine for opaque objects but can cause problems when dealing with many semi transparent objects.

Image the situation in the last exercise. The more distant object we still wish to appear through the foreground object. To alleviate this problem we need the transparent object to be tested against the depth buffer but not to contribute to the depth buffer if it is closer than the value in the buffer. This way objects more distant than the depth buffer will still be drawn. To achieve this we use the following method.

        GL11.glDepthMask(false);
    

Setting the depth mask to false makes the depth buffer read only.

This of cause has the problem that the opaque objects cannot contribute to the depth buffer. The expensive solution is listed below

  1. Draw the entire scene with depth testing on for all the opaque fragments.
  2. Draw the entire scene again with the depth buffer reead only.

We need to draw the scene twice! This is an expensive option to achieve realistic scenes with transparent objects.

In the first step we need to draw the entire scene for all the opaque fragments with depthMask true. To achieve this we must enable alpha testing with the following method

      GL11.glEnable(GL11.GL_ALPHA_TEST);
    
To specify how the alpha component of the fragment is tested use the following method.

    GL11.glAlphaFunc(int function, float reference);
    

This function will enable drawing of fragments with an alpha value that satisfies a comparison with a reference value by an equation specified by function. For example to draw only opaque fragments you could use the following.

        GL11.glAlphaFunc(GL11.GL_EQUAL, 1.0f);
    

This would only draw fragments where the alpha value of the fragment is equal to 1.

Then we need to set the DepthMask to false and redraw the whole scene with the alpha function

        GL11.glAlphaFunc(GL11.GL_LESS, 1.0f);
    

The choice of comparisons functions for AlphaFunc are as following.


parametermeaning
GL_NEVERnever accept the fragment
GL_ALWAYSalways accept the fragment
GL_LESSAccept fragment if fragment alpha < reference alpha
GL_LEQUALAccept fragment if fragment alpha <= reference alpha
GL_EQUALAccept fragment if fragment alpha equal to reference alpha
GL_GEQUALAccept fragment if fragment alpha >= reference alpha
GL_GREATERAccept fragment if fragment alpha > reference alpha
GL_NOTEQUALAccept fragment if fragment alpha not equal to reference alpha

The code flow may look something like this


         public void init()
         {
            Display.create();

            GL11.glEnable(GL11.GL_DEPTH_TEST);
            GL11.glEnable(GL11.GL_ALPHA_TEST);
            GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

            // setup the projection and modelview

         }
         
         public void loop()
            {
            while()
                {
                  GL11.glDepthMask(true); // need to ensure we can write to depth buffer to clear it
                  GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_COLOR_BUFFER_BIT);

                  // first render
                   GL11.glDisable(GL11.GL_BLEND);
                   GL11.glAlphaFunc(GL11.GL_EQUAL, 1.0f);

                   render();

                   // second render
                   GL11.glEnable(GL11.GL_BLEND);
                   GL11.glDepthMask(false);
                   GL11.glAlphaFunc(GL11.GL_LESS, 1.0f);
                   
                   render();

                  Display.update();
                }
            }

         public void render()
         {
         // render all the graphics
         }