Tutorials
Articles
OpenGL Demos
Games
OpenGL Misc
MSG Board
About
Donate
Links
Home
Megabyte Softworks
C++, OpenGL, Algorithms




Current series: OpenGL 3.3
(Return to list of OpenGL 3.3 tutorials)

Download (1.43 MB)
4628 downloads. 8 comments
9.) Fonts And Ortho Projection

Welcome to the ninth OpenGL 3.3 tutorial. This one will cover two frequently used things in interactive applications: first is orthographic 2D projection, one that will allow us to render things over scene (for example GUI), and then with this knowledge, we can proceed with creation of FreeType fonts class, that will be able to print 2D text with specified parameters. In this tutorial, we'll have one torus rendering in the middle, and some 2D text on screen that will be reporting its number of faces and other text. And this much text is enough for introduction, so let's begin LULZ.

Orthographic projection

This part of tutorial is rather easy. Orthographic projection is a means of representing 3D object in two dimensions, where projection lines are orthogonal to projection plane. If this explanations scares you, don't worry, straightforward explanation follows. Projection plane is a plane to which project data - in our case it's our 2D screen on monitor. Projection lines are perpendicular to our 2D screen - they point right at you LULZ ! This all basically now means, that if we express our objects in 2D screen coordinates, orthogonal projection matrix will project them exactly to 2D screen coordinates. That's in the rough all.

In order to set this kind of projection, we will have to calculate orthographic projection matrix. Luckily, our glm library provides a function that calculates orthographic projection matrix, and thus all we need to do is to change projection matrix. Or do we? I think that the best thing to handle all these 2D drawings over a scene would be another shader program just for that. We won't be doing any kind of lighting or any complex calculations - all we want from our shader program is to properly transform vertices to on-screen coordinates, provide support for changing colors of drawn things, and that's all (for now). So let's have a look at this pieces of code, which prepares our scene for 2D drawing:


void COpenGLControl::setOrtho2D(int width, int height)
{
   mOrtho = glm::ortho(0.0f, float(width), 0.0f, float(height));
}

LRESULT CALLBACK COpenGLWinApp::msgHandlerMain(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
   //...
   case WM_SIZE:
      oglControl.resizeOpenGLViewportFull();
      oglControl.setProjection3D(45.0f, float(LOWORD(lParam))/float(HIWORD(lParam)), 0.001f, 1000.0f);
      oglControl.setOrtho2D(LOWORD(lParam), HIWORD(lParam));
      break;

   //...
}

First one is implemented in OpenGL control class. It just calls glm::ortho function and remembers the resulting matrix. This function has four parameters - the left, right, bottom and top coordinate of our orthographics projection. In our case it's left side of window (0.0f) with whole window width, and bottom side of window (0.0f) with whole window height. That's all you need to know about this function. We call every time we resize window - that's the time when orthogrpahic matrix needs to be recalculated. If you are interested in how orthographic projection matrix looks like, you can have a look here: http://en.wikipedia.org/wiki/Orthographic_projection_(geometry)

Now that we have setup orthographic projection, all we have to do is send data in screen coordinates to proceed with 2D drawings. And our 2D drawings in this tutorial will be fonts.

FreeType Fonts

FreeType (www.freetype.org) is a free, open source, portable library for managing fonts. It can load TrueType and OpenType fonts, and this is absolutely enough. All fonts in your computer are in this format, and you can download thousands more from many sites on internet. So first thing you need to do is to download FreeType library and extract in your libraries directory. The process will be a little different - there is no prebuilt library, you must build it on your own. But it's nothing difficult, there is a Visual C++ project that you can open, then just press F7 in Visual Studio and build FreeType library (.lib file for Visual Studio). I will guide you through this process: first go to www.freetype.org, then click freetype.sourceforge.net for example, then http://savannah.nongnu.org/download/freetype/ and then download latest FreeType version (in time of writing this, it's 2.4.8):

Extract it into your libraries directory and open it. It should look like this:

Now go to builds/win32/vc2008 and open freetype.sln. Then select LIB Release configuration and build it, and also LIB Debug configuration and build it. After it's done, you will find resulting .lib files in freetype-2.4.8/objs/win32/vc2008/. You will need to add this directory in VC++ Directories to use it (it's shown in first tutorial). You also need to add include directory, which is freetype-2.4.8/include/ to VC++ Include directories. Now after you've done this, we can proceed with creation of CFreeTypeFont class, which will again handle all stuff we will need to do with fonts - loading, printing, releasing etc. This is how the class looks like:


