« 在OpenGL上设置字体和显示文字(上)水效果Ⅱ - 涟漪 »

在OpenGL上设置字体和显示文字(下)

本篇紧随上篇,继续说一下鄙人所了解的在OpenGL进行文字显示的方法,也方便不知从哪个次元来到这里的学习者提供一点这方面的信息。上一篇见:[在OpenGL上设置字体和显示文字(上)] ——ZwqXin.com

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/opengl/opengl-font-setting-showing-2.html

2. 纹理字体

最近接触Irrlicht引擎,里面的GUI模块涉及的字体设置就是用了这种纹理字体的方法。事实上,上篇的方法最后多少了涉及到了位图字体,但是这里所说的,是直接从一张“写着各个英文字符和其他常用字符”的纹理上,按对此纹理上的图案“结构”的先验知识而获取字符对应的“纹理部分”,显示到屏幕上。

换句话来说,这就是真正的纹理贴图了,因此必须结合一张特定设计的“字体纹理”。要显示一个字符,需要你绘制一个一定大小的矩形(这个长度值相当于之前的字体高度——它控制最后出来的文字的大小),然后找到你所需字符在该纹理上的纹理坐标,作为索引检索出纹理上的对应字符部分的小纹理,贴到该矩形上。

  1.  
  2. //////////从字体集纹理中取出的字符
  3. void MyFont::BuildTextureFont(GLuint fonttex, int fontHeight, int screenWidth, int screenHeight)
  4. {
  5.     TextureFontFont = fonttex;
  6.  
  7.     float   cx;                                         // Holds Our X Character Coord
  8.     float   cy;                                         // Holds Our Y Character Coord
  9.  
  10.     glEnable(GL_TEXTURE_2D);
  11.  
  12.     TextureFontBase = glGenLists(256);                  // Creating 256 Display Lists
  13.  
  14.     glBindTexture(GL_TEXTURE_2D, TextureFontFont);      // Select Our Font Texture
  15.  
  16.     for(int loop=0; loop<256; loop++)                    // Loop Through All 256 Lists
  17.     {
  18.         cx=float(loop%16)/16.0f;                        // X Position Of Current Character
  19.         cy=float(loop/16)/16.0f;                        // Y Position Of Current Character
  20.  
  21.         glNewList(TextureFontBase + loop, GL_COMPILE);  // Start Building A List
  22.             glBegin(GL_QUADS);                          // Use A Quad For Each Character
  23.                 glTexCoord2f(cx,1-cy-0.0625f);          // Texture Coord (Bottom Left)
  24.                 glVertex2i(0,0);                        // Vertex Coord (Bottom Left)
  25.                 glTexCoord2f(cx+0.0625f,1-cy-0.0625f);  // Texture Coord (Bottom Right)
  26.                 glVertex2i(fontHeight, 0);              // Vertex Coord (Bottom Right)
  27.                 glTexCoord2f(cx+0.0625f,1-cy);          // Texture Coord (Top Right)
  28.                 glVertex2i(fontHeight,fontHeight);      // Vertex Coord (Top Right)
  29.                 glTexCoord2f(cx,1-cy);                  // Texture Coord (Top Left)
  30.                 glVertex2i(0, fontHeight);              // Vertex Coord (Top Left)
  31.             glEnd();                                    // Done Building Our Quad (Character)
  32.             glTranslated(fontHeight,0,0);               // Move To The Right Of The Character
  33.         glEndList();                                    // Done Building The Display List
  34.     }                                                   // Loop Until All 256 Are Built
  35.  
  36.     glDisable(GL_TEXTURE_2D);
  37.  
  38.     ScreenWidth = screenWidth;
  39.     ScreenHeight = screenHeight;
  40.  
  41.     TextureFont = true;
  42. }

这在BUILD阶段就做的必要是没有的,但是一次过导入纹理中256个字符,生成256个小纹理,并在每帧都选择对应的纹理索引检索纹理,且每个字符如此——这样的重复不变而低效的事情,还是由OpenGL的显示列表技术来做比较好——一次过在初始化时做好,放入显示列表。在 PRINT阶段只要调用显示列表就好。关于显示列表,eastcowboy在他的OPENGL入门学习中详细提及过,有兴趣的朋友可看看他的文章:OpenGL入门学习——第八课-使用显示列表 。比起最时兴的VBO,显示列表在重复劳动上还是有一定优势的额~

