OpenGL:Tutorials:Tutorial Framework:Ortho and Alpha

From GPWiki
Jump to: navigation, search

This tutorial deals with orthographic (2D) projections and OpenGL's alpha functions. The demo is a simple sprite-like effort. It shows how we can use OpenGL to display rotateable, scaleable, transparent sprites. These sprites are actually textured quads, GL's 'blitting' functions like glCopyPixels() generally have very poor performance on consumer-level hardware, using polygons is a little awkward at first, but does take advantage of hardware acceleration.


Warning

There are some elements in this webpage that are deprecated in the recent versions of OpenGL.

Some Changes

The texture loader now uses a GLuint array to hold our texture handles, this means that we can allocate and clean up the textures with just two calls:

// Handles to our textures
GLuint Texture[128];
 
// Allocate all textures in one go
glGenTextures(128,Texture);
 
// Clean up textures
glDeleteTextures(128,Texture);

The array values are now passed into the loader rather than being returned.

The other change for this demo is the screen setup. We are using an orthographic view. There is still a depth component, but there is no perspective.

We setup this view like so:

glViewport(0,0,800,600);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0,800,0,600,-100,100);
glMatrixMode(GL_MODELVIEW);

Pretty much the same as before, except for the glOrtho() call. I personally try to match the ortho screen size to the actual screen resoulution. That way we can place 1:1 textures without stretching and scaling and give GL an easier life. The Z clipping planes, here set to -100 and 100 are a little further out than normal, but the reason for that will become clear later.

NOTE. In OpenGL, the Y axis is upside down compared to other systems. 0 is at the bottom of the screen.

Alpha Blending

The reason I chose the TGA loader when I added the texture loader was due to the format's support for 32bit images. The extra 8bits in 32bit TGAs are called the alpha channel and allow us to specify 256 alpha levels to perform nice effects. OpenGL can do many funky things with alpha values, but the one I'm covering here is probably the most common; Transparency.

We set OpenGL alpha blending options using glBlendFunc():

   glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

There are quite a few different parameters that can be used with glBlendFunc(), for an easy way to see the effects in realtime, try glSandBox from Codehead.co.uk

We also need to enable blending:

   glEnable(GL_BLEND);

The alpha value specified for the polygon must be 1.0f for the transparency effect to work.

   glColor4f(1.0f,1.0f,1.0f,1.0f);

Also, to get the transparency effect right, the polygons must be drawn from the back of the scene to the front, this is often known as the 'Painter's Algorithm'. The demo gives the option of reversing the draw order so that you can observe the result of not sticking to this rule.

One of the reasons that the drawing order is important when using alpha blending, is that the entire polygon still enters the z-buffer, even the areas that are not visible. If a polygon is drawn behind the transparent polygon, the transparent areas will obscure the new polygon.


Alpha Testing

Fortunately, OpenGL has a mechanism to mask the entry of polygon fragments into the z-buffer, this is called Alpha Testing.
When using alpha testing, the alpha channel values determine whether or not areas of the polygon are rendered, including the z-buffer values. Alpha testing is an on/off affair, unlike the smooth transitions offered by alpha blending, the test is specified using glAlphaFunc(). In our demo we set up to only accept pixels with alpha values above 0.1:

   glAlphaFunc(GL_GREATER,0.1f);

Possible operators for glAlphaFunc() are GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, and GL_ALWAYS, the second parameter simply specifies the cutoff value.

As always with OpenGL, as well as setting parameters, we must enable the operation:

   glEnable(GL_ALPHA_TEST);

In the demo, we can see the alpha test effect best when the draw order is reversed. The transparency effect will not work, but the corners of the polygons are not visible due to those pixels failing the alpha test.


The Sprites

In this demo, a structure contains the information for the sprites:

   typedef struct
    {
     float xPos,yPos,Rotate;
     int Width,Height,Tex; 
     float xVec,yVec,rVec;
    }SpriteInfo;
 
    SpriteInfo Spr[NUM_SPRITES]; // Sprite array