class CFreeTypeFont
{
public:
   bool loadFont(string sFile, int iPXSize);
   bool loadSystemFont(string sName, int iPXSize);

   int getTextWidth(string sText, int iPXSize);

   void print(string sText, int x, int y, int iPXSize = -1);

   void releaseFont();

   void setShaderProgram(CShaderProgram* a_shShaderProgram);

   CFreeTypeFont();
private:
   void createChar(int iIndex);

   CTexture tCharTextures[256];
   int iAdvX[256], iAdvY[256];
   int iBearingX[256], iBearingY[256];
   int iCharWidth[256], iCharHeight[256];
   int iLoadedPixelSize, iNewLine;

   bool bLoaded;

   UINT uiVAO;
   CVertexBufferObject vboData;

   FT_Library ftLib;
   FT_Face ftFace;
   CShaderProgram* shShaderProgram;
};

We need to examine two functions - loadFont and print, which are most important. Other functions are easy to understand, these two require little explanation. So let's begin with font loading function. It has 2 parameters - filename of font and desired pixel size. Purpose of this function is to load specified font with specified pixel size. This number just means, what font (and slo texture) sizes will internally be stored. If we want to have a big captions, we should put there a bigger number. If it's for general printing of some descriptions for example, then we are good with smaller size. Of course, we can print with smaller sizes then those we specified, but when printing bigger, the font textures are stretched and result is worse. In this tutorial, we load font with 32 pixel size. Our goal is to create a texture for each character with size enough to hold our letter at desired pixel size. This means, that the pixel size of font doesn't have to be power of two, it can be 40 for example, but if character needs to use 40x40 image, our texture will be of nearest greater power of two size - in this case 64x64. So let's have a look at the load function:


bool CFreeTypeFont::loadFont(string sFile, int iPXSize)
{
   BOOL bError = FT_Init_FreeType(&ftLib);
   
   bError = FT_New_Face(ftLib, sFile.c_str(), 0, &ftFace);
   if(bError)return false;
   FT_Set_Pixel_Sizes(ftFace, iPXSize, iPXSize);
   iLoadedPixelSize = iPXSize;

   glGenVertexArrays(1, &uiVAO);
   glBindVertexArray(uiVAO);
   vboData.createVBO();
   vboData.bindVBO();

   FOR(i, 128)createChar(i);
   bLoaded = true;

   FT_Done_Face(ftFace);
   FT_Done_FreeType(ftLib);
   
   vboData.uploadDataToGPU(GL_STATIC_DRAW);
   glEnableVertexAttribArray(0);
   glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec2)*2, 0);
   glEnableVertexAttribArray(1);
   glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec2)*2, (void*)(sizeof(glm::vec2)));
   return true;
}

First, we initialize FreeType library. Then we create a new face object by calling FT_New_Face. A face describes a given typeface and style. First parameter is previously initialized library, then it's font filename, face index is third and it just tells which face from file to load, because there can be multiple font faces embedded in single file. But index 0 will always work, as is written in FreeType documentation. Last parameter is face object where data of face are stored. Then we set pixel sizes of letters of face. That's all for FreeType part of initialization, let's get to OpenGL part.

As I said, we will have one texture for each character. Some textures may require bigger textures (for example, letter 'a' will surely need greater texture space than '.' for example). Because of that, we will have also VBO, in which we will store letter by letter rendering data. For every character, we will define a quad to properly render the letter. And we will texture this quad with letter texture. The drawing mode will be GL_TRIANGLE_STRIPS, since there is no GL_QUADS anymore. Texture coordinates of every letter wll always be the same - (0, 1), (0, 0), (1, 1) and (1, 0) and they will get copied into buffer multiple times, which means that we are wasting some space. But it's not that much space that we cannot afford it LULZ. I know it can be done more efficient, but it's not necessary now.

Now after we initalized VBO and VAO for this font, we must proceed with creation of 128 characters (all ASCII characters), even though all characters with ASCII code below 32 are probably useless. So we call function createChar for each character. This function creates a texture with appropriate size for given character. Let's see how it looks like:


inline int next_p2(int n){int res = 1; while(res < n)res <<= 1; return res;}