最后的结果是按glTranslated进行排列的256个具有纹理字符的矩形。为什么要得到渲染窗口的大小呢?因为这些矩形要保证在屏幕最前方,就应该让它突破矩阵变换啊。在上篇[在OpenGL上设置字体和显示文字(上)] 也提过glWindowPos2i(x, y),但这里对实际的矩形它不是很适用。因此我还是选择原来的路子,来给予文字以独立于图形的属性——在屏幕最前而位置只由屏幕坐标XY决定。方法就是,或许很多人也熟悉的,glOrtho正交投影变换。因此,屏幕大小(这里指渲染窗口的大小)是需要的。

  1.  
  2. void MyFont::PrintTextureText(GLint x, GLint y, char *string, int TextureSet)
  3. {
  4.     if (TextureSet > 1)TextureSet = 1;
  5.     if (TextureSet < 0)TextureSet = 0;
  6.  
  7.     glPushAttrib(GL_CURRENT_BIT | GL_LIGHTING_BIT | GL_ENABLE_BIT|  GL_LIST_BIT);
  8.     glDisable(GL_LIGHTING); 
  9.  
  10.     glEnable(GL_TEXTURE_2D);
  11.     glBindTexture(GL_TEXTURE_2D, TextureFontFont);      // Select Our Font Texture
  12.  
  13.     glDisable(GL_DEPTH_TEST);                           // Disables Depth Testing
  14.     glEnable(GL_BLEND);
  15.     glBlendFunc(GL_SRC_ALPHA,GL_ONE);
  16.  
  17.     glColor4f(mColor[0], mColor[1], mColor[2], mColor[3]);  
  18.  
  19.     glMatrixMode(GL_PROJECTION);                        // Select The Projection Matrix
  20.     glPushMatrix();                                     // Store The Projection Matrix
  21.     
  22.     glLoadIdentity();                                   // Reset The Projection Matrix
  23.     glOrtho(0,ScreenWidth,0,ScreenHeight,-1,1);         // Set Up An Ortho Screen
  24.  
  25.     glMatrixMode(GL_MODELVIEW);                         // Select The Modelview Matrix
  26.     glPushMatrix();                                     // Store The Modelview Matrix
  27.     
  28.     glLoadIdentity();                                   // Reset The Modelview Matrix
  29.     glTranslated(x,y,0);                                // Position The Text (0,0 - Bottom Left)
  30.     
  31.     glListBase(TextureFontBase-32 + (128 * TextureSet));// Choose The Font Set (0 or 1)
  32.     glCallLists(strlen(string),GL_UNSIGNED_BYTE,string);// Write The Text To The Screen
  33.     
  34.     glMatrixMode(GL_PROJECTION);                        // Select The Projection Matrix
  35.     glPopMatrix();                                      // Restore The Old Projection Matrix
  36.     glMatrixMode(GL_MODELVIEW);                         // Select The Modelview Matrix
  37.     glPopMatrix();                                      // Restore The Old Projection Matrix
  38.     
  39.     glDisable(GL_BLEND);
  40.     glEnable(GL_DEPTH_TEST);                            // Enables Depth Testing
  41.     glDisable(GL_TEXTURE_2D);
  42.  
  43.     glPopAttrib();
  44. }

TextureSet选择字符集,因为字符纹理里面每个字符对应两种字体,用0/1选择。glPushAttrib的作用前面说过了。之后我们用glPushMatrix保存当前的投影/模型视图矩阵,在弄好一切好就马上切换回去。弄什么呢?新的坐标变换。文字(glCallLists绘制)是脱离我们OPENGL图形世界的,因此单独给予它一套变换——在屏幕最前方且不受视觉透视影响。glOrtho(0,ScreenWidth,0,ScreenHeight,-1,1)这样的投影变换设置配合glTranslated这样的模型变换就能满足我的要求了。因为创建的正交投影是与渲染窗口大小一致的,所以glTranslated的X,Y的单位与像素pixel对应——这不也就是GDI那种设置方式了么哈。