We initalize the sprites with random positions, direction vectors, rotation speeds, textures and sizes:

   // Init the sprite objects
   for(index=0;index<NUM_SPRITES;++index)
    {
     Spr[index].xPos=(rand()%700)+50;
     Spr[index].yPos=(rand()%500)+50;
     tSize=rand()%256;
     Spr[index].Width=tSize;
     Spr[index].Height=tSize;
     Spr[index].xVec=(rand()%10)/10.0f;
     Spr[index].yVec=(rand()%10)/10.0f;
     Spr[index].Rotate=0;
     Spr[index].rVec=((rand()%100)/100.0f)-0.5f;
     Spr[index].Tex=rand()%2;
    }
<syntaxhighlight>
 
Every frame we update the sprites and reverse the direction vectors if the sprite is leaving the screen:
<syntaxhighlight type="cpp">
   // Update the sprite object positions
   for(index=0;index<NUM_SPRITES;++index)
    {
     Spr[index].xPos+=Spr[index].xVec;
      if(Spr[index].xPos < 0 || Spr[index].xPos > 800)
       Spr[index].xVec*=-1.0f;
 
     Spr[index].yPos+=Spr[index].yVec;
      if(Spr[index].yPos < 0 || Spr[index].yPos > 600)
       Spr[index].yVec*=-1.0f;
 
     Spr[index].Rotate+=Spr[index].rVec;
    }

Each sprite is rotated and translated into position individually. In order to do this we need to reset the modelview matrix to a known state for each sprite. OpenGL provides two useful functions that help us here. glPushMatrix() pushes the current matrix onto a Stack, we can then use glPopMatrix() to restore the matrix state later.

   // Draw the sprites
   for(index=0,zPos=0;index<NUM_SPRITES;++index,zPos+=zMod)
    {
     tX=Spr[index].Width/2.0f;
     tY=Spr[index].Height/2.0f;
 
     glBindTexture(GL_TEXTURE_2D,Texture[Spr[index].Tex]);
 
     glPushMatrix();  // Save modelview matrix
 
     glTranslatef(Spr[index].xPos,Spr[index].yPos,0.0f);  // Position sprite
     glRotatef(Spr[index].Rotate,0.0f,0.0f,1.0f);
 
     glBegin(GL_QUADS);                                   // Draw sprite 
      glTexCoord2f(0.0f,0.0f); glVertex3i(-tX, tY,zPos);
      glTexCoord2f(0.0f,1.0f); glVertex3i(-tX,-tY,zPos);
      glTexCoord2f(1.0f,1.0f); glVertex3i( tX,-tY,zPos);
      glTexCoord2f(1.0f,0.0f); glVertex3i( tX, tY,zPos);
     glEnd();
 
    glPopMatrix();  // Restore modelview matrix
   }


Here's a screen shot of the demo
GLTut5-2DAlpha.jpg
Press 1, 2 or 3 to toggle the options.

Source Code

The source to Render.cpp, compile this demo using the OpenGL Tutorial Framework

#include "Framework.h"
#include <time.h>
#include "tga.h"
 
#define NUM_SPRITES 32 // Number of Sprites
 
typedef struct
 {
  float xPos,yPos,Rotate;
  int Width,Height,Tex;
  float xVec,yVec,rVec;
 }SpriteInfo;
 