void CFreeTypeFont::createChar(int iIndex)
{
   FT_Load_Glyph(ftFace, FT_Get_Char_Index(ftFace, iIndex), FT_LOAD_DEFAULT);

   FT_Render_Glyph(ftFace->glyph, FT_RENDER_MODE_NORMAL);
   FT_Bitmap* pBitmap = &ftFace->glyph->bitmap;

   int iW = pBitmap->width, iH = pBitmap->rows;
   int iTW = next_p2(iW), iTH = next_p2(iH);

   GLubyte* bData = new GLubyte[iTW*iTH];
   // Copy glyph data and add dark pixels elsewhere
   FOR(ch, iTH)FOR(cw, iTW)
      bData[ch*iTW+cw] = (ch >= iH || cw >= iW) ? 0 : pBitmap->buffer[(iH-ch-1)*iW+cw];
 
   // And create a texture from it

   tCharTextures[iIndex].createFromData(bData, iTW, iTH, 16, GL_DEPTH_COMPONENT, false);
   tCharTextures[iIndex].setFiltering(TEXTURE_FILTER_MAG_BILINEAR, TEXTURE_FILTER_MIN_BILINEAR);

   tCharTextures[iIndex].setSamplerParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
   tCharTextures[iIndex].setSamplerParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
   tCharTextures[iIndex].setSamplerParameter(GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE);

   // Calculate glyph data
   iAdvX[iIndex] = ftFace->glyph->advance.x>>6;
   iBearingX[iIndex] = ftFace->glyph->metrics.horiBearingX>>6;
   iCharWidth[iIndex] = ftFace->glyph->metrics.width>>6;

   iAdvY[iIndex] = (ftFace->glyph->metrics.height - ftFace->glyph->metrics.horiBearingY)>>6;
   iBearingY[iIndex] = ftFace->glyph->metrics.horiBearingY>>6;
   iCharHeight[iIndex] = ftFace->glyph->metrics.height>>6;

   iNewLine = max(iNewLine, int(ftFace->glyph->metrics.height>>6));

   // Rendering data, texture coordinates are always the same, so now we waste a little memory
   glm::vec2 vQuad[] =
   {
      glm::vec2(0.0f, float(-iAdvY[iIndex]+iTH)),
      glm::vec2(0.0f, float(-iAdvY[iIndex])),
      glm::vec2(float(iTW), float(-iAdvY[iIndex]+iTH)),
      glm::vec2(float(iTW), float(-iAdvY[iIndex]))
   };
   glm::vec2 vTexQuad[] = {glm::vec2(0.0f, 1.0f), glm::vec2(0.0f, 0.0f), glm::vec2(1.0f, 1.0f), glm::vec2(1.0f, 0.0f)};

   // Add this char to VBO
   FOR(i, 4)
   {
      vboData.addData(&vQuad[i], sizeof(glm::vec2));
      vboData.addData(&vTexQuad[i], sizeof(glm::vec2));
   }
   delete[] bData;
}

