OpenGL:Tutorials:Tutorial Framework:Ortho and Alpha
From GPWiki
The wiki is now hosted by GameDev.NET at wiki.gamedev.net. All gpwiki.org content has been moved to the new server. However, the GPWiki forums are still active! Come say hello. 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.
[edit] Some ChangesThe 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. [edit] Alpha BlendingThe 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.
[edit] Alpha TestingFortunately, OpenGL has a mechanism to mask the entry of polygon fragments into the z-buffer, this is called Alpha Testing. 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.
[edit] The SpritesIn 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; } Every frame we update the sprites and reverse the direction vectors if the sprite is leaving the screen: // 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 }
[edit] Source CodeThe 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; } [edit] DownloadsOpenGL_Tut5_(2D Alpha).zip - A zip including all source code, image files and Win32 exe. |



