« 水效果Ⅰ - 水池在OpenGL上设置字体和显示文字(下) »

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

任何一个DEMO、仿真项目、游戏,都少不了文字这种媒体。这不可不说是对图形视觉媒体的补充——我们还有一些无法超越文字来向观众表达的心事,或是补充说明,或是感悟,或是感激。—— ZwqXin.com

一般的文字不属于图形渲染部分,而属于用户界面部分,这在游戏引擎中看或许一目了然,但是在底层的图形渲染API——OPENGL或D3D中,文字的显示“并不是必须”,但它是多么深深地被需要着口牙。所以,把字体设置、文字显示作为一种图形学技术而非单纯的完全我属或他属,我是这么想的。(同样,拾取也算是这样吧?[乱弹OpenGL选择-拾取机制Ⅰ])

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

怎么表达文字呢?在OpenGL中,我们没有什么现成的东西可用,但确实有办法让我们“得到这种技术”。让我最记忆深刻的是NEHE的两三篇教程(貌似都是十几课吧),讲述的就是今天的这个主题。可以到NEHE网站或者在DANCINGWIND的中文翻译(见[搜集的优良OpenGL教程] )看看~。

而我所知道的这三种方法,前两种应该就是来自那里吧(记得~~),当时要努力完成课程DEMO,于是胡胡混混地就把相应的那两三课学了,而且把它的文字显示方法应用到自己的程序中(还经历了一段艰辛的探索史...连我当时的MyFont类也记录了这份小辛酸,现在看来,是因为当时的知识水平不够理解吧)。然后后来又一个课程DEMO,老师后来觉得我应该“写中文”,于是我又去探索中文字体显示方法了,并在一个开源DEMO中找到,分析代码段后就拿来主义了,至昨不曾好好考究——这就是我所知的第三种方法。

三种方法都是三步曲:在初始化的时候“创建字体”[Build],在渲染阶段“应用字体”(显示文字)[Print],在程序结束或不再需要文字显示的时候“销毁字体”[kill]。其中前两步比较重要,着重讨论讨论哈~

1. 常规的屏幕字体打印(NormalFont)

应用得比较广,大名鼎鼎的AZURE以前的DEMO就是用这个的。NEHE教程中作为首次出现的字体显示方法,介绍应该比较全面,大家想仔细了解的话请务必从上面网址进入学习(当然还有因为我理解不透不敢乱讲的缘由)。

  1.  /////////一般的英语字体打印
  2. void MyFont::BuildGLFont(int fontHeight)
  3. {
  4.    HDC   hDC =::GetDC(HWND_DESKTOP);    //////就是这里搞晕了半晚
  5.    
  6.    int tFontHeight = -1 * fontHeight;
  7.  
  8.    NormalFontBase = glGenLists(96);                         // Storage For 96 Characters
  9.    HFONT font = CreateFont( -tFontHeight,                   // Height Of Font
  10.                               0,                            // Width Of Font
  11.                               0,                            // Angle Of Escapement
  12.                               0,                            // Orientation Angle
  13.                               FW_BOLD,                      // Font Weight
  14.                               TRUE,                         // Italic
  15.                               FALSE,                        // Underline
  16.                               FALSE,                        // Strikeout
  17.                               ANSI_CHARSET,                 // Character Set Identifier
  18.                               OUT_TT_PRECIS,                // Output Precision
  19.                               CLIP_DEFAULT_PRECIS,          // Clipping Precision
  20.                               ANTIALIASED_QUALITY,          // Output Quality
  21.                               FF_DONTCARE|DEFAULT_PITCH,    // Family And Pitch
  22.                               "Georgia");                   // Font Name
  23.  
  24.     HFONT oldfont = (HFONT)SelectObject(hDC, font);         // Selects The Font We Want
  25.     
  26.     wglUseFontBitmaps(hDC, 32, 96, NormalFontBase);         // Builds 96 Characters Starting At Character 32
  27.     
  28.     SelectObject(hDC, oldfont);                         // Selects The Font We Want to return to
  29.     DeleteObject(font);                                 // Delete The Font
  30.     
  31.     SetBkMode(hDC,TRANSPARENT);   
  32.  
  33.     NormalFont = true; 
  34. }

其中bUild的时候首先用到的是GDI的CreateFont函数创建字体——这应该是比较常用的方法,设置了关于字体的一切并选入字体后,有一步重要的操作:wglUseFontBitmaps。

  1.  
  2. wglUseFontBitmap
  3. 为当前选中的GDI字体创建一组OpenGL显示列表位图字体 
  4. BOOL wglUseFontBitmap(HDC hDC, DWORD dwFirst, DWORD dwCount, DWORD dwListBase); 
  5.  
  6. 参数
  7. hDC
  8. 设备环境句柄
  9.  
  10. dwFirst
  11. 用于创建显示列表字体的第一个字符的ASCII值
  12.  
  13. dwCount
  14. 字符数
  15.  
  16. dwListBase
  17. 第一个显示列表的名称
  18.  
  19. 返回值
  20. 成功返回TRUE,否则返回FALSE 
  21.  