FT_Load_Glyph loads an image of character. It takes face object, glyph index, and load mode as parameters. Glyph index is used to locate correct character in face object. In order to get proper glyph index, we call function FT_Get_Char_Index and this will return correct glyph index for our characters. Face object contains charmaps, whic are used to convert character code to proper glyph indices. Freetype uses Unicode charmap by default, so that makes it a good choice to print characters specific for some languages, like Slovak or German (later I plan to extend this tutorial, so that this class is able to print Unicode strings, it's actually pretty easy).

After loading a character, we render it to memory bitmap with normal render mode. Now we have data from which we can create OpenGL texture. But since Freetype bitmap of specified letter won't always be power of two, we first find next greater power of two with function next_p2 (it's inline, so that code is faster). After that, we copy glyph image data to our own array, where we add blank dark pixels to the texture (if glyph image was 40x40, our texture is 64x64 and those newly added pixels are black and transparent when rendering). We also flip image, because it's flipped by default (we could also flip texture coordinates, which would be faster, but I rather chose to flip data, even though it's slower LULZ, but I can look at the texture in memory without turning my head upside down LULZ). From this data, we create texture (I added a function createFromData to CTexture class, so that it's easy to do just that). We won't create any mipmaps for character textures, filters for minification and magnification will both be bilinear. We set texture format to GL_DEPTH_COMPONENT, because this is what we need - every pixel is a grayscale value, that represents how intense that pixel is. We want to use this color also as alpha for blending (we want all 4 components of RGBA to have that grayscale value).

Next lines of code calculate glyph parameters. Each character is somehow specific. For example - letter 'a' is quite normal, but letter 'g' for example goes uner the baseline, so when it's rendered in texture, the lower edge of textured quad must start under baseline. And characters like '.' are very small and they don't need that much of texture space. All these things can be parametrized as letter bearingX, bearingY, advanceX, advanceY and so on. It's nicely described here, I think that you shouldn't have a problem understanding it from original freetype tutorials, definitely have a look there now: http://www.freetype.org/freetype2/docs/tutorial/step2.html. I think it would be useless to try to describe all these things again.

One thing you probably noticed is that all these numbers are shifted to right by 6. (>> 6). It's because all these glyph data are in (1/64th of pixel) units for some reasons, so we must divide these numbers by 64 in order to get values in pixels. Shifting right by 6 does just that (divided by 2^6). Finally we add proper vertices positions and texture coordinates for every character to our local font VBO.

After all characters are processed, we release FreeType objects, since we have all important data grabbed from them and then we can upload VBO to GPU and that's all for initialization phase.

Font render

Finally, we get to text rendering. This code will render text sText on position x,y with pixel size iPXSize:


void CFreeTypeFont::print(string sText, int x, int y, int iPXSize)
{
   if(!bLoaded)return;

   glBindVertexArray(uiVAO);
   shShaderProgram->setUniform("gSampler", 0);
   glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   int iCurX = x, iCurY = y;
   if(iPXSize == -1)iPXSize = iLoadedPixelSize;
   float fScale = float(iPXSize)/float(iLoadedPixelSize);
   FOR(i, ESZ(sText))
   {
      if(sText[i] == '\n')
      {
         iCurX = x;
         iCurY -= iNewLine*iPXSize/iLoadedPixelSize;
         continue;
      }
      int iIndex = int(sText[i]);
      iCurX += iBearingX[iIndex]*iPXSize/iLoadedPixelSize;

      tCharTextures[iIndex].bindTexture();
      glm::mat4 mModelView = glm::translate(glm::mat4(1.0f), glm::vec3(float(iCurX), float(iCurY), 0.0f));
      mModelView = glm::scale(mModelView, glm::vec3(fScale));
      shShaderProgram->setUniform("modelViewMatrix", mModelView);

      // Draw letter
      glDrawArrays(GL_TRIANGLE_STRIP, iIndex*4, 4);

      iCurX += (iAdvX[iIndex]-iBearingX[iIndex])*iPXSize/iLoadedPixelSize;
   }
   glDisable(GL_BLEND);
}

First of all, we enable blending. We want the color to act as alpha. We don't have to care about that, because in shader, we call texture2D function on texture with depth component, and it seems it grabs values exactly the way we want - that every RGBA component has the same value (whole tutorial works on my GPU properly, so it should be OK, but correct me if I'm wrong).

Edit: This doesn't work on all cards. On my old notebook on Nvidia 9300M it produced incorrect results, so I had to change code of this tutorial from:
outputColor = vTexColor*vColor;
to
outputColor = (vTexColor.r, vTexColor.r, vTexColor.r, vTexColor.r)*vColor;
To force it to grab same value for whole RGBA part.
I also decided to create fragment shader only for fonts, it's called font2D.frag.


Then we just proceed character by character and render it. Before all we calculate fScale value - how to much to scale incoming data according to desired printed pixel size. If our loaded font has pixel size 64 and we want to print text with pixel size 32, then we need to scale incoming data by 32/64 = 0.5. Then we just properly setup our transformation matrix - translate and then scale it, and we can render character. When it's rendered, we just move our current line marker iCurX (where on the line we're printing).

There are some cases handled - like if there's newline character, we just move down by previously calculated value iNewLine and reset line marker. Another special case is ' ' character (space). I don't know what's wrong, but if I didn't just skip the space, there was a single black dot instead of just space. Maybe it has something to do with wrong texture, because blank space just doesn't need texture. You can try to unhandle that case to see.

After that, we disable blending and our printing work is done. Last thing to have a look at is actual 2D orthographic rendering.

Rendering of scene

