c++opengl三维图形中显⽰⽂字_(旧)在OpenGL上设置字体
和显⽰⽂字
任何⼀个DEMO、仿真项⽬、游戏,都少不了⽂字这种媒体。这不可不说是对图形视觉媒体的补充——我们还有⼀些⽆法超越⽂字来向观众表达的⼼事,或是补充说明,或是感悟,或是感激。—— ZwqXin
<
⼀般的⽂字不属于图形渲染部分,⽽属于⽤户界⾯部分,这在游戏引擎中看或许⼀⽬了然,但是在底层的图形渲染API——OPENGL或D3D 中,⽂字的显⽰“并不是必须”,但它是多么深深地被需要着⼝⽛。所以,把字体设置、⽂字显⽰作为⼀种图形学技术⽽⾮单纯的完全我属
乱弹OpenGL选择-拾取机制Ⅰ])
或他属,我是这么想的。(同样,拾取也算是这样吧?[乱弹OpenGL选择-拾取机制Ⅰ
怎么表达⽂字呢?在OpenGL中,我们没有什么现成的东西可⽤,但确实有办法让我们“得到这种技术”。让我最记忆深刻的是NEHE的两
搜集的优良
三篇教程(貌似都是⼗⼏课吧),讲述的就是今天的这个主题。可以到NEHE⽹站
NEHE⽹站或者在DANCINGWIND的中⽂翻译(见[搜集的优良OpenGL教程] )看看~。
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. int tFontHeight = -1 * fontHeight;
6. NormalFontBa = glGenLists(96); // Storage For 96 Characters
7. HFONT font = CreateFont( -tFontHeight, // Height Of Font
8. 0, // Width Of Font
9. 0, // Angle Of Escapement
10. 0, // Orientation Angle
11. FW_BOLD, // Font Weight
12. TRUE, // Italic
13. FALSE, // Underline
14. FALSE, // Strikeout
15. ANSI_CHARSET, // Character Set Identifier
16. OUT_TT_PRECIS, // Output Precision
17. CLIP_DEFAULT_PRECIS, // Clipping Precision
18. ANTIALIASED_QUALITY, // Output Quality
19. FF_DONTCARE|DEFAULT_PITCH, // Family And Pitch
20. "Georgia"); // Font Name
21. HFONT oldfont = (HFONT)SelectObject(hDC, font); // Selects The Font We Want
22. wglUFontBitmaps(hDC, 32, 96, NormalFontBa); // Builds 96 Characters Starting At Character 32
嵇康广陵散的故事23. SelectObject(hDC, oldfont); // Selects The Font We Want to return to
24. DeleteObject(font); // Delete The Font
25. SetBkMode(hDC,TRANSPARENT);
26. NormalFont = true;
27. }
其中bUild的时候⾸先⽤到的是GDI的CreateFont函数
CreateFont函数创建字体——这应该是⽐较常⽤的⽅法,设置了关于字体的⼀切并选⼊字体后,有⼀步重要的操作:wglUFontBitmaps。
1. wglUFontBitmap
2. 为当前选中的GDI字体创建⼀组OpenGL显⽰列表位图字体
3. BOOL wglUFontBitmap(HDC hDC, DWORD dwFirst, DWORD dwCount, DWORD dwListBa);
4. 参数
5. hDC
6. 设备环境句柄
7. dwFirst
8. ⽤于创建显⽰列表字体的第⼀个字符的ASCII值
9. dwCount
10. 字符数
11. dwListBa
12. 第⼀个显⽰列表的名称
13. 返回值
14. 成功返回TRUE,否则返回FALSE小班体育游戏
输⼊为DC,32,96以及创建的96个新显⽰列表的ba列表。函数绘制从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. if (string == NULL)
5. return;
背的词语6. va_start(pArguments, string);
7. vsprintf(text, string, pArguments);
8. va_end(pArguments);
9. glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT | GL_LIGHTING_BIT);
10. glDisable(GL_DEPTH_TEST);
11. glDisable(GL_LIGHTING);
12. glDisable(GL_TEXTURE_2D);
13. glColor4f(mColor[0], mColor[1], mColor[2], mColor[3]);
14. glWindowPos2i(x, y);
15. glListBa(NormalFontBa - 32);
16. glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);
17. glPopAttrib();
18. }
这⾥看看。简单来说,就是C时代的可变参数列。va_start - vsprintf -
⾸先看函数形式——printf形式,若想有个详细了解,可到这⾥看看
(va_arg)- va_end这套机制就是为了把可变参数列的内容,通过va_list (char*指针)⼀个⼀个从栈中取出来赋予他者——我们的glCallLists所要接受的所有“具体字符”,通过ba为基础的索引快速寻觅⽽取得对应ASCII字符的字体信息(实际是位图字体),并依照期望使其形成为“具体字符串”印⼊屏幕。
另外着重介绍的是我所添加的两个优化——它们贯穿三种⽂字显⽰⽅法之中。
其⼀是glPushAttrib,它与glPopAttrib配合,保证了其之间的OPENGL状态设置的独⽴性,使其不影响
该代码逻辑的前后的具体渲染状GL_ALL_ATTRIB_BITS是保险点,但只要你弄清楚⾃⼰的需要,像上⾯这样给予特定的状态作为参数效率会更⾼。恩,态。当然参数取GL_ALL_ATTRIB_BITS
颜⾊,光照,深度测试,混合……选择与当前⽅法最匹配的状态⽽没有对状态机的后顾之忧,如同⽂字本⾝⼀样——作为对象完全独⽴于图形渲染“模块”。
另⼀是glWindowPos2i(x, y),按《OpenGL编程指南》第8章
《OpenGL编程指南》第8章所说,它取代我们以前⽤的glRasterPos2i,不再让作为描绘对象的物体乱弹OpenGL中的矩阵变换(上)] ,⽽是直接独⽴到OPENGL世界的出⼝——屏幕坐标系,如GDI般⽤窗⼝承受模型-视图-投影变换之苦[乱弹OpenGL中的矩阵变换(上)
坐标(根据屏幕像素数)来描述⽂字的起点位置。这同样是赋予⽂字的独⽴性,⽽且意义重⼤——可知道,当时我⽤glRasterPos2i多么狼狈,好难才让⽂字不在场景“⾥⾯”乱窜。
在具体应⽤中,在初始化调⽤BuildGLFont.,在渲染阶段调⽤PrintGLText。譬如:
1. //CMAINFRAME
2. MyFont mFont;
3. //初始化:
4. mFont.BuildGLFont(25);//25是字体字⾼,控制字体⼤⼩
5. //渲染阶段(RenderGLScene)
6. mFont.PrintGLText(530, 710, " - My 3D Graphics");
7. //将在坐标X = 530, Y =710位置开始绘制⽂字。对1024*768⼤⼩的渲染窗⼝中,即在右上⾓
乱弹OpenGL选择-拾取机制Ⅱ]注意,OpenGL窗⼝坐标系的原点在窗⼝的左下⾓,横坐标为X,竖坐标为Y,最⼤值在右上⾓。(同见[乱弹OpenGL选择-拾取机制Ⅱ)
浏览⼀下效果,第⼀⾏就是了,因为我默认⽤的是Georgia字体(应该⼀般⼈电脑都有),所以很漂亮
接下来会谈及其余两种⽅法,并⽐较之。真正的纹理⽂字是怎样弄的呢?怎样让中⽂字体乖乖显⽰?什么时候⽤哪种⽅法?我集成的MyFont类是怎样个怪样?听下回分解:
在OpenGL上设置字体和显⽰⽂字(下)w
本篇紧随上篇,继续说⼀下鄙⼈所了解的在OpenGL进⾏⽂字显⽰的⽅法,也⽅便不知从哪个次元来到这⾥的学习者提供⼀点这⽅⾯的信
<节约用水的英语
息。上⼀篇见:[在OpenGL上设置字体和显⽰⽂字(上)
在OpenGL上设置字体和显⽰⽂字(上)] ——ZwqXin
2. 纹理字体
最近接触Irrlicht引擎,⾥⾯的GUI模块涉及的字体设置就是⽤了这种纹理字体的⽅法。事实上,上篇的⽅法最后多少了涉及到了位图字体,但是这⾥所说的,是直接从⼀张“写着各个英⽂字符和其他常⽤字符”的纹理上,按对此纹理上的图案“结构”的先验知识⽽获取字符对应的“纹理部分”,显⽰到屏幕上。
换句话来说,这就是真正的纹理贴图了,因此必须结合⼀张特定设计的“字体纹理”。要显⽰⼀个字符,需要你绘制⼀个⼀定⼤⼩的矩形(这个长度值相当于之前的字体⾼度——它控制最后出来的⽂字的⼤⼩),然后找到你所需字符在该纹理上的纹理坐标,作为索引检索出纹理上的对应字符部分的⼩纹理,贴到该矩形上。
1. //从字体集纹理中取出的字符
2. void MyFont::BuildTextureFont(GLuint fonttex, int fontHeight, int screenWidth, int screenHeight)
3. {
4. TextureFontFont = fonttex;
5. float cx; // Holds Our X Character Coord
6. float cy; // Holds Our Y Character Coord
7. glEnable(GL_TEXTURE_2D);
8. TextureFontBa = glGenLists(256); // Creating 256 Display Lists
9. glBindTexture(GL_TEXTURE_2D, TextureFontFont); // Select Our Font Texture
10. for(int loop=0; loop<256; loop++) // Loop Through All 256 Lists
11. {
12. cx=float(loop%16)/16.0f; // X Position Of Current Character
13. cy=float(loop/16)/16.0f; // Y Position Of Current Character
14. glNewList(TextureFontBa + loop, GL_COMPILE); // Start Building A List
15. glBegin(GL_QUADS); // U A Quad For Each Character
16. glTexCoord2f(cx,1-cy-0.0625f); // Texture Coord (Bottom Left)
17. glVertex2i(0,0); // Vertex Coord (Bottom Left)
18. glTexCoord2f(cx+0.0625f,1-cy-0.0625f); // Texture Coord (Bottom Right)
19. glVertex2i(fontHeight, 0); // Vertex Coord (Bottom Right)
20. glTexCoord2f(cx+0.0625f,1-cy); // Texture Coord (Top Right)
21. glVertex2i(fontHeight,fontHeight); // Vertex Coord (Top Right)
22. glTexCoord2f(cx,1-cy); // Texture Coord (Top Left)
23. glVertex2i(0, fontHeight); // Vertex Coord (Top Left)
24. glEnd(); // Done Building Our Quad (Character)
25. glTranslated(fontHeight,0,0); // Move To The Right Of The Character
26. glEndList(); // Done Building The Display List
27. } // Loop Until All 256 Are Built
28. glDisable(GL_TEXTURE_2D);
29. ScreenWidth = screenWidth;
30. ScreenHeight = screenHeight;
31. TextureFont = true;
32. }
这在BUILD阶段就做的必要是没有的,但是⼀次过导⼊纹理中256个字符,⽣成256个⼩纹理,并在每帧都选择对应的纹理索引检索纹理,且每个字符如此——这样的重复不变⽽低效的事情,还是由OpenGL的显⽰列表技术来做⽐较好——⼀次过在初始化时做好,放⼊显⽰列表。在 PRINT阶段只要调⽤显⽰列表就好。关于显⽰列表,eastcowboy在他的OPENGL⼊门学习中详细提及过,有兴趣的朋友可看看他OpenGL⼊门学习——第⼋课-使⽤显⽰列表 。⽐起最时兴的VBO,显⽰列表在重复劳动上还是有⼀定优势的额~
的⽂章:OpenGL⼊门学习——第⼋课-使⽤显⽰列表
最后的结果是按glTranslated进⾏排列的256个具有纹理字符的矩形。为什么要得到渲染窗⼝的⼤⼩呢?因为这些矩形要保证在屏幕最前
轴心时代在OpenGL上设置字体和显⽰⽂字(上)] 也提过glWindowPos2i(x, y),但这⾥对实际的矩形它⽅,就应该让它突破矩阵变换啊。在上篇[在OpenGL上设置字体和显⽰⽂字(上)
不是很适⽤。因此我还是选择原来的路⼦,来给予⽂字以独⽴于图形的属性——在屏幕最前⽽位置只
由屏幕坐标XY决定。⽅法就是,或许很多⼈也熟悉的,glOrtho正交投影变换。因此,屏幕⼤⼩(这⾥指渲染窗⼝的⼤⼩)是需要的。
1. void MyFont::PrintTextureText(GLint x, GLint y, char *string, int TextureSet)
2. {
3. if (TextureSet > 1)TextureSet = 1;
4. if (TextureSet < 0)TextureSet = 0;
5. glPushAttrib(GL_CURRENT_BIT | GL_LIGHTING_BIT | GL_ENABLE_BIT| GL_LIST_BIT);
6. glDisable(GL_LIGHTING);
7. glEnable(GL_TEXTURE_2D);
8. glBindTexture(GL_TEXTURE_2D, TextureFontFont); // Select Our Font Texture
9. glDisable(GL_DEPTH_TEST); // Disables Depth Testing
10. glEnable(GL_BLEND);宣传信息
防疫科普知识
11. glBlendFunc(GL_SRC_ALPHA,GL_ONE);
12. glColor4f(mColor[0], mColor[1], mColor[2], mColor[3]);
13. glMatrixMode(GL_PROJECTION); // Select The Projection Matrix朝霞
14. glPushMatrix(); // Store The Projection Matrix
15. glLoadIdentity(); // Ret The Projection Matrix
16. glOrtho(0,ScreenWidth,0,ScreenHeight,-1,1); // Set Up An Ortho Screen
17. glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
18. glPushMatrix(); // Store The Modelview Matrix
19. glLoadIdentity(); // Ret The Modelview Matrix
20. glTranslated(x,y,0); // Position The Text (0,0 - Bottom Left)
21. glListBa(TextureFontBa-32 + (128 * TextureSet));// Choo The Font Set (0 or 1)
22. glCallLists(strlen(string),GL_UNSIGNED_BYTE,string);// Write The Text To The Screen
23. glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
24. glPopMatrix(); // Restore The Old Projection Matrix
25. glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
26. glPopMatrix(); // Restore The Old Projection Matrix
27. glDisable(GL_BLEND);
28. glEnable(GL_DEPTH_TEST); // Enables Depth Testing
29. glDisable(GL_TEXTURE_2D);
30. glPopAttrib();
31. }
TextureSet选择字符集,因为字符纹理⾥⾯每个字符对应两种字体,⽤0/1选择。glPushAttrib的作⽤
前⾯说过了。之后我们⽤glPushMatrix保存当前的投影/模型视图矩阵,在弄好⼀切好就马上切换回去。弄什么呢?新的坐标变换。⽂字(glCallLists绘制)是脱离我们OPENGL图形世界的,因此单独给予它⼀套变换——在屏幕最前⽅且不受视觉透视影响。glOrtho(0,ScreenWidth,0,ScreenHeight,-1,1)这样的投影变换设置配合glTranslated这样的模型变换就能满⾜我的要求了。因为创建的正交投影是与渲染窗⼝⼤⼩⼀致的,所以glTranslated的X,Y的单位与像素pixel对应——这不也就是GDI那种设置⽅式了么哈。