任何一个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教程中作为首次出现的字体显示方法,介绍应该比较全面,大家想仔细了解的话请务必从上面网址进入学习(当然还有因为我理解不透不敢乱讲的缘由)。
- /////////一般的英语字体打印
- void MyFont::BuildGLFont(int fontHeight)
- {
- HDC hDC =::GetDC(HWND_DESKTOP); //////就是这里搞晕了半晚
- int tFontHeight = -1 * fontHeight;
- NormalFontBase = glGenLists(96); // Storage For 96 Characters
- HFONT font = CreateFont( -tFontHeight, // Height Of Font
- 0, // Width Of Font
- 0, // Angle Of Escapement
- 0, // Orientation Angle
- FW_BOLD, // Font Weight
- TRUE, // Italic
- FALSE, // Underline
- FALSE, // Strikeout
- ANSI_CHARSET, // Character Set Identifier
- OUT_TT_PRECIS, // Output Precision
- CLIP_DEFAULT_PRECIS, // Clipping Precision
- ANTIALIASED_QUALITY, // Output Quality
- FF_DONTCARE|DEFAULT_PITCH, // Family And Pitch
- "Georgia"); // Font Name
- HFONT oldfont = (HFONT)SelectObject(hDC, font); // Selects The Font We Want
- wglUseFontBitmaps(hDC, 32, 96, NormalFontBase); // Builds 96 Characters Starting At Character 32
- SelectObject(hDC, oldfont); // Selects The Font We Want to return to
- DeleteObject(font); // Delete The Font
- SetBkMode(hDC,TRANSPARENT);
- NormalFont = true;
- }
其中bUild的时候首先用到的是GDI的CreateFont函数创建字体——这应该是比较常用的方法,设置了关于字体的一切并选入字体后,有一步重要的操作:wglUseFontBitmaps。
- wglUseFontBitmap
- 为当前选中的GDI字体创建一组OpenGL显示列表位图字体
- BOOL wglUseFontBitmap(HDC hDC, DWORD dwFirst, DWORD dwCount, DWORD dwListBase);
- 参数
- hDC
- 设备环境句柄
- dwFirst
- 用于创建显示列表字体的第一个字符的ASCII值
- dwCount
- 字符数
- dwListBase
- 第一个显示列表的名称
- 返回值
- 成功返回TRUE,否则返回FALSE
输入为DC,32,96以及创建的96个新显示列表的base列表。函数绘制从ASCII码为32-128的字符进入显示列表,依赖OPENGL显示列表的高速显示能力(直接从硬件拿存储区),可以方便进行字符的切换。
在Print过程中,调用glCallLists就能调动起这些列表了,但是怎么决定具体要“调动”哪些字母呢(96个之中)?
- void MyFont::PrintGLText(GLint x, GLint y, const char *string, ...)
- char text[256];
- va_list pArguments;
- if (string == NULL)
- return;
- va_start(pArguments, string);
- vsprintf(text, string, pArguments);
- va_end(pArguments);
- glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT | GL_LIGHTING_BIT);
- glDisable(GL_DEPTH_TEST);
- glDisable(GL_LIGHTING);
- glDisable(GL_TEXTURE_2D);
- glColor4f(mColor[0], mColor[1], mColor[2], mColor[3]);
- glWindowPos2i(x, y);
- glListBase(NormalFontBase - 32);
- glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);
- glPopAttrib();
- }
首先看函数形式——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。譬如:
- //CMAINFRAME
- MyFont mFont;
- //初始化:
- mFont.BuildGLFont(25);//25是字体字高,控制字体大小
- //渲染阶段(RenderGLScene)
- mFont.PrintGLText(530, 710, "http://www.Zwqxin.com - My 3D Graphics");
- //将在坐标X = 530, Y =710位置开始绘制文字。对1024*768大小的渲染窗口中,即在右上角
注意,OpenGL窗口坐标系的原点在窗口的左下角,横坐标为X,竖坐标为Y,最大值在右上角。(同见[乱弹OpenGL选择-拾取机制Ⅱ] )
浏览一下效果,第一行就是了,因为我默认用的是Georgia字体(应该一般人电脑都有),所以很漂亮
接下来会谈及其余两种方法,并比较之。真正的纹理文字是怎样弄的呢?怎样让中文字体乖乖显示?什么时候用哪种方法?我集成的MyFont类是怎样个怪样?听下回分解:
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/opengl/opengl-font-setting-showing.html