输入为DC,32,96以及创建的96个新显示列表的base列表。函数绘制从ASCII码为32-128的字符进入显示列表,依赖OPENGL显示列表的高速显示能力(直接从硬件拿存储区),可以方便进行字符的切换。

在Print过程中,调用glCallLists就能调动起这些列表了,但是怎么决定具体要“调动”哪些字母呢(96个之中)?

  1. void MyFont::PrintGLText(GLint x, GLint y, const char *string, ...) 
  2.     char  text[256];
  3.     va_list pArguments;
  4.  
  5.     if (string == NULL)
  6.         return;
  7.  
  8.     va_start(pArguments, string);
  9.     vsprintf(text, string, pArguments);
  10.     va_end(pArguments);
  11.  
  12.  
  13.     glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT |  GL_ENABLE_BIT | GL_LIGHTING_BIT);
  14.     glDisable(GL_DEPTH_TEST);
  15.     glDisable(GL_LIGHTING);
  16.     glDisable(GL_TEXTURE_2D);
  17.     glColor4f(mColor[0], mColor[1], mColor[2], mColor[3]);
  18.  
  19.     glWindowPos2i(x, y);
  20.  
  21.     glListBase(NormalFontBase - 32);
  22.     glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);
  23.     glPopAttrib();
  24. }

首先看函数形式——printf形式,若想有个详细了解,可到这里看看。简单来说,就是C时代的可变参数列。va_start - vsprintf - (va_arg)- va_end这套机制就是为了把可变参数列的内容,通过va_list (char*指针)一个一个从栈中取出来赋予他者——我们的glCallLists所要接受的所有“具体字符”,通过base为基础的索引快速寻觅而取得对应ASCII字符的字体信息(实际是位图字体),并依照期望使其形成为“具体字符串”印入屏幕。

另外着重介绍的是我所添加的两个优化——它们贯穿三种文字显示方法之中。

其一是glPushAttrib,它与glPopAttrib配合,保证了其之间的OPENGL状态设置的独立性,使其不影响该代码逻辑的前后的具体渲染状态。当然参数取GL_ALL_ATTRIB_BITS是保险点,但只要你弄清楚自己的需要,像上面这样给予特定的状态作为参数效率会更高。恩,颜色,光照,深度测试,混合……选择与当前方法最匹配的状态而没有对状态机的后顾之忧,如同文字本身一样——作为对象完全独立于图形渲染“模块”。

另一是glWindowPos2i(x, y),按《OpenGL编程指南》第8章所说,它取代我们以前用的glRasterPos2i,不再让作为描绘对象的物体承受模型-视图-投影变换之苦[乱弹OpenGL中的矩阵变换(上)] ,而是直接独立到OPENGL世界的出口——屏幕坐标系,如GDI般用窗口坐标(根据屏幕像素数)来描述文字的起点位置。这同样是赋予文字的独立性,而且意义重大——可知道,当时我用glRasterPos2i多么狼狈,好难才让文字不在场景“里面”乱窜。

在具体应用中,在初始化调用BuildGLFont.,在渲染阶段调用PrintGLText。譬如:

  1. //CMAINFRAME
  2. MyFont mFont;
  3.  
  4. //初始化:
  5. mFont.BuildGLFont(25);//25是字体字高,控制字体大小
  6.  
  7. //渲染阶段(RenderGLScene)
  8.  
  9. mFont.PrintGLText(530, 710, "http://www.Zwqxin.com - My 3D Graphics");
  10.  
  11. //将在坐标X = 530, Y =710位置开始绘制文字。对1024*768大小的渲染窗口中,即在右上角

注意,OpenGL窗口坐标系的原点在窗口的左下角,横坐标为X,竖坐标为Y,最大值在右上角。(同见[乱弹OpenGL选择-拾取机制Ⅱ] )

www.zwqxin.com 在Opengl上设置字体 显示文字
浏览一下效果,第一行就是了,因为我默认用的是Georgia字体(应该一般人电脑都有),所以很漂亮

接下来会谈及其余两种方法,并比较之。真正的纹理文字是怎样弄的呢?怎样让中文字体乖乖显示?什么时候用哪种方法?我集成的MyFont类是怎样个怪样?听下回分解:

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

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

  • quote 1.流浪地球
  • 你好,关注你的博客很久了,学到很多东西,非常感谢!
    想和你讨论一下这篇文章va_list的用法,PrintGLText函数中的string参数,应该是格式化字符串,在它的后面才应该是要出的字符串内容吧,按照你的调用方法pArguments指针应该是指向空的,为什么会有输出呢?请指教一二
    zwqxin 于 2011-3-10 23:11:38 回复
    貌似不是这么理解的吧,你再想想?譬如PrintGLText(0, 2, "Your ID is %d", nID)...或者是我这边用得不对那么就请多多指教了。
  • 2011-3-10 19:58:36 回复该留言

发表评论:

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

IE下本页面显示有问题?

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

日历

Search

网站分类

最新评论及回复

最近发表

Powered By Z-Blog 1.8 Walle Build 100427

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