void renderScene(LPVOID lpParam)
{
   //...
   
   // Render torus

   //...

   glDisable(GL_DEPTH_TEST);
   
   spFont2D.useProgram();
   // Font color, you can even change transparency of font with alpha parameter
   spFont2D.setUniform("vColor", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
   spFont2D.setUniform("projectionMatrix", oglControl->getOrthoMatrix());

   char buf[255];
   sprintf(buf, "Font Size: %d\nPress UP and DOWN arrow key to change\n\nTotal Torus Faces: %d", iFontSize, iTorusFaces);

   ftFont.print(buf, 20, oglControl->getViewportHeight()-10-iFontSize, iFontSize);

   ftFont.print("www.mbsoftworks.sk", 20, 20, 24);
   
   glEnable(GL_DEPTH_TEST);
   fGlobalAngle += appMain.sof(100.0f);
   cCamera.update();

   if(Keys::onekey(VK_UP))iFontSize++;
   if(Keys::onekey(VK_DOWN))iFontSize--;
   if(Keys::onekey(VK_ESCAPE))PostQuitMessage(0);

   oglControl->swapBuffers();
}

First, we normally render our 3D scene with our directional light program. In this tutorial, there is only one torus rendered - no sun, no ground and no boxes - just torus . You can also move around with WSAD keys as usual. Then we switch shader program to 2D ortho program and disable depth testing, because we want to draw over our scene and there's no need for it. Now we can do all our 2D rendering. There are few lines of text printed. After that, we restore depth testing and we are done with 2D rendering.

In initScene function, we call loadSystemFont for our FreeType class, and also setShaderProgram to tell it, which shader program to use. And that should cover all important topics from this tutorial.

Conclusion

This is how the result looks like:

Ability to print font gives us many possibilites. Now we can create menus for our games for example , or we can just print briefing for a mission. FreeType fonts are definitely a great choice for OpenGL fonts, because they are easy to use, they can load probably all used font types today and they can handle Unicode with no problems. Now that we wrapped with OpenGL into a single easy-to-use class, our OpenGL coding days should seem much brighter .

That's all for this tutorial. I hope it helped you, and you can look forward to next, in which we will cover rendering of skyboxes.



Download (1.43 MB)
4628 downloads. 8 comments
 
Name:

E-mail:
(Optional)
Entry:

Enter the text from image:



Smileys




j on 02.01.2014 17:56:27
DO NOT use this approach. This is the worst possible way to go: to generate a separate texture for EVERY single char and then have multiple re-bindings just to render a short text.

Binding texture is costly, hence texture atlases. If you plan to display anything longer than a few chars do not use this code or approach.

On a side note: "using namespace" in a header file? C'mon, that's exactly what namespaces are NOT for :). /rant
TheWeepingCorpse on 04.02.2014 18:44:00
Agreed. Much better to create a triangle mesh. I cache all print statements and use degenerate triangles to join seperate lines. This allows a full screen of text to be rendered with a single draw call.
Johan on 01.07.2013 20:03:08
Great tutorial!
I was hoping to base my own text renderer on your tutorial, but I have hit some issues. First, the billboards are drawn black, not transparent. You can check this by changing the clear color to blue or something and running the program. Second, some fonts are not rendered correctly. I tested with Arial.ttf and the 'l' and ':' were broken, probably others too. To clarify; this is unaltered tutorial code. I'm just playing around with fonts. Any ideas?
*paws* on 14.10.2012 22:03:18
i don't get any character out. i only see the torus turning and the background. if i switch of the glBlendFunc in the freetypefont::print() function, i get the quads but no characters
oh, and to see the quads i had to set outputcolor in fontfragmentshader to vColor aka. white

maybe i did just miss something? o.o'
Antoine (antoinevantilborgh@hotmail.com) on 13.06.2013 19:17:07
I also don't get any character out, but only the quads if I disable the glBlendFunc. Already any solution for this? thnx....
sigEleven on 02.08.2012 06:23:26
The colour multiplication in the supplied font2d.frag shader is backwards. :)

You need to change it to:
outputColor = (vTexColor.r, vTexColor.r, vTexColor.r, vTexColor.r)*vColor;

in order to be able to properly change font colour/alpha.

Cheers
Michal Bubnar (michalbb1@gmail.com) on 02.08.2012 08:45:32
spFont2D.useProgram() is in renderScene function
sigEleven (aidevelopment@gmail.com) on 02.08.2012 05:03:59
Still wrapping my head around this one, I'm a little confused by the ortho2D shaders... I never see them used (spFont2D.useProgram();) ... are they not in use at all in this example?
Jump to page:
1