具体应用:

  1. //CMAINFRAME 
  2. MyFont mFont; 
  3.   
  4. //初始化: 
  5. mFont.BuildTextureFont(FontTextureID, 25, VB_WIDTH, VB_HEIGHT);
  6. //25是字体字高,控制字体大小 ,FontTextureID字体纹理的纹理ID
  7.   
  8. //渲染阶段(RenderGLScene) 
  9.   
  10. mFont.PrintTextureText(790,645,"Font Test",1);
  11.   
  12. //将在坐标X = 790, Y =645位置开始绘制文字。对1024*768大小的渲染窗口中,
  13. //即在右上角偏下

3.GDI字体

事实上第一种方法也可以说是GDI的方法,但是这里更明显,结合GDI字体创建和位图创建。而且它不涉及显示列表。它是在渲染时动态创建包容文字的设备相关位图(具体可参考我的一篇旧文[认识HBITMAP与Bmp操作(整内存拷贝版)] ),再把此位图定位而成的。因此,它是很慢的~(抽)。但是为了中文字体,一点点的性能损失算得了什么呢。

  1. ///////////GDI, 位图字体 可设中文字体
  2. void MyFont::BuildGDIFont(LPCTSTR lpszFacename, int fontWeights, int fontHeight)
  3. {
  4.   int tfontHeight = -1 * fontHeight;
  5.       hGDIFont = CreateFont(tfontHeight, 0, 0, 0, fontWeights, 0, 0, 0, GB2312_CHARSET,
  6.                         0, 0, 0, FF_MODERN, lpszFacename);
  7.       GDIFont = true;
  8. }

BUILD部分就不多说了,就是CreateFont~~fontWeights表示字体的重量,在0~900内可选,直接设置FW_BOLD之类的也行,这里给出这个参数不过是多给它一些可控性而已。首参数是字体,譬如可以是"黑体","宋体"等等,也可以是英文字体。当然这里应该是取你电脑的字库里的字体,所以考虑程序的通用性,别搞些另类的字体或只有自己有的字体~GB2312_CHARSET你该知道啦哈。

  1. void MyFont::PrintfChtext(int x, int y, LPCTSTR lpszText)
  2. {
  3.       CBitmap bitmap;
  4.       BITMAP bm;
  5.       SIZE size;
  6.       
  7.       HDC MDC = ::CreateCompatibleDC(NULL);
  8.       SelectObject(MDC, hGDIFont);
  9.       
  10.       ::GetTextExtentPoint32(MDC,lpszText,strlen(lpszText),&size);
  11.  
  12.       bitmap.CreateBitmap(size.cx, size.cy, 1, 1, NULL);
  13.  
  14.       HBITMAP oldBmp=(HBITMAP)SelectObject(MDC,bitmap);
  15.  
  16.       SetBkColor  (MDC, RGB(0,     0,   0));
  17.       SetTextColor(MDC, RGB(255, 255, 255));
  18.  
  19.       TextOut(MDC, 0, 0, lpszText, strlen(lpszText));
  20.  
  21.       bitmap.GetBitmap(&bm);
  22.       size.cx = (bm.bmWidth + 31) & (~31);
  23.  
  24.       int bufsize = size.cy * size.cx;
  25.  
  26.       struct {  
  27.               BITMAPINFOHEADER bih;
  28.               RGBQUAD col[2];
  29.              }bic; 
  30.  
  31.       BITMAPINFO *binf = (BITMAPINFO *)&bic; 
  32.       binf->bmiHeader.biSize     = sizeof(binf->bmiHeader);
  33.       binf->bmiHeader.biWidth    = bm.bmWidth;
  34.       binf->bmiHeader.biHeight   = bm.bmHeight;
  35.       binf->bmiHeader.biPlanes   = 1;   
  36.       binf->bmiHeader.biBitCount = 1;
  37.       binf->bmiHeader.biCompression = BI_RGB;
  38.       binf->bmiHeader.biSizeImage   = bufsize; 
  39.  
  40.       UCHAR* Bits = new UCHAR[bufsize]; 
  41.       ::GetDIBits(MDC,bitmap, 0, bm.bmHeight, Bits, binf, DIB_RGB_COLORS); 
  42.                                       
  43.       glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
  44.  
  45.       //glRasterPos2i(x, y); 
  46.       glWindowPos2i(x, y);
  47.       glBitmap(size.cx, size.cy, 0, 0, 0, 0, Bits); 
  48.  
  49.       delete Bits;    
  50.       SelectObject(MDC, oldBmp);  
  51.       ::DeleteDC(MDC);
  52. }
  53.  
  54. void MyFont::PrintGDIText(GLint x, GLint y, CString str)
  55. {
  56.     glLoadIdentity();
  57.     glPushAttrib(GL_CURRENT_BIT | GL_LIGHTING_BIT);
  58.  
  59.     glDisable(GL_TEXTURE_2D); 
  60.     glDisable(GL_LIGHTING); 
  61.  
  62.     glColor4f(mColor[0], mColor[1], mColor[2], mColor[3]);  
  63.  
  64.     PrintfChtext (x, y, str); 
  65.     
  66.     glPopAttrib();
  67. }

