GLSL Font

Last Updated 10/19/14
Before we used alpha textures to render our fonts, but OpenGL 3.0+ does not support alpha textures. Using some GLSL trickery, we can treat a single color channel texture as like an alpha texture.From LFontProgram2D.glvs
//Transformation Matrices
uniform mat4 LProjectionMatrix;
uniform mat4 LModelViewMatrix;
#if __VERSION__ >= 130
//Vertex position attribute
in vec2 LVertexPos2D;
//Texture coordinate attribute
in vec2 LTexCoord;
out vec4 texCoord;
#else
//Vertex position attribute
attribute vec2 LVertexPos2D;
//Texture coordinate attribute
attribute vec2 LTexCoord;
varying vec4 texCoord;
#endif
void main()
{
//Process texCoord
texCoord = vec4( LTexCoord.s, LTexCoord.t, 0.0, 1.0 );
//Process vertex
gl_Position = LProjectionMatrix * LModelViewMatrix * vec4( LVertexPos2D.x, LVertexPos2D.y, 0.0, 1.0 );
}
Our vertex shader for our GLSL font render is pretty much the same as with the textured polygon program. This makes sense since when we render text we're still rendering textured
quads.
From LFontProgram2D.glfs
//Texture Color
uniform vec4 LTextColor;
uniform sampler2D LTextureUnit;
#if __VERSION__ >= 130
//Texture coordinate
in vec4 texCoord;
//Final color
out vec4 gl_FragColor;
#else
//Texture coordinate
varying vec4 texCoord;
#endif
void main()
{
//Get red texture color
vec4 red = texture( LTextureUnit, texCoord.st );
//Set alpha fragment
gl_FragColor = vec4( 1.0, 1.0, 1.0, red.r ) * LTextColor;
}
While alpha textures are not supported, 8bit textures of pure red, green, and blue are. So what we're going to do is load the alpha texture as a red texture, and then in the
fragment shader you see here we'll be using the red component to set the alpha value of the fragment.
From LFontProgram2D.h
void setTextColor( LColorRGBA color );
/*
Pre Condition:
-Bound LFontProgram2D
Post Condition:
-Updates shader program textured polygon color
Side Effects:
-None
*/
void setTextureUnit( GLuint unit );
/*
Pre Condition:
-Bound LFontProgram2D
Post Condition:
-Updates shader program multitexture unit
Side Effects:
-None
*/
private:
//Attribute locations
GLint mVertexPos2DLocation;
GLint mTexCoordLocation;
//Coloring location
GLint mTextColorLocation;
//Texture unit location
GLint mTextureUnitLocation;
//Projection matrix
glm::mat4 mProjectionMatrix;
GLint mProjectionMatrixLocation;
//Modelview matrix
glm::mat4 mModelViewMatrix;
GLint mModelViewMatrixLocation;
};
The LFontProgram2D class pretty much functions the same as the textured polygon shader. For the sake of avoiding typos, note that this class has a setTextColor() function as
opposed to a setTextureColor() function.
From LFontProgram2D.cpp
bool LFontProgram2D::loadProgram()
{
//Generate program
mProgramID = glCreateProgram();
//Load vertex shader
GLuint vertexShader = loadShaderFromFile( "35_glsl_font/LFontProgram2D.glvs", GL_VERTEX_SHADER );
//Check for errors
if( vertexShader == 0 )
{
glDeleteProgram( mProgramID );
mProgramID = 0;
return false;
}
//Attach vertex shader to program
glAttachShader( mProgramID, vertexShader );
//Create fragment shader
GLuint fragmentShader = loadShaderFromFile( "35_glsl_font/LFontProgram2D.glfs", GL_FRAGMENT_SHADER );
//Check for errors
if( fragmentShader == 0 )
{
glDeleteShader( vertexShader );
glDeleteProgram( mProgramID );
mProgramID = 0;
return false;
}
//Attach fragment shader to program
glAttachShader( mProgramID, fragmentShader );
//Link program
glLinkProgram( mProgramID );
//Check for errors
GLint programSuccess = GL_TRUE;
glGetProgramiv( mProgramID, GL_LINK_STATUS, &programSuccess );
if( programSuccess != GL_TRUE )
{
printf( "Error linking program %d!\n", mProgramID );
printProgramLog( mProgramID );
glDeleteShader( vertexShader );
glDeleteShader( fragmentShader );
glDeleteProgram( mProgramID );
mProgramID = 0;
return false;
}
//Clean up excess shader references
glDeleteShader( vertexShader );
glDeleteShader( fragmentShader );
//Get variable locations
mVertexPos2DLocation = glGetAttribLocation( mProgramID, "LVertexPos2D" );
if( mVertexPos2DLocation == -1 )
{
printf( "%s is not a valid glsl program variable!\n", "LVertexPos2D" );
}
mTexCoordLocation = glGetAttribLocation( mProgramID, "LTexCoord" );
if( mTexCoordLocation == -1 )
{
printf( "%s is not a valid glsl program variable!\n", "LTexCoord" );
}
mTextColorLocation = glGetUniformLocation( mProgramID, "LTextColor" );
if( mTextColorLocation == -1 )
{
printf( "%s is not a valid glsl program variable!\n", "LTextColor" );
}
mTextureUnitLocation = glGetUniformLocation( mProgramID, "LTextureUnit" );
if( mTextureUnitLocation == -1 )
{
printf( "%s is not a valid glsl program variable!\n", "LTextureUnit" );
}
mProjectionMatrixLocation = glGetUniformLocation( mProgramID, "LProjectionMatrix" );
if( mProjectionMatrixLocation == -1 )
{
printf( "%s is not a valid glsl program variable!\n", "LProjectionMatrix" );
}
mModelViewMatrixLocation = glGetUniformLocation( mProgramID, "LModelViewMatrix" );
if( mModelViewMatrixLocation == -1 )
{
printf( "%s is not a valid glsl program variable!\n", "LModelViewMatrix" );
}
return true;
}
As you can see not much has changed. Most of the work is done in the fragment shader.
From LTexture.h
protected:
static LTexturedPolygonProgram2D* mTexturedPolygonProgram2D;
GLuint powerOfTwo( GLuint num );
/*
Pre Condition:
-None
Post Condition:
-Returns nearest power of two integer that is greater
Side Effects:
-None
*/
void initVBO();
/*
Pre Condition:
-A valid OpenGL context
-A loaded member texture
Post Condition:
-Generates VBO and IBO to use for rendering
Side Effects:
-Binds NULL VBO and IBO
*/
void freeVBO();
/*
Pre Condition:
-A generated VBO
Post Condition:
-Frees VBO and IBO
Side Effects:
-None
*/
//Texture name
GLuint mTextureID;
//Current pixels
GLuint* mPixels32;
GLubyte* mPixels8;
//Pixel format
GLuint mPixelFormat;
//Texture dimensions
GLuint mTextureWidth;
GLuint mTextureHeight;
//Unpadded image dimensions
GLuint mImageWidth;
GLuint mImageHeight;
//VBO IDs
GLuint mVBOID;
GLuint mIBOID;
};
On the outside, the LTexture class looks largely the same as before.
From LTexture.cpp
bool LTexture::loadTextureFromFile32( std::string path )
{
//Texture loading success
bool textureLoaded = false;
//Generate and set current image ID
ILuint imgID = 0;
ilGenImages( 1, &imgID );
ilBindImage( imgID );
//Load image
ILboolean success = ilLoadImage( path.c_str() );
//Image loaded successfully
if( success == IL_TRUE )
{
//Convert image to RGBA
success = ilConvertImage( IL_RGBA, IL_UNSIGNED_BYTE );
if( success == IL_TRUE )
{
//Initialize dimensions
GLuint imgWidth = (GLuint)ilGetInteger( IL_IMAGE_WIDTH );
GLuint imgHeight = (GLuint)ilGetInteger( IL_IMAGE_HEIGHT );
//Calculate required texture dimensions
GLuint texWidth = powerOfTwo( imgWidth );
GLuint texHeight = powerOfTwo( imgHeight );
//Texture is the wrong size
if( imgWidth != texWidth || imgHeight != texHeight )
{
//Place image at upper left
iluImageParameter( ILU_PLACEMENT, ILU_UPPER_LEFT );
//Resize image
iluEnlargeCanvas( (int)texWidth, (int)texHeight, 1 );
}
//Create texture from file pixels
textureLoaded = loadTextureFromPixels32( (GLuint*)ilGetData(), imgWidth, imgHeight, texWidth, texHeight );
}
//Delete file from memory
ilDeleteImages( 1, &imgID );
//Set pixel format
mPixelFormat = GL_RGBA;
}
//Report error
if( !textureLoaded )
{
printf( "Unable to load %s\n", path.c_str() );
}
return textureLoaded;
}
Naturally, RGBA texture loading hasn't changed.
From LTexture.cpp
bool LTexture::loadPixelsFromFile8( std::string path )
{
//Free texture data if needed
freeTexture();
//Texture loading success
bool pixelsLoaded = false;
//Generate and set current image ID
ILuint imgID = 0;
ilGenImages( 1, &imgID );
ilBindImage( imgID );
//Load image
ILboolean success = ilLoadImage( path.c_str() );
//Image loaded successfully
if( success == IL_TRUE )
{
//Convert image to grey scale
success = ilConvertImage( IL_LUMINANCE, IL_UNSIGNED_BYTE );
if( success == IL_TRUE )
{
//Initialize dimensions
GLuint imgWidth = (GLuint)ilGetInteger( IL_IMAGE_WIDTH );
GLuint imgHeight = (GLuint)ilGetInteger( IL_IMAGE_HEIGHT );
//Calculate required texture dimensions
GLuint texWidth = powerOfTwo( imgWidth );
GLuint texHeight = powerOfTwo( imgHeight );
//Texture is the wrong size
if( imgWidth != texWidth || imgHeight != texHeight )
{
//Place image at upper left
iluImageParameter( ILU_PLACEMENT, ILU_UPPER_LEFT );
//Resize image
iluEnlargeCanvas( (int)texWidth, (int)texHeight, 1 );
}
//Allocate memory for texture data
GLuint size = texWidth * texHeight;
mPixels8 = new GLubyte[ size ];
//Get image dimensions
mImageWidth = imgWidth;
mImageHeight = imgHeight;
mTextureWidth = texWidth;
mTextureHeight = texHeight;
//Copy pixels
memcpy( mPixels8, ilGetData(), size );
pixelsLoaded = true;
}
//Delete file from memory
ilDeleteImages( 1, &imgID );
//Set pixel format
mPixelFormat = GL_RED;
}
//Report error
if( !pixelsLoaded )
{
printf( "Unable to load %s\m", path.c_str() );
}
return pixelsLoaded;
}
As you can see, alpha texture loading hasn't changed much either. We just set the pixel format to red as opposed to alpha.
From LSpriteSheet.h
//Sprite drawing origin
enum LSpriteOrigin
{
LSPRITE_ORIGIN_CENTER,
LSPRITE_ORIGIN_TOP_LEFT,
LSPRITE_ORIGIN_BOTTOM_LEFT,
LSPRITE_ORIGIN_TOP_RIGHT,
LSPRITE_ORIGIN_BOTTOM_RIGHT
};
class LSpriteSheet : public LTexture
{
public:
LSpriteSheet();
/*
Pre Condition:
-None
Post Condition:
-Initializes buffer ID
Side Effects:
-None
*/
~LSpriteSheet();
/*
Pre Condition:
-None
Post Condition:
-Deallocates sprite sheet data
Side Effects:
-None
*/
int addClipSprite( LFRect& newClip );
/*
Pre Condition:
-None
Post Condition:
-Adds clipping rectangle to clip array
-Returns index of clipping rectangle within clip array
Side Effects:
-None
*/
LFRect getClip( int index );
/*
Pre Condition:
-A valid index
Post Condition:
-Returns clipping clipping rectangle at given index
Side Effects:
-None
*/
bool generateDataBuffer( LSpriteOrigin origin = LSPRITE_ORIGIN_CENTER );
/*
Pre Condition:
-A loaded base LTexture
-Clipping rectangles in clip array
Post Condition:
-Generates VBO and IBO to render sprites with
-Sets given origin for each sprite
-Returns true on success
-Reports to console is an error occured
Side Effects:
-Member buffers are bound
*/
void freeSheet();
/*
Pre Condition:
-None
Post Condition:
-Deallocates member VBO, IBO, and clip array
Side Effects:
-None
*/
void freeTexture();
/*
Pre Condition:
-None
Post Condition:
-Frees sprite sheet and base LTexture
Side Effects:
-None
*/
void renderSprite( int index );
/*
Pre Condition:
-Loaded base LTexture
-Generated VBO
Post Condition:
-Renders sprite at given index
Side Effects:
-Base LTexture is bound
-Member buffers are bound
*/
protected:
//Sprite clips
std::vector<LFRect> mClips;
//VBO data
GLuint mVertexDataBuffer;
GLuint* mIndexBuffers;
};
The LSpriteSheet class is back and on the outside it hasn't changed much.
From LSpriteSheet.cpp
void LSpriteSheet::renderSprite( int index )
{
//Sprite sheet data exists
if( mVertexDataBuffer != NULL )
{
//Set texture
glBindTexture( GL_TEXTURE_2D, getTextureID() );
//Enable vertex and texture coordinate arrays
mTexturedPolygonProgram2D->enableVertexPointer();
mTexturedPolygonProgram2D->enableTexCoordPointer();
//Bind vertex data
glBindBuffer( GL_ARRAY_BUFFER, mVertexDataBuffer );
//Set texture coordinate data
mTexturedPolygonProgram2D->setTexCoordPointer( sizeof(LTexturedVertex2D), (GLvoid*)offsetof( LTexturedVertex2D, texCoord ) );
//Set vertex data
mTexturedPolygonProgram2D->setVertexPointer( sizeof(LTexturedVertex2D), (GLvoid*)offsetof( LTexturedVertex2D, position ) );
//Draw quad using vertex data and index data
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mIndexBuffers[ index ] );
glDrawElements( GL_QUADS, 4, GL_UNSIGNED_INT, NULL );
//Disable vertex and texture coordinate arrays
mTexturedPolygonProgram2D->disableVertexPointer();
mTexturedPolygonProgram2D->disableTexCoordPointer();
}
}
Again since we're doing 100% shader based rendering, the sprite sheet class uses the textured polygon shader. This is easy to do since a LSpriteSheet is an LTexture through
inheritance.
From LFont.h
//Class wide program
static LFontProgram2D* mFontProgram2D;
//Font TTF library
static FT_Library mLibrary;
//Spacing variables
GLfloat mSpace;
GLfloat mLineHeight;
GLfloat mNewLine;
};
The LFont class now has a class wide shader object it uses.
From LFont.cpp
//LFont library
FT_Library LFont::mLibrary;
//Class wide program
LFontProgram2D* LFont::mFontProgram2D = NULL;
bool LFont::initFreeType()
{
//Init FreeType for single threaded applications
#ifndef __FREEGLUT_H__
FT_Error error = FT_Init_FreeType( &mLibrary );
if( error )
{
printf( "FreeType error:%X", error );
return false;
}
#endif
return true;
}
void LFont::setFontProgram2D( LFontProgram2D* fontProgram2D )
{
//Set rendering program
mFontProgram2D = fontProgram2D;
}
And of course it needs a function to set the class wide object.
From LFont.cpp
bool LFont::loadBitmap( std::string path )
{
//Loading flag
bool success = true;
//Background pixel
const GLubyte BLACK_PIXEL = 0x00;
//Get rid of the font if it exists
freeFont();
//Image pixels loaded
if( loadPixelsFromFile8( path ) )
{
//Get cell dimensions
GLfloat cellW = imageWidth() / 16.f;
GLfloat cellH = imageHeight() / 16.f;
//Get letter top and bottom
GLuint top = cellH;
GLuint bottom = 0;
GLuint aBottom = 0;
//Current pixel coordinates
int pX = 0;
int pY = 0;
//Base cell offsets
int bX = 0;
int bY = 0;
//Begin parsing bitmap font
GLuint currentChar = 0;
LFRect nextClip = { 0.f, 0.f, cellW, cellH };
//Go through cell rows
for( unsigned int rows = 0; rows < 16; ++rows )
{
//Go through each cell column in the row
for( unsigned int cols = 0; cols < 16; ++cols )
{
//Begin cell parsing
//Set base offsets
bX = cellW * cols;
bY = cellH * rows;
//Initialize clip
nextClip.x = cellW * cols;
nextClip.y = cellH * rows;
nextClip.w = cellW;
nextClip.h = cellH;
//Find left side of character
for( int pCol = 0; pCol < cellW; ++pCol )
{
for( int pRow = 0; pRow < cellH; ++pRow )
{
//Set pixel offset
pX = bX + pCol;
pY = bY + pRow;
//Non-background pixel found
if( getPixel8( pX, pY ) != BLACK_PIXEL )
{
//Set sprite's x offset
nextClip.x = pX;
//Break the loops
pCol = cellW;
pRow = cellH;
}
}
}
//Right side
for( int pCol_w = cellW - 1; pCol_w >= 0; pCol_w-- )
{
for( int pRow_w = 0; pRow_w < cellH; pRow_w++ )
{
//Set pixel offset
pX = bX + pCol_w;
pY = bY + pRow_w;
//Non-background pixel found
if( getPixel8( pX, pY ) != BLACK_PIXEL )
{
//Set sprite's width
nextClip.w = ( pX - nextClip.x ) + 1;
//Break the loops
pCol_w = -1;
pRow_w = cellH;
}
}
}
//Find Top
for( int pRow = 0; pRow < cellH; ++pRow )
{
for( int pCol = 0; pCol < cellW; ++pCol )
{
//Set pixel offset
pX = bX + pCol;
pY = bY + pRow;
//Non-background pixel found
if( getPixel8( pX, pY ) != BLACK_PIXEL )
{
//New Top Found
if( pRow < top )
{
top = pRow;
}
//Break the loops
pCol = cellW;
pRow = cellH;
}
}
}
//Find Bottom
for( int pRow_b = cellH - 1; pRow_b >= 0; --pRow_b )
{
for( int pCol_b = 0; pCol_b < cellW; ++pCol_b )
{
//Set pixel offset
pX = bX + pCol_b;
pY = bY + pRow_b;
//Non-background pixel found
if( getPixel8( pX, pY ) != BLACK_PIXEL )
{
//Set BaseLine
if( currentChar == 'A' )
{
aBottom = pRow_b;
}
//New bottom Found
if( pRow_b > bottom )
{
bottom = pRow_b;
}
//Break the loops
pCol_b = cellW;
pRow_b = -1;
}
}
}
//Go to the next character
mClips.push_back( nextClip );
++currentChar;
}
}
//Set Top
for( int t = 0; t < 256; ++t )
{
mClips[ t ].y += top;
mClips[ t ].h -= top;
}
//Create texture from parsed pixels
if( loadTextureFromPixels8() )
{
//Build vertex buffer from sprite sheet data
if( !generateDataBuffer( LSPRITE_ORIGIN_TOP_LEFT ) )
{
printf( "Unable to create vertex buffer for bitmap font!\n" );
success = false;
}
}
else
{
printf( "Unable to create texture from bitmap font pixels!\n" );
success = false;
}
//Set texture wrap
glBindTexture( GL_TEXTURE_2D, getTextureID() );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER );
//Set spacing variables
mSpace = cellW / 2;
mNewLine = aBottom - top;
mLineHeight = bottom - top;
}
else
{
printf( "Could not load bitmap font image: %s!\n", path.c_str() );
success = false;
}
return success;
}
Font loading hasn't changed much, which makes sense since shader programs handle rendering.
From LFont.cpp
void LFont::renderText( GLfloat x, GLfloat y, std::string text, LFRect* area, int align )
{
//If there is a texture to render from
if( getTextureID() != 0 )
{
//Draw positions
GLfloat dX = x;
GLfloat dY = y;
//If the text needs to be aligned
if( area != NULL )
{
//Correct empty alignment
if( align == 0 )
{
align = LFONT_TEXT_ALIGN_LEFT | LFONT_TEXT_ALIGN_TOP;
}
//Handle horizontal alignment
if( align & LFONT_TEXT_ALIGN_LEFT )
{
dX = area->x;
}
else if( align & LFONT_TEXT_ALIGN_CENTERED_H )
{
dX = area->x + ( area->w - substringWidth( text.c_str() ) ) / 2.f;
}
else if( align & LFONT_TEXT_ALIGN_RIGHT )
{
dX = area->x + ( area->w - substringWidth( text.c_str() ) );
}
//Handle vertical alignment
if( align & LFONT_TEXT_ALIGN_TOP )
{
dY = area->y;
}
else if( align & LFONT_TEXT_ALIGN_CENTERED_V )
{
dY = area->y + ( area->h - stringHeight( text.c_str() ) ) / 2.f;
}
else if( align & LFONT_TEXT_ALIGN_BOTTOM )
{
dY = area->y + ( area->h - stringHeight( text.c_str() ) );
}
}
//Move to draw position
mFontProgram2D->leftMultModelView( glm::translate<GLfloat>( glm::mat4(), glm::vec3( dX, dY, 0.f ) ) );
//Set texture
glBindTexture( GL_TEXTURE_2D, getTextureID() );
//Enable vertex and texture coordinate arrays
mFontProgram2D->enableVertexPointer();
mFontProgram2D->enableTexCoordPointer();
//Bind vertex data
glBindBuffer( GL_ARRAY_BUFFER, mVertexDataBuffer );
//Set texture coordinate data
mFontProgram2D->setTexCoordPointer( sizeof(LTexturedVertex2D), (GLvoid*)offsetof( LTexturedVertex2D, texCoord ) );
//Set vertex data
mFontProgram2D->setVertexPointer( sizeof(LTexturedVertex2D), (GLvoid*)offsetof( LTexturedVertex2D, position ) );
//Go through string
for( int i = 0; i < text.length(); ++i )
{
//Space
if( text[ i ] == ' ' )
{
mFontProgram2D->leftMultModelView( glm::translate<GLfloat>( glm::mat4(), glm::vec3( mSpace, 0.f, 0.f ) ) );
mFontProgram2D->updateModelView();
dX += mSpace;
}
//Newline
else if( text[ i ] == '\n' )
{
//Handle horizontal alignment
GLfloat targetX = x;
if( area != NULL )
{
if( align & LFONT_TEXT_ALIGN_LEFT )
{
targetX = area->x;
}
else if( align & LFONT_TEXT_ALIGN_CENTERED_H )
{
targetX = area->x + ( area->w - substringWidth( &text.c_str()[ i + 1 ] ) ) / 2.f;
}
else if( align & LFONT_TEXT_ALIGN_RIGHT )
{
targetX = area->x + ( area->w - substringWidth( &text.c_str()[ i + 1 ] ) );
}
}
//Move to target point
mFontProgram2D->leftMultModelView( glm::translate<GLfloat>( glm::mat4(), glm::vec3( targetX - dX, mNewLine, 0.f ) ) );
mFontProgram2D->updateModelView();
dY += mNewLine;
dX += targetX - dX;
}
//Character
else
{
//Get ASCII
GLuint ascii = (unsigned char)text[ i ];
//Update position matrix in program
mFontProgram2D->updateModelView();
//Draw quad using vertex data and index data
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mIndexBuffers[ ascii ] );
glDrawElements( GL_QUADS, 4, GL_UNSIGNED_INT, NULL );
//Move over
mFontProgram2D->leftMultModelView( glm::translate<GLfloat>( glm::mat4(), glm::vec3( mClips[ ascii ].w, 0.f, 0.f ) ) );
mFontProgram2D->updateModelView();
dX += mClips[ ascii ].w;
}
}
//Disable vertex and texture coordinate arrays
mFontProgram2D->disableVertexPointer();
mFontProgram2D->disableTexCoordPointer();
}
}
Even in the font rendering function, all were doing is switching from sending the data to the fixed function pipeline to our own shader program.
From LUtil.cpp
//Screen dimensions
LFRect gScreenArea = { 0.f, 0.f, SCREEN_WIDTH, SCREEN_HEIGHT };
//Textured polygon shader
LTexturedPolygonProgram2D gTexturedPolygonProgram2D;
//Loaded texture
LTexture gOpenGLTexture;
LColorRGBA gImgColor = { 0.5f, 0.5f, 0.5f, 1.f };
//Font shader
LFontProgram2D gFontProgram2D;
//Loaded font
LFont gFont;
LColorRGBA gTextColor = { 1.f, 0.5f, 1.f, 1.f };
At the top of LUtil.cpp, we declare our shader and media objects.
From LUtil.cpp
bool loadGP()
{
//Load textured shader program
if( !gTexturedPolygonProgram2D.loadProgram() )
{
printf( "Unable to load textured polygon program!\n" );
return false;
}
//Bind textured shader program
gTexturedPolygonProgram2D.bind();
//Initialize projection
gTexturedPolygonProgram2D.setProjection( glm::ortho<GLfloat>( 0.0, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0, 1.0, -1.0 ) );
gTexturedPolygonProgram2D.updateProjection();
//Initialize modelview
gTexturedPolygonProgram2D.setModelView( glm::mat4() );
gTexturedPolygonProgram2D.updateModelView();
//Set texture unit
gTexturedPolygonProgram2D.setTextureUnit( 0 );
//Set program for texture
LTexture::setTexturedPolygonProgram2D( &gTexturedPolygonProgram2D );
//Load font shader program
if( !gFontProgram2D.loadProgram() )
{
printf( "Unable to load font rendering program!\n" );
return false;
}
//Bind font shader program
gFontProgram2D.bind();
//Initialize projection
gFontProgram2D.setProjection( glm::ortho<GLfloat>( 0.0, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0, 1.0, -1.0 ) );
gFontProgram2D.updateProjection();
//Initialize modelview
gFontProgram2D.setModelView( glm::mat4() );
gFontProgram2D.updateModelView();
//Set texture unit
gFontProgram2D.setTextureUnit( 0 );
//Set program for font rendering
LFont::setFontProgram2D( &gFontProgram2D );
return true;
}
Here we're loading our two shader programs. Remember that in order to update a program's variables, it has to be currently bound.
From LUtil.cpp
void render()
{
//Clear color buffer
glClear( GL_COLOR_BUFFER_BIT );
//Bind texture rendering program
gTexturedPolygonProgram2D.bind();
//Reset transformations
gTexturedPolygonProgram2D.setModelView( glm::mat4() );
//Render texture centered
gTexturedPolygonProgram2D.setTextureColor( gImgColor );
gOpenGLTexture.render( ( SCREEN_WIDTH - gOpenGLTexture.imageWidth() ) / 2.f, ( SCREEN_HEIGHT - gOpenGLTexture.imageHeight() ) / 2.f );
//Bind font rendering program
gFontProgram2D.bind();
//Reset transformations
gFontProgram2D.setModelView( glm::mat4() );
//Render text centered
gFontProgram2D.setTextColor( gTextColor );
gFont.renderText( 0.f, 0.f, "GLSL Text Rendering!", &gScreenArea, LFONT_TEXT_ALIGN_CENTERED_H | LFONT_TEXT_ALIGN_CENTERED_V );
//Update screen
glutSwapBuffers();
}
In the render() function we bind the textured polygon shader, set its modelview matrix and then render the texture.
Then we bind the font shader, update its modelview matrix (even though they have the same names in their shaders, they are seperate programs), and render the text.
Then we bind the font shader, update its modelview matrix (even though they have the same names in their shaders, they are seperate programs), and render the text.