// Function declarations
bool LoadTexture(char *TexName, GLuint TexHandle);
 
 
void Render(void)
 {
  GLuint Texture[128];         // Handles to our textures
  SpriteInfo Spr[NUM_SPRITES]; // Sprite array
  int index,zPos,zMod;         // Loop vars
  float tX,tY,tSize;           // Temp vars to cut down on calculation
  bool ABlendFlag=true,ATestFlag=false,ZOrder=true; // Option flags
 
  // Allocate all textures in one go
  glGenTextures(128,Texture);
 
 
  // Setup our screen
  glViewport(0,0,800,600);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0,800,0,600,-100,100);
  glMatrixMode(GL_MODELVIEW);
  glClearColor(0.0f,0.0f,0.0f,1.0f);
 
  // Enable z-buffer
  glEnable(GL_DEPTH_TEST);
  glDepthMask(GL_TRUE);
 
  // Load the textures
  LoadTexture("Logo.tga",Texture[0]);
  LoadTexture("ColWheel.tga",Texture[1]);
  LoadTexture("labels.tga",Texture[2]);
 
  // Seed the randomiser
  srand(time(NULL));
 
  // Init the sprite objects
   for(index=0;index<NUM_SPRITES;++index)
    {
     Spr[index].xPos=(rand()%700)+50;
     Spr[index].yPos=(rand()%500)+50;
     tSize=rand()%256;
     Spr[index].Width=tSize;
     Spr[index].Height=tSize;
     Spr[index].xVec=(rand()%10)/10.0f;
     Spr[index].yVec=(rand()%10)/10.0f;
     Spr[index].Rotate=0;
     Spr[index].rVec=((rand()%100)/100.0f)-0.5f;
     Spr[index].Tex=rand()%2;
    }
 
  // Set the general polygon properties
  glColor4f(1.0f,1.0f,1.0f,1.0f);
  glEnable(GL_TEXTURE_2D);
  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
  glAlphaFunc(GL_GREATER,0.1f);
 
  // This loop will run until Esc is pressed
   while(RunLevel)
    {
      if(Keys[VK_ESCAPE]) // Esc Key
       RunLevel=0;
 
      if(Keys['1']) // '1' Key toggles Alpha blending
       {
        ABlendFlag^=true;
        Keys[49]=false;
       }
 
      if(Keys['2']) // '2' Key toggles Alpha testing
       {
        ATestFlag^=true;
        Keys[50]=false;
       }
 
      if(Keys['3']) // '3' Key toggles z ordering
       {
        ZOrder^=true;
        Keys[51]=false;
       }
 
      // Update the sprite object positions
      for(index=0;index<NUM_SPRITES;++index)
       {
        Spr[index].xPos+=Spr[index].xVec;
         if(Spr[index].xPos < 0 || Spr[index].xPos > 800)
          Spr[index].xVec*=-1.0f;
 
        Spr[index].yPos+=Spr[index].yVec;
         if(Spr[index].yPos < 0 || Spr[index].yPos > 600)
          Spr[index].yVec*=-1.0f;
 
        Spr[index].Rotate+=Spr[index].rVec;
       }
 
     // Start the scene
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     glLoadIdentity(); 
 
     // Draw status panel
     glBindTexture(GL_TEXTURE_2D,Texture[2]);
     glBegin(GL_QUADS);
      glTexCoord2f(0.0f,1.0f); glVertex3i(  0,-32,-100);
      glTexCoord2f(0.0f,0.0f); glVertex3i(  0, 96,-100);
      glTexCoord2f(1.0f,0.0f); glVertex3i(256, 96,-100);
      glTexCoord2f(1.0f,1.0f); glVertex3i(256,-32,-100);
     glEnd();
 
     // Set and mark options
      if(ABlendFlag)
       {
        glEnable(GL_BLEND);
        glBegin(GL_QUADS);
         glTexCoord2f(0.0f,1.0f);      glVertex3i(202, 70,-99);
         glTexCoord2f(0.0f,0.8125f);   glVertex3i(202,102,-99);
         glTexCoord2f(0.093f,0.8125f); glVertex3i(236,102,-99);
         glTexCoord2f(0.093f,1.0f);    glVertex3i(236, 70,-99);
        glEnd();
       }
      else
       {
        glDisable(GL_BLEND);
        glBegin(GL_QUADS);
         glTexCoord2f(0.093f,1.0f);    glVertex3i(202, 64,-99);
         glTexCoord2f(0.093f,0.8125f); glVertex3i(202, 96,-99);
         glTexCoord2f(0.186f,0.8125f); glVertex3i(236, 96,-99);
         glTexCoord2f(0.186f,1.0f);    glVertex3i(236, 64,-99);
        glEnd();
       }
 
      if(ATestFlag)
       {
        glEnable(GL_ALPHA_TEST);
        glBegin(GL_QUADS);
         glTexCoord2f(0.0f,1.0f);      glVertex3i(202, 38,-99);
         glTexCoord2f(0.0f,0.8125f);   glVertex3i(202, 70,-99);
         glTexCoord2f(0.093f,0.8125f); glVertex3i(236, 70,-99);
         glTexCoord2f(0.093f,1.0f);    glVertex3i(236, 38,-99);
        glEnd();
       }
      else
       {
        glDisable(GL_ALPHA_TEST);
        glBegin(GL_QUADS);
         glTexCoord2f(0.093f,1.0f);    glVertex3i(202, 32,-99);
         glTexCoord2f(0.093f,0.8125f); glVertex3i(202, 64,-99);
         glTexCoord2f(0.186f,0.8125f); glVertex3i(236, 64,-99);
         glTexCoord2f(0.186f,1.0f);    glVertex3i(236, 32,-99);
        glEnd();
       }
 
      if(ZOrder)
       {
        zMod=1;
        glBegin(GL_QUADS);
         glTexCoord2f(0.0f,1.0f);      glVertex3i(202,  6,-99);
         glTexCoord2f(0.0f,0.8125f);   glVertex3i(202, 38,-99);
         glTexCoord2f(0.093f,0.8125f); glVertex3i(236, 38,-99);
         glTexCoord2f(0.093f,1.0f);    glVertex3i(236,  6,-99);
        glEnd();
       }
      else
       {
        zMod=-1;
        glBegin(GL_QUADS);
         glTexCoord2f(0.093f,1.0f);    glVertex3i(202,  0,-99);
         glTexCoord2f(0.093f,0.8125f); glVertex3i(202, 32,-99);
         glTexCoord2f(0.186f,0.8125f); glVertex3i(236, 32,-99);
         glTexCoord2f(0.186f,1.0f);    glVertex3i(236,  0,-99);
        glEnd();
       }
 
 
     // Draw the sprites
      for(index=0,zPos=0;index<NUM_SPRITES;++index,zPos+=zMod)
       {
        tX=Spr[index].Width/2.0f;
        tY=Spr[index].Height/2.0f;
 
        glBindTexture(GL_TEXTURE_2D,Texture[Spr[index].Tex]);
 
        glPushMatrix();
 
         glTranslatef(Spr[index].xPos,Spr[index].yPos,0.0f);
         glRotatef(Spr[index].Rotate,0.0f,0.0f,1.0f);
 
         glBegin(GL_QUADS);
          glTexCoord2f(0.0f,0.0f); glVertex3i(-tX, tY,zPos);
          glTexCoord2f(0.0f,1.0f); glVertex3i(-tX,-tY,zPos);
          glTexCoord2f(1.0f,1.0f); glVertex3i( tX,-tY,zPos);
          glTexCoord2f(1.0f,0.0f); glVertex3i( tX, tY,zPos);
         glEnd();
 
        glPopMatrix();
 
       }
 
     // Show our scene
     FlipBuffers();
    }
 
  // Clean up textures
  glDeleteTextures(128,Texture);
 }
 
 
// Load a TGA texture
bool LoadTexture(char *TexName, GLuint TexHandle)
 {
  TGAImg Img;        // Image loader
 
  // Load our Texture
   if(Img.Load(TexName)!=IMG_OK)
    return false;
 
  glBindTexture(GL_TEXTURE_2D,TexHandle); // Set our Tex handle as current
 
  // Create the texture
   if(Img.GetBPP()==24)
    glTexImage2D(GL_TEXTURE_2D,0,3,Img.GetWidth(),Img.GetHeight(),0,
                 GL_RGB,GL_UNSIGNED_BYTE,Img.GetImg());
   else if(Img.GetBPP()==32)
    glTexImage2D(GL_TEXTURE_2D,0,4,Img.GetWidth(),Img.GetHeight(),0,
                 GL_RGBA,GL_UNSIGNED_BYTE,Img.GetImg());
   else
    return false;
 
  // Specify filtering and edge actions
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
 
  return true;
 }

Downloads

OpenGL_Tut_(2D_Alpha).zip - A zip including all source code, image files and Win32 exe.