glPushAttrib和glWindowPos2i的意义不多说了。看主体函数PrintfChtext。几个GDI函数:

  • GetTextExtentPoint32用当前所选字体来计算字符串尺寸,按逻辑单位计算的高和宽都没有考虑裁剪取的情况。
  • CreateBitmap创建单位色位图。

函数所做的是依据字符串大小建立一张单色位图,把该位图信息存入UCHAR数组Bits内。类似的操作在[认识HBITMAP与Bmp操作(整内存拷贝版)] 也谈过,所以细节部分譬如为什么要取宽度位8倍数等等就略过。重点是要把数组Bits交给谁。恩,glBitmap函数道出了一切。

OpenGL里除了几何对象(点、线、多边形)和纹理图像对象外,还有一种不常用的对象,位图Bitmap。一般来说这样的位图是灰度的,也就是位图矩块内每个像素只有1BIT的信息——0 OR 1。这对文字来说正好,因为文字就是黑色(/透明)背景中的白色部分,黑白分明。颜色可以在绘制位图后用glColor设置嘛。glBitmap函数就是用来根据数据显示位图的。而该位图内可以说是被打印了GDI文字上去(用TextOut),于是最后在屏幕上的还是位图文字,且可以用glWindowPos2i调节起始点位置。就这样,完成的GDI文字从基于DC的GDI环境,显示到基于RC的OPenGL环境上。可喜可贺。

具体应用:

  1.  
  2. //CMAINFRAME  
  3. MyFont mFont;  
  4.    
  5. //初始化:  
  6. mFont.BuildGDIFont("宋体", FW_NORMAL, 25);
  7. //25是字体字高,控制字体大小 ;FW_NORMAL常态重的字体
  8.    
  9. //渲染阶段(RenderGLScene)  
  10.    
  11. mFont.PrintGDIText(840, 675, "抖动波演示[D]");
  12.    
  13. //将在坐标X = 840, Y =675位置开始绘制文字。对1024*768大小的渲染窗口中, 
  14. //即在右上角偏下

于是,我所知道的就这么多了。阁下若有更好的建议,........求指教!

效果(分别上到下对应方法1,3,2):

www.zwqxin.com 在Opengl上设置字体 显示文字

 最后是MyFont类了,加上纹理字体的那张特制字体纹理(2011.2.结构更新ZWFont)。阁下若有更好的改进,........求指教!

ZWFont类byZwqXin.zip

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/opengl/opengl-font-setting-showing-2.html

  • quote 1.wuwenye
  • FreeType库。

    暴雪都选用的库,不用多说了吧,呵呵。个人感觉并不比GDI复杂,最大的好处是,不用安装字体,只要有TTF文件就可以了。
  • 2010-9-1 21:43:06 回复该留言
  • quote 3.hailengc
  • http://hlcao.com
  • 你好,接触opgl时间不长,最近做了小东西,头痛字体呢。楼主的文章写得很好,类也写得很好~~
    借鉴了楼主的类哈,谢过谢过 呵呵!
  • 2011-5-24 21:41:54 回复该留言

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

IE下本页面显示有问题?

→点击地址栏右侧【兼容视图】←

日历

Search

网站分类

最新评论及回复

最近发表

Powered By Z-Blog 1.8 Walle Build 100427

Copyright 2008-2013 ZwqXin. All Rights Reserved. Theme edited from ipati.