新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 计算机科学论坛计算机技术与应用『 C/C++编程思想 』 → [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 13-lesson 14 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 16460 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 13-lesson 14 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客楼主
    发贴心情 [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 13-lesson 14

    第十三十四课源码

    按此在新窗口浏览图片图像字体:

    这一课我们将创建一些基于2D图像的字体,它们可以缩放,但不能旋转,并且总是面向前方,但作为基本的显示来说,我想已经够了。

      
       
       
    欢迎来到另一课教程,这次我将教你如何使用位图字体,也许你会对自己说:“在屏幕上显示文字有什么难的?”。但是你真正尝试过就会知道,它确实没那么容易。

    当然,你可以载入一段美术程序,把文字写在一个图片上,再把这幅图片载入你的OpenGL程序中,打开混合选项,从而在屏幕上显示出文字。但是这种做法非常耗时。而且根据你选择的滤波类型,最终结果常常会显得很模糊,或者有很多马赛克。另外,除非你的图像包含一个Alpha通道,否则一旦绘制在屏幕上,那些文字就会不透明(与屏幕中的其它物体混合)。

    如果你使用过记事本、微软的Word或者其它文字处理软件,你会注意到所有不同的字体都是可用的。这课就会教你如何在自己的OpenGL程序中使用和原样相同的字体。事实上,任何安装在你的计算机中的字体都可以使用在演示中(中文不行)。

    使用位图字体比起使用图形字体(贴图)看起来不止强100倍。你可以随时改变显示在屏幕上的文字,而且用不着为它们逐个制作贴图。只需要将文字定位,再使用我最新的gl命令就可以在屏幕上显示文字了。

    我尽可能试着将命令做的简单。你只需要敲入glPrint("Hello") 。它是那么简单。不管怎样,从这段长长的介绍就可以看出,我对这课教程是多么的满意。写这段代码大概花了我一个半小时,为什么这么长的时间呢?那是因为在使用位图字体方面完全没有可用的资料,除非你愿意使用MFC中的代码。为了使代码简单,我想,如果我把它全部重写为容易理解的C语言代码,那一定会好些 :)

    一个小注释,这段代码是专门针对Windows写的,它使用了Windows的wgl函数来创建字体,显然,Apple机系统有agl,X系统有glx来支持做同样事情的,不幸的是,我不能保证这些代码也是容易使用的。如果那位有能在屏幕上显示文字且独立于平台的代码,请告诉我,我将重写一个有关字体的教程。

    我们从第一课的典型代码开始,添加上stdio.h头文件以便进行标准输入/输出操作,另外,stdarg.h头文件用来解析文字以及把变量转换为文字。最后加上math.h头文件,这样我们就可以使用SIN和COS函数在屏幕中移动文字了。

      
       

    #include <stdarg.h>  // 用来定义可变参数的头文件

       
    另外,我们还要添加3个变量。base将保存我们创建的第一个显示列表的编号。每个字符都需要有自己的显示列表。例如,字符‘A’在显示列表中是65,‘B’是66,‘C’是67,等等。所以,字符‘A’应保存在显示列表中的base + 65这个位置。

    然后添加两个计数器(cnt1 和 cnt2),它们采用不用的累加速度,通过SIN和COS函数来改变文字在屏幕上的位置。在屏幕上创造出一种看起来像是半随机的移动方式。同时,我们用这两个计数器来改变文字的颜色(后面会进一步解释)。

      
       

    GLuint base;   // 绘制字体的显示列表的开始位置
    GLfloat cnt1;   // 字体移动计数器1
    GLfloat cnt2;   // 字体移动计数器2

       
    下面这段代码用来构建真实的字体,这也是最难写的一部分代码。‘HFONT font’告诉Windows我们将要使用一个Windows字体。Oldfont用来存放字体。
    接下来我们在定义base的同时使用glGenLists(96)创建了一组共96个显示列表。

      
       

    GLvoid BuildFont(GLvoid)      // 创建位图字体
    {
     HFONT font;      // 字体句柄
     HFONT oldfont;      // 旧的字体句柄

     base = glGenLists(96);     // 创建96个显示列表

       
    下面该有趣的部分了,我们将创建属于自己的字体。我们从指定字体的大小开始,你会注意到它是一个负数,我们通过加上一个负号来告诉Windows寻找一个基于CHARACTER高度的字体。如果我们使用一个正数,就是寻找一个与基于CELL的高度相匹配的字体。
      
       

     font = CreateFont( -24,     // 字体高度

       
    然后我们指定每个单元的宽度,你会注意到我把它定义为0,这样,Windows就会使用默认值。如果你愿意的话,可以改变它的值,比如更宽一点,等等。  
       

        0,    // 字体宽度
       
    Angle Of Escapement会将字体旋转,它不是一个常用的属性,除了0,90,180,270四个角度以外,由于字体本身要适应其看不见的方形边框,常常会显的裁切不正。MSDN帮助中解释Orientation Angle用于指定每个字的底边和显示设备的X轴之间的角度,每个单位是十分之一个角度,不幸的是我对这个没有概念。
      
       

        0,    // 字体的旋转角度 Angle Of Escapement
        0,    // 字体底线的旋转角度Orientation Angle

       
    字体重量是一个很重要的参数,你可以设置一个0–1000之间的值或使用一个已定义的值。FW_DONTCARE是0, FW_NORMAL是400, FW_BOLD是700 and FW_BLACK是900。还有许多预先定义的值,但是这四个的效果比较好。值越大,字体就越粗。
      
       

        FW_BOLD,    // 字体的重量
       
    Italic(斜体),Underline(下划线)和Strikeout(删除线)可以是TRUE或FALSE。如果将Underline设置为TRUE,那么字体就会带有下划线,否则就没有,非常简单。
      
       

        FALSE,    // 是否使用斜体
        FALSE,    // 是否使用下划线
        FALSE,    // 是否使用删除线

       
    Character Set Identifier(字符集标识符)用来描述你要使用的字符集(内码)类型。有太多需要说明的类型了。CHINESEBIG5_CHARSET,GREEK_CHARSET,RUSSIAN_CHARSET,DEFAULT_CHARSET ,等等。我使用的是ANSI,尽管DEFAULT也是很好用的。
    如果你有兴趣使用Webdings或Wingdings等字体,你必须使用SYMBOL_CHARSET而不是ANSI_CHARSET。
      
       

        ANSI_CHARSET,   // 设置字符集

       
    Output Precision(输出精度)非常重要。它告诉Windows在有多种字符集的情况下使用哪类字符集。OUT_TT_PRECIS告诉Windows如果一个名字对应多种不同的选择字体,那么选择字体的TRUETYPE类型。Truetype字体通常看起来要好些,尤其是你把它们放大的时候。你也可以使用OUT_TT_ONLY_PRECIS,它将会一直尝试使用一种TRUETYPE类型的字体
      
       

        OUT_TT_PRECIS,   // 输出精度

       
    裁剪精度是一种当字体落在裁剪范围之外时使用的剪辑类型,不用多说,只要把它设置为DEFAULT就可以了。
      
       

        CLIP_DEFAULT_PRECIS,  // 裁剪精度

       
    输出质量非常重要。你可以使用PROOF,DRAFT,NONANTIALIASED,DEFAULT或ANTIALISED。
    我们都知道,ANTIALIASED字体看起来很好,将一种字体Antialiasing(反锯齿)可以实现在Windows下打开字体平滑时同样的效果,它使任何东西看起来都要少些锯齿,也就是更平滑。  
       

        ANTIALIASED_QUALITY,  // 输出质量

       
    下面是Family和Pitch设置。Pitch属性有DEFAULT_PITCH,FIXED_PITCH和VARIABLE_PITCH,Family有FF_DECORATIVE,FF_MODERN,FF_ROMAN,FF_SCRIPT,FF_SWISS,FF_DONTCARE.尝试一下这些值,你就会知道它们到底有什么功能。我把它们都设置为默认值。
      
       

        FF_DONTCARE|DEFAULT_PITCH,  // Family And Pitch

       
    最后,是我们需要的字体的确切的名字。打开Microsoft Word或其它什么文字处理软件,点击字体下拉菜单,找一个你喜欢的字体。将‘Courier New’替换为你想用的字体的名字,你就可以使用它了。(中文还不行,需要别的方法)
      
       

        "Courier New");   // 字体名称

       
    现在,选择我们刚才创建的字体。Oldfont将指向被选择的对象。然后我们从第32个字符(空格)开始建立96个显示列表。如果你愿意,也可以建立所有256个字符,只要确保使用glGenLists建立256个显示列表就可以了。然后我们将oldfont对象指针选入hDC并且删除font对象。  
       

     oldfont = (HFONT)SelectObject(hDC, font);   // 选择我们需要的字体
     wglUseFontBitmaps(hDC, 32, 96, base);   // 创建96个显示列表,绘制从ASCII码为32-128的字符
     SelectObject(hDC, oldfont);     // 选择原来的字体
     DeleteObject(font);     // 删除字体
    }

       
    接下来的代码很简单。它在内存中从base开始删除96个显示列表。我不知道Windows是否会做这些工作,但还是保险为好。
      
       

    GLvoid KillFont(GLvoid)      // 删除显示列表
    {
     glDeleteLists(base, 96);     //删除96个显示列表
    }

       
    下面就是我优异的GL文字程序了。你可以通过调用glPrint(“需要写的文字”)来调用这段代码。文字被存储在字符串 * fmt中。  
       

    GLvoid glPrint(const char *fmt, ...)     // 自定义GL输出字体函数
    {

       
    下面的第一行创建了一个大小为256个字符的字符数组,里面保存我们想要的文字串。第二行创建了一个指向一个变量列表的指针。我们在传递字符串的同时也传递了这个变量列表。如果我们传递文本时也传递了变量,这个指针将指向它们。  
       

     char  text[256];    // 保存文字串
     va_list  ap;     // 指向一个变量列表的指针

       
    下面两行代码检查是否有需要显示的内容,如果什么也没有,fmt就等于空(NULL),屏幕上也就什么都没有。
      
       

     if (fmt == NULL)      // 如果无输入则返回
      return;      
       
    接下来三行代码将文字中的所有符号转换为它们的字符编号。最后,文字和转换的符号被存储在一个叫做text的字符串中。以后我会多解释一些有关字符的细节。
      
       

     va_start(ap, fmt);      // 分析可变参数
         vsprintf(text, fmt, ap);    // 把参数值写入字符串
     va_end(ap);      // 结束分析

       
    然后我们将GL_LIST_BIT压入属性堆栈,它会防止glListBase影响到我们的程序中的其它显示列表。
    GlListBase(base-32)是一条有些难解释的命令。比如说要写字母‘A’,它的相应编号为65。如果没有glListBase(base-32)命令,OpenGL就不知道到哪去找这个字母。它会在显示列表中的第65个位置找它,但是,假如base的值等于1000,那么‘A’的实际存放位置就是1065了。所以通过base设置一个起点,OpenGL就知道到哪去找到正确的显示列表了。减去32是因为我们没有构造过前32个显示列表,那么就跳过它们好了。于是,我们不得不通过从base的值减去32来让OpenGL知道这一点。我希望这些有意义。

      
       

     glPushAttrib(GL_LIST_BIT);     // 把显示列表属性压入属性堆栈
     glListBase(base - 32);     // 设置显示列表的基础值

       
    现在OpenGL知道字母的存放位置了,我们就可以让它在屏幕上显示文字了。GlCallLists是一个很有趣的命令。它可以同时将多个显示列表的内容显示在屏幕上。
    下面的代码做后续工作。首先,它告诉OpenGL我们将要在屏幕上显示出显示列表中的内容。Strlen(text)函数用来计算我们将要显示在屏幕上的文字的长度。然后,OpenGL需要知道我们允许发送给它的列表的最大值。我们不能发送长度大于255的字符串。这个字符列表的参数被当作一个无符号字符数组处理,它们的值都介于0到255之间。最后,我们通过传递text(它指向我们的字符串)来告诉OpenGL显示的内容。

    也许你想知道为什么字符不会彼此重叠堆积在一起。那时因为每个字符的显示列表都知道字符的右边缘在那里,在写完一个字符后,OpenGL自动移动到刚写过的字符的右边,在写下一个字或画下一个物体时就会从GL移动到的最后的位置开始,也就是最后一个字符的右边。

    最后,我们将GL_LIST_BIT属性弹出堆栈,将GL恢复到我们使用glListBase(base-32)设置base那时的状态。

      
       

     glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);  // 调用显示列表绘制字符串
     glPopAttrib();      // 弹出属性堆栈
    }

       
    在初始化代码中唯一的变化就是BuildFont()。它调用前面的代码来创建字体,然后OpenGL就可以使用这个字体了。  
       

    BuildFont();       // 创建字体

       
    下面就是画图的代码了。我们从清除屏幕和深度缓存开始。我们调用glLoadIdentity()来重置所有东西。然后我们将坐标系向屏幕里移动一个单位。如果不移动的话无法显示出文字。当你使用透视投影而不是ortho投影的时候位图字体表现的更好。由于ortho看起来不好,所以我用透视投影,并移动坐标系。。
    你会注意到如果把坐标系在屏幕里放的更深远,字体并不会想你想象的那样缩小,只是你可以在控制文字位置时有更多的选择。如果你将坐标系移入屏幕一个单位,你就可以字X轴上-0.5到+0.5的范围内调整文字的位置。如果深入10个单位的话,移动范围就从-5到+5。它给了你更多的选择来替代使用小数指定文字的精确位置。什么都不能改变文字的大小,即使是调用glScale(x,y,z)函数.如果你想改变字体的大小,只能在创建它的时候改变它。

      
       

    int DrawGLScene(GLvoid)      // 此过程中包括所有的绘制代码
    {
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // 清除屏幕及深度缓存
     glLoadIdentity();      // 重置当前的模型观察矩阵
     glTranslatef(0.0f,0.0f,-1.0f);    // 移入屏幕一个单位

       
    下面我们使用一些奇妙的数学方法来产生颜色变化脉冲。如果你不懂我在做什么你也不必担心。我喜欢利用教多的变量和教简单的方法来达到我的目的。
    这样,我使用那两个用来改变文字在屏幕上位置的计数器来改变红、绿、蓝这些颜色。红色值使用COS和计数器1在-1.0到1.0之间变化。绿色值使用SIN和计数器2也在-1.0到1.0之间变化。蓝色值使用COS和计数器1和2在0.5到1.5之间变化。于是,蓝色值就永远不会等于0,文字的颜色也永远不会消失。笨办法,但很管用。

      
       

     // 根据字体位置设置颜色
     glColor3f(1.0f*float(cos(cnt1)),1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)));

       
    下面是一个新命令。GlRasterPos2f(x,y)用于在屏幕上定位位图字体。屏幕的中心依然是(0,0),注意,这里没有Z轴位置。位图字体只使用X轴(左/右)和Y轴(上/下)。因为我们将坐标系移入屏幕一个单位,往左最大值为-0.5,往右最大值为+0.5。你会注意到我在X轴上向左移动了0.45个像素。它将文字移到屏幕的中心位置。否则,因为文字的起点就是屏幕的中心,会造成文字整体偏右。
    计算文字位置的算法与设置文字颜色的算法差不多。它将文字在X轴的-0.50到-0.40的范围内移动(记住,我们从起点就减了0.45),这就保证文字始终能显示在屏幕内。由于使用COS和计数器1,所以文字左右摆动,使用SIN和计数器2在Y轴的-0.35到0.35范围内移动。

      
       

     // 设置光栅化位置,即字体的位置
     glRasterPos2f(-0.45f+0.05f*float(cos(cnt1)), 0.35f*float(sin(cnt2)));

       
    现在轮到我最满意的部分了。将真正的文字写到屏幕上。我试着把它做的非常简单,而且非常友好,便于使用。你会注意到它看起来像调用一个OpenGL的函数,有点类似C语言中的输出语句的风格。在屏幕上输出文字只需要调用glPrint(“你想写的文字”).它很容易。文字将精确的显示在屏幕上你指定的位置。
    Shawn T.发给我修改过的代码允许glPrint传递变量到屏幕。这意味着你可以增加一个计数器,并且在屏幕上显示出这个计数器的值,它是这样工作的。。。在下一行你看到:要显示的普通文字,然后有一个空格,一个破折号,一个空格,然后是一个“符号”(%7.2f)(C语言中的输出格式控制字).现在你会看着%7.2说这是什么意思。它其实很简单,%是一个记号,表示不要把7.2f本身显示在屏幕上,因为它代表一个变量。7表示小数点左边最多有7位数字。然后是小数部分,小数点右边的2表示小数点右边最多保留两位小数。最后,f表示我们想要显示的数字类型为浮点型。我们想在屏幕上显示计数器1的值。比如,计数器1的值为300.12345f,那么在屏幕上显示的数字就是300.12,小数部分的3,4,5会舍去。因为我们只需要显示小数点后面两位数字。

    我知道如果你是一个有经验的C程序员,这是个很基础的问题。不过也许也有人没有用过pringf函数。如果你想了解更多的字符,那就买本书或者查阅MSDN。

      
       

     glPrint("Active OpenGL Text With NeHe - %7.2f", cnt1);  // 输出文字到屏幕

       
    最后一件事就是以不同的速率增加计数器的值来产生颜色脉冲并且移动文字。  
       

     cnt1+=0.051f;      // 增加计数器值
     cnt2+=0.005f;      // 增加计数器值
     return TRUE;      // 继续运行
    }

       
    最后,如下所示,就是增加在KillGLWindow()函数中增加KillFont()函数,这很重要,它在我们退出程序之前做清理工作。
      
       

    KillFont();       // 删除字体

       
    好了,用于使用位图字体的所有一切都在你的OpenGL程序中了。我在网上寻找过与这篇教程相似的文章,但没有找到。或许我的网站是第一个涉及这个主题的C代码的网站吧。不管怎样,享用这篇教程,快乐编码!


       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/16 12:01:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客2
    发贴心情 
    Lesson 13
       
    Welcome to yet another Tutorial. This time on I'll be teaching you how to use Bitmap Fonts. You may be saying to yourself "what's so hard about putting text onto the screen". If you've ever tried it, it's not that easy!

    Sure you can load up an art program, write text onto an image, load the image into your OpenGL program, turn on blending then map the text onto the screen. But this is time consuming, the final result usually looks blurry or blocky depending on the type of filtering you use, and unless your image has an alpha channel your text will end up transparent (blended with the objects on the screen) once it's mapped to the screen.

    If you've ever used Wordpad, Microsoft Word or some other Word Processor, you may have noticed all the different types of Font's avaialable. This tutorial will teach you how to use the exact same fonts in your own OpenGL programs. As a matter of fact... Any font you install on your computer can be used in your demos.

    Not only do Bitmap Fonts looks 100 times better than graphical fonts (textures). You can change the text on the fly. No need to make textures for each word or letter you want to write to the screen. Just position the text, and use my handy new gl command to display the text on the screen.

    I tried to make the command as simple as possible. All you do is type glPrint("Hello"). It's that easy. Anyways. You can tell by the long intro that I'm pretty happy with this tutorial. It took me roughly 1 1/2 hours to create the program. Why so long? Because there is literally no information available on using Bitmap Fonts, unless of course you enjoy MFC code. In order to keep the code simple I decided it would be nice if I wrote it all in simple to understand C code :)

    A small note, this code is Windows specific. It uses the wgl functions of Windows to build the font. Apparently Apple has agl support that should do the same thing, and X has glx. Unfortunately I can't guarantee this code is portable. If anyone has platform independant code to draw fonts to the screen, send it my way and I'll write another font tutorial.

    We start off with the typical code from lesson 1. We'll be adding the stdio.h header file for standard input/output operations; the stdarg.h header file to parse the text and convert variables to text, and finally the math.h header file so we can move the text around the screen using SIN and COS.   
       

    #include <windows.h>  // Header File For Windows
    #include <math.h>  // Header File For Windows Math Library  ( ADD )
    #include <stdio.h>  // Header File For Standard Input/Output ( ADD )
    #include <stdarg.h>  // Header File For Variable Argument Routines ( ADD )
    #include <gl\gl.h>  // Header File For The OpenGL32 Library
    #include <gl\glu.h>  // Header File For The GLu32 Library
    #include <gl\glaux.h>  // Header File For The GLaux Library

    HDC  hDC=NULL; // Private GDI Device Context
    HGLRC  hRC=NULL; // Permanent Rendering Context
    HWND  hWnd=NULL; // Holds Our Window Handle
    HINSTANCE hInstance; // Holds The Instance Of The Application

       
    We're going to add 3 new variables as well. base will hold the number of the first display list we create. Each character requires it's own display list. The character 'A' is 65 in the display list, 'B' is 66, 'C' is 67, etc. So 'A' would be stored in display list base+65.

    Next we add two counters (cnt1 & cnt2). These counters will count up at different rates, and are used to move the text around the screen using SIN and COS. This creates a semi-random looking movement on the screen. We'll also use the counters to control the color of the letters (more on this later).   
       

    GLuint base;   // Base Display List For The Font Set
    GLfloat cnt1;   // 1st Counter Used To Move Text & For Coloring
    GLfloat cnt2;   // 2nd Counter Used To Move Text & For Coloring

    bool keys[256];  // Array Used For The Keyboard Routine
    bool active=TRUE;  // Window Active Flag Set To TRUE By Default
    bool fullscreen=TRUE; // Fullscreen Flag Set To Fullscreen Mode By Default

    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc

       
    The following section of code builds the actual font. This was the most difficult part of the code to write. 'HFONT font' in simple english tells Windows we are going to be manipulating a Windows font. oldfont is used for good house keeping.

    Next we define base. We do this by creating a group of 96 display lists using glGenLists(96). After the display lists are created, the variable base will hold the number of the first list.   
       

    GLvoid BuildFont(GLvoid)     // Build Our Bitmap Font
    {
     HFONT font;      // Windows Font ID
     HFONT oldfont;     // Used For Good House Keeping

     base = glGenLists(96);     // Storage For 96 Characters ( NEW )

       
    Now for the fun stuff. We're going to create our font. We start off by specifying the size of the font. You'll notice it's a negative number. By putting a minus, we're telling windows to find us a font based on the CHARACTER height. If we use a positive number we match the font based on the CELL height.   
       

     font = CreateFont( -24,    // Height Of Font ( NEW )

       
    Then we specify the cell width. You'll notice I have it set to 0. By setting values to 0, windows will use the default value. You can play around with this value if you want. Make the font wide, etc.   
       

        0,    // Width Of Font

       
    Angle of Escapement will rotate the font. Unfortunately this isn't a very useful feature. Unless your at 0, 90, 180, and 270 degrees, the font usually gets cropped to fit inside it's invisible square border. Orientation Angle quoted from MSDN help Specifies the angle, in tenths of degrees, between each character's base line and the x-axis of the device. Unfortunately I have no idea what that means :(   
       

        0,    // Angle Of Escapement
        0,    // Orientation Angle

       
    Font weight is a great parameter. You can put a number from 0 - 1000 or you can use one of the predefined values. FW_DONTCARE is 0, FW_NORMAL is 400, FW_BOLD is 700 and FW_BLACK is 900. There are alot more predefined values, but those 4 give some good variety. The higher the value, the thicker the font (more bold).   
       

        FW_BOLD,   // Font Weight

       
    Italic, Underline and Strikeout can be either TRUE or FALSE. Basically if underline is TRUE, the font will be underlined. If it's FALSE it wont be. Pretty simple :)   
       

        FALSE,    // Italic
        FALSE,    // Underline
        FALSE,    // Strikeout

       
    Character set Identifier describes the type of Character set you wish to use. There are too many types to explain. CHINESEBIG5_CHARSET, GREEK_CHARSET, RUSSIAN_CHARSET, DEFAULT_CHARSET, etc. ANSI is the one I use, although DEFAULT would probably work just as well.

    If you're interested in using a font such as Webdings or Wingdings, you need to use SYMBOL_CHARSET instead of ANSI_CHARSET.   
       

        ANSI_CHARSET,   // Character Set Identifier

       
    Output Precision is very important. It tells Windows what type of character set to use if there is more than one type available. OUT_TT_PRECIS tells Windows that if there is more than one type of font to choose from with the same name, select the TRUETYPE version of the font. Truetype fonts always look better, especially when you make them large. You can also use OUT_TT_ONLY_PRECIS, which ALWAYS trys to use a TRUETYPE Font.   
       

        OUT_TT_PRECIS,   // Output Precision

       
    Clipping Precision is the type of clipping to do on the font if it goes outside the clipping region. Not much to say about this, just leave it set to default.   
       

        CLIP_DEFAULT_PRECIS,  // Clipping Precision

       
    Output Quality is very important.you can have PROOF, DRAFT, NONANTIALIASED, DEFAULT or ANTIALIASED. We all know that ANTIALIASED fonts look good :) Antialiasing a font is the same effect you get when you turn on font smoothing in Windows. It makes everything look less jagged.   
       

        ANTIALIASED_QUALITY,  // Output Quality

       
    Next we have the Family and Pitch settings. For pitch you can have DEFAULT_PITCH, FIXED_PITCH and VARIABLE_PITCH, and for family you can have FF_DECORATIVE, FF_MODERN, FF_ROMAN, FF_SCRIPT, FF_SWISS, FF_DONTCARE. Play around with them to find out what they do. I just set them both to default.   
       

        FF_DONTCARE|DEFAULT_PITCH, // Family And Pitch

       
    Finally... We have the actual name of the font. Boot up Microsoft Word or some other text editor. Click on the font drop down menu, and find a font you like. To use the font, replace 'Courier New' with the name of the font you'd rather use.   
       

        "Courier New");   // Font Name

       
    Now we select the font we just created. oldfont will point to the selected object. We then build the 96 display lists starting at character 32 (which is a blank space). You can build all 256 if you'd like, just make sure you build 256 display lists using glGenLists. After that we select the object oldfont points to and then we delete the font object.   
       

     oldfont = (HFONT)SelectObject(hDC, font);  // Selects The Font We Want
     wglUseFontBitmaps(hDC, 32, 96, base);   // Builds 96 Characters Starting At Character 32
     SelectObject(hDC, oldfont);    // Selects The Font We Want
     DeleteObject(font);     // Delete The Font
    }

       
    The following code is pretty simple. It deletes the 96 display lists from memory starting at the first list specified by 'base'. I'm not sure if windows would do this for you, but it's better to be safe than sorry :)   
       

    GLvoid KillFont(GLvoid)      // Delete The Font List
    {
     glDeleteLists(base, 96);    // Delete All 96 Characters ( NEW )
    }

       
    Now for my handy dandy GL text routine. You call this section of code with the command glPrint("message goes here"). The text is stored in the char string *fmt.   
       

    GLvoid glPrint(const char *fmt, ...)    // Custom GL "Print" Routine
    {

       
    The first line below creates storage space for a 256 character string. text is the string we will end up printing to the screen. The second line below creates a pointer that points to the list of arguments we pass along with the string. If we send any variables along with the text, this will point to them.   
       

     char  text[256];    // Holds Our String
     va_list  ap;     // Pointer To List Of Arguments

       
    The next two lines of code check to see if there's anything to display? If there's no text, fmt will equal nothing (NULL), and nothing will be drawn to the screen.   
       

     if (fmt == NULL)     // If There's No Text
      return;      // Do Nothing

       
    The following three lines of code convert any symbols in the text to the actual numbers the symbols represent. The final text and any converted symbols are then stored in the character string called "text". I'll explain symbols in more detail down below.   
       

     va_start(ap, fmt);     // Parses The String For Variables
         vsprintf(text, fmt, ap);    // And Converts Symbols To Actual Numbers
     va_end(ap);      // Results Are Stored In Text

       
    We then push the GL_LIST_BIT, this prevents glListBase from affecting any other display lists we may be using in our program.

    The command glListBase(base-32) is a little hard to explain. Say we draw the letter 'A', it's represented by the number 65. Without glListBase(base-32) OpenGL wouldn't know where to find this letter. It would look for it at display list 65, but if base was equal to 1000, 'A' would actually be stored at display list 1065. So by setting a base starting point, OpenGL knows where to get the proper display list from. The reason we subtract 32 is because we never made the first 32 display lists. We skipped them. So we have to let OpenGL know this by subtracting 32 from the base value. I hope that makes sense.   
       

     glPushAttrib(GL_LIST_BIT);    // Pushes The Display List Bits  ( NEW )
     glListBase(base - 32);     // Sets The Base Character to 32 ( NEW )

       
    Now that OpenGL knows where the Letters are located, we can tell it to write the text to the screen. glCallLists is a very interesting command. It's capable of putting more than one display list on the screen at a time.

    The line below does the following. First it tells OpenGL we're going to be displaying lists to the screen. strlen(text) finds out how many letters we're going to send to the screen. Next it needs to know what the largest list number were sending to it is going to be. We're not sending any more than 255 characters. The lists parameter is treated as an array of unsigned bytes, each in the range 0 through 255. Finally we tell it what to display by passing text (pointer to our string).

    In case you're wondering why the letters don't pile on top of eachother. Each display list for each character knows where the right side of the letter is. After the letter is drawn, OpenGL translates to the right side of the drawn letter. The next letter or object drawn will be drawn starting at the last location GL translated to, which is to the right of the last letter.

    Finally we pop the GL_LIST_BIT setting GL back to how it was before we set our base setting using glListBase(base-32).   
       

     glCallLists(strlen(text), GL_UNSIGNED_BYTE, text); // Draws The Display List Text ( NEW )
     glPopAttrib();      // Pops The Display List Bits ( NEW )
    }

       
    The only thing different in the Init code is the line BuildFont(). This jumps to the code above that builds the font so OpenGL can use it later on.   
       

    int InitGL(GLvoid)      // All Setup For OpenGL Goes Here
    {
     glShadeModel(GL_SMOOTH);    // Enable Smooth Shading
     glClearColor(0.0f, 0.0f, 0.0f, 0.5f);   // Black Background
     glClearDepth(1.0f);     // Depth Buffer Setup
     glEnable(GL_DEPTH_TEST);    // Enables Depth Testing
     glDepthFunc(GL_LEQUAL);     // The Type Of Depth Testing To Do
     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations

     BuildFont();      // Build The Font

     return TRUE;      // Initialization Went OK
    }

       
    Now for the drawing code. We start off by clearing the screen and the depth buffer. We call glLoadIdentity() to reset everything. Then we translate one unit into the screen. If we don't translate, the text wont show up. Bitmap fonts work better when you use an ortho projection rather than a perspective projection, but ortho looks bad, so to make it work in projection, translate.

    You'll notice that if you translate even deeper into the screen the size of the font does not shrink like you'd expect it to. What actually happens when you translate deeper is that you have more control over where the text is on the screen. If you tranlate 1 unit into the screen, you can place the text anywhere from -0.5 to +0.5 on the X axis. If you tranlate 10 units into the screen, you place the text from -5 to +5. It just gives you more control instead of using decimal places to position the text at exact locations. Nothing will change the size of the text. Not even glScalef(x,y,z). If you want the font bigger or smaller, make it bigger or smaller when you create it!   
       

    int DrawGLScene(GLvoid)      // Here's Where We Do All The Drawing
    {
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
     glLoadIdentity();     // Reset The View
     glTranslatef(0.0f,0.0f,-1.0f);    // Move One Unit Into The Screen

       
    Now we use some fancy math to make the colors pulse. Don't worry if you don't understand what I'm doing. I like to take advantage of as many variables and stupid tricks as I can to achieve results :)

    In this case I'm using the two counters we made to move the text around the screen to change the red, green and blue colors. Red will go from -1.0 to 1.0 using COS and counter 1. Green will also go from -1.0 to 1.0 using SIN and counter 2. Blue will go from 0.5 to 1.5 using COS and counter 1 and 2. That way blue will never be 0, and the text should never completely fade out. Stupid, but it works :)   
       

     // Pulsing Colors Based On Text Position
     glColor3f(1.0f*float(cos(cnt1)),1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)));

       
    Now for a new command. glRasterPos2f(x,y) will position the Bitmapped Font on the screen. The center of the screen is still 0,0. Notice there's no Z position. Bitmap Fonts only use the X axis (left/right) and Y axis (up/down). Because we translate one unit into the screen, the far left is -0.5, and the far right is +0.5. You'll notice that I move 0.45 pixels to the left on the X axis. This moves the text into the center of the screen. Otherwise it would be more to the right of the screen because it would be drawn from the center to the right.

    The fancy(?) math does pretty much the same thing as the color setting math does. It moves the text on the x axis from -0.50 to -0.40 (remember, we subtract 0.45 right off the start). This keeps the text on the screen at all times. It swings left and right using COS and counter 1. It moves from -0.35 to +0.35 on the Y axis using SIN and counter 2.   
       

     // Position The Text On The Screen
     glRasterPos2f(-0.45f+0.05f*float(cos(cnt1)), 0.35f*float(sin(cnt2)));

       
    Now for my favorite part... Writing the actual text to the screen. I tried to make it super easy, and very user friendly. You'll notice it looks alot like an OpenGL call, combined with the good old fashioned Print statement :) All you do to write the text to the screen is glPrint("{any text you want}"). It's that easy. The text will be drawn onto the screen at the exact spot you positioned it.

    Shawn T. sent me modified code that allows glPrint to pass variables to the screen. This means that you can increase a counter and display the results on the screen! It works like this... In the line below you see our normal text. Then there's a space, a dash, a space, then a "symbol" (%7.2f). Now you may look at %7.2f and say what the heck does that mean. It's very simple. % is like a marker saying don't print 7.2f to the screen, because it represents a variable. The 7 means a maximum of 7 digits will be displayed to the left of the decimal place. Then the decimal place, and right after the decimal place is a 2. The 2 means that only two digits will be displayed to the right of the decimal place. Finally, the f. The f means that the number we want to display is a floating point number. We want to display the value of cnt1 on the screen. As an example, if cnt1 was equal to 300.12345f the number we would end up seeing on the screen would be 300.12. The 3, 4, and 5 after the decimal place would be cut off because we only want 2 digits to appear after the decimal place.

    I know if you're an experienced C programmer, this is absolute basic stuff, but there may be people out there that have never used printf. If you're interested in learning more about symbols, buy a book, or read through the MSDN.   
       

     glPrint("Active OpenGL Text With NeHe - %7.2f", cnt1); // Print GL Text To The Screen

       
    The last thing to do is increase both the counters by different amounts so the colors pulse and the text moves.   
       

     cnt1+=0.051f;      // Increase The First Counter
     cnt2+=0.005f;      // Increase The Second Counter
     return TRUE;      // Everything Went OK
    }

       
    The last thing to do is add KillFont() to the end of KillGLWindow() just like I'm showing below. It's important to add this line. It cleans things up before we exit our program.   
       

     if (!UnregisterClass("OpenGL",hInstance))  // Are We Able To Unregister Class
     {
      MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
      hInstance=NULL;     // Set hInstance To NULL
     }

     KillFont();      // Destroy The Font
    }

       
    That's it... Everything you need to know in order to use Bitmap Fonts in your own OpenGL projects. I've searched the net looking for a tutorial similar to this one, and have found nothing. Perhaps my site is the first to cover this topic in easy to understand C code? Anyways. Enjoy the tutorial, and happy coding!

    Jeff Molofee (NeHe)

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/16 12:02:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客3
    发贴心情 
    第十四课


    按此在新窗口浏览图片 图形字体:

    在一课我们将教你绘制3D的图形字体,它们可像一般的3D模型一样被变换。

      
       
       
    这节课继续上一节课课的内容。在第13课我们学习了如何使用位图字体,这节课,我们将学习如何使用轮廓字体。
    创建轮廓字体的方法类似于在第13课中我们创建位图字体的方法。但是,轮廓字体看起来要酷100倍!你可以指定轮廓字体的大小。轮廓字体可以在屏幕中以3D方式运动,而且轮廓字体还可以有一定的厚度!而不是平面的2D字符。使用轮廓字体,你可以将你的计算机中的任何字体转换为OpenGL中的3D字体,加上合适的法线,在有光照的时候,字符就会被很好的照亮了。

    一个小注释,这段代码是专门针对Windows写的,它使用了Windows的wgl函数来创建字体,显然,Apple机系统有agl,X系统有glx来支持做同样事情的,不幸的是,我不能保证这些代码也是容易使用的。如果哪位有能在屏幕上显示文字且独立于平台的代码,请告诉我,我将重写一个有关字体的教程。

    我们从第一课的典型代码开始,添加上stdio.h头文件以便进行标准输入/输出操作,另外,stdarg.h头文件用来解析文字以及把变量转换为文字。最后加上math.h头文件,这样我们就可以使用SIN和COS函数在屏幕中移动文字了。
      
       
       
    另外,我们还要添加2个变量。base将保存我们创建的第一个显示列表的编号。每个字符都需要有自己的显示列表。例如,字符‘A’在显示列表中是65,‘B’是66,‘C’是67,等等。所以,字符‘A’应保存在显示列表中的base + 65这个位置。
    我们再添加一个叫做rot的变量。用它配合SIN和COS函数在屏幕上旋转文字。我们同时用它来改变文字的颜色。

      
       

    GLuint base;   // 绘制字体的显示列表的开始位置
    GLfloat rot;   // 旋转字体

       
    GLYPHMETRICSFLOAT gmf[256]用来保存256个轮廓字体显示列表中对应的每一个列表的位置和方向的信息。我们通过gmf[num]来选择字母。num就是我们想要了解的显示列表的编号。在稍后的代码中,我将说明如何如何检查每个字符的宽度,以便自动将文字定位在屏幕中心。切记,每个字符的宽度可以不相同。Glyphmetrics会大大简化我们的工作。  
       

    GLYPHMETRICSFLOAT gmf[256]; // 记录256个字符的信息

       
    下面这段用来构建真正的字体的代码类似于我们创建位图字体的方法。和13课一样,只是使用wglUseFontOutlines函数替换wglUseFontBitmaps函数。
      
       

    base = glGenLists(256);      // 创建256个显示列表 
    wglUseFontOutlines( hDC,     // 设置当前窗口设备描述表的句柄
        0,    // 用于创建显示列表字体的第一个字符的ASCII值
        255,    // 字符数
        base,    // 第一个显示列表的名称

       
    That's not all however. We then set the deviation level. The closer to 0.0f, the smooth the font will look. After we set the deviation, we get to set the font thickness. This describes how thick the font is on the Z axis. 0.0f will produce a flat 2D looking font and 1.0f will produce a font with some depth.

    The parameter WGL_FONT_POLYGONS tells OpenGL to create a solid font using polygons. If we use WGL_FONT_LINES instead, the font will be wireframe (made of lines). It's also important to note that if you use GL_FONT_LINES, normals will not be generated so lighting will not work properly.

    The last parameter gmf points to the address buffer for the display list data.   
       

        0.0f,    // 字体的光滑度,越小越光滑,0.0为最光滑的状态
        0.2f,    // 在z方向突出的距离
        WGL_FONT_POLYGONS,   // 使用多边形来生成字符,每个顶点具有独立的法线
        gmf);    //一个接收字形度量数据的数组的地址,每个数组元素用它对应的显示列表字符的数据填充
    }

       
    The following code is pretty simple. It deletes the 256 display lists from memory starting at the first list specified by base. I'm not sure if Windows would do this for you, but it's better to be safe than sorry :)   
       

    GLvoid KillFont(GLvoid)      // 删除显示列表
    {
     glDeleteLists(base, 256);     // 删除256个显示列表
    }

       
    下面就是我优异的GL文字程序了。你可以通过调用glPrint(“需要写的文字”)来调用这段代码。文字被存储在字符串text[]中。  
       

    GLvoid glPrint(const char *fmt, ...)     // 自定义GL输出字体函数
    {

       
    下面的第一行定义了一个叫做length的变量。我们使用这个变量来查询字符串的长度。第二行创建了一个大小为256个字符的字符数组,里面保存我们想要的文字串。第三行创建了一个指向一个变量列表的指针,我们在传递字符串的同时也传递了这个变量列表。如果我们传递文字时也传递了变量,这个指针将指向它们。  
       

     float  length=0;     // 查询字符串的长度
     char  text[256];    // 保存我们想要的文字串
     va_list  ap;     // 指向一个变量列表的指针

       
    下面两行代码检查是否有需要显示的内容,如果什么也没有,屏幕上也就什么都没有。
      
       

     if (fmt == NULL)      // 如果无输入则返回
      return;     
       
    接下来三行代码将文字中的所有符号转换为它们的字符编号。最后,文字和转换的符号被存储在一个叫做“text”的字符串中。以后我会多解释一些有关字符的细节。  
       

     va_start(ap, fmt);      // 分析可变参数
         vsprintf(text, fmt, ap);    // 把参数值写入字符串
     va_end(ap);      // 结束分析

       
    感谢Jim Williams对下面一段代码的建议。以前我是用手工将文字置于中心的,而他的办法要好的多。
    我们从一个循环开始,它将逐个检查文本中的字符。我们通过strlen(text)得到文本的长度。设置好了循环以后,我们将通过加上每个字符的长度来增加length的值。当循环结束以后,被保存在length中的值就是整个字符串的长度。所以,如果我们要写的是“hello”,假设每个字符的长度都为10个单位,我们先给length的值加上第一个字母的长度10。然后,我们检查第二个字母的长度,它的长度也是10,所以length就变成10 + 10(20)。当我们检查完所有5个字母以后,length的值就会等于50(5 *10)。

    给出我们每个字符的长度的代码是gmf[text[loop]].gmfCellIncX。记住,gmf存储了我们每个显示列表的信息。如果loop等于0,text[loop]就是我们的字符串中的第一个字符。如果loop等于1,text[loop]就是我们的字符串中的第二个字符。gmfCellIncX告诉我们被选择的字符的长度。GmfCellIncX表示显示位置从已绘制上的上一个字符向右移动的真正距离,这样,字符之间就不会重叠在一起。同时,这个距离就是我们想得到的字符的宽度。你还可以通过gmfCelllncY命令来得到字符的高度。如果你是在垂直方向绘制文本而不是在水平方向时,这会很方便。

      
       

     for (unsigned int loop=0;loop<(strlen(text));loop++) // 查找整个字符串的长度
     {
      length+=gmf[text[loop]].gmfCellIncX;  
     }

       
    最后我们取出计算后得到的length,并把它变成负数(因为我们要将文本从屏幕中心左移从而把整个文本置于屏幕中间)。然后我们把length除以2。我们并不想移动整个文本的长度,只需要一半!  
       

     glTranslatef(-length/2,0.0f,0.0f);   // 把字符串置于最左边

       
    然后我们将GL_LIST_BIT压入属性堆栈,它会防止glListBase影响到我们的程序中的其它显示列表  
       

     glPushAttrib(GL_LIST_BIT);    // 把显示列表属性压入属性堆栈
     glListBase(base);     // 设置显示列表的基础值为0

       
    现在OpenGL知道字符的存放位置了,我们就可以让它在屏幕上显示文字了。GlCallLists会调用多个显示列表从而把整个文字的内容同时显示在屏幕上。
    下面的代码做后续工作。首先,它告诉OpenGL我们将要在屏幕上显示出显示列表中的内容。Strlen(text)函数用来计算我们将要显示在屏幕上的文字的长度。然后,OpenGL需要知道我们允许发送给它的列表的最大值。我们依然不能发送长度大于255的字符串。所以我们使用UNSIGNED_BYTE。(用0 - 255来表示我们需要的字符)。最后,我们通过传递字符串文字告诉OpenGL显示什么内容。

    也许你想知道为什么字符不会彼此重叠堆积在一起。那时因为每个字符的显示列表都知道字符的右边缘在那里,在写完一个字符后,OpenGL自动移动到刚写过的字符的右边,在写下一个字或画下一个物体时就会从GL移动到的最后的位置开始,也就是最后一个字符的右边。

    最后,我们将GL_LIST_BIT属性弹出堆栈,将GL恢复到我们使用glListBase(base)设置base之前的状态。

      
       

     glCallLists(strlen(text), GL_UNSIGNED_BYTE, text); // 调用显示列表绘制字符串
     glPopAttrib();     // 弹出属性堆栈
    }
       
    下面就是画图的代码了。我们从清除屏幕和深度缓存开始。我们调用glLoadIdentity()来重置所有东西。然后我们将坐标系向屏幕里移动十个单位。轮廓字体在透视图模式下表现非常好。你将文字移入屏幕越深,文字开起来就更小。文字离你越近,它看起来就更大。
    也可以使用glScalef(x,y,z)命令来操作轮廓字体。如果你想把字体放大两倍,可以使用glScalef(1.0f,2.0f,1.0f). 2.0f 作用在y轴, 它告诉OpenGL将显示列表的高度绘制为原来的两倍。如果2.0f作用在x轴,那么文本的宽度将变成原来的两倍。

      
       

    int DrawGLScene(GLvoid)     // 此过程中包括所有的绘制代码
    {
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕及深度缓存
     glLoadIdentity();     // 重置当前的模型观察矩阵
     glTranslatef(0.0f,0.0f,-10.0f);   // 移入屏幕一个单位

       
    在向屏幕里移动以后,我们希望文本能旋转起来。下面3行代码用来在3个轴上旋转屏幕。我将rot乘以不同的数,以便每个方向上的旋转速度不同。  
       

     glRotatef(rot,1.0f,0.0f,0.0f);   // 沿X轴旋转
     glRotatef(rot*1.5f,0.0f,1.0f,0.0f);   // 沿Y轴旋转
     glRotatef(rot*1.4f,0.0f,0.0f,1.0f);   // 沿Z轴旋转

       
    下面是令人兴奋的颜色循环了。照常,我们使用唯一递增的变量(rot)。颜色通过使用COS和SIN来循环变化。我将rot除以不同的数,这样每种颜色会以不同的速度递增。最终的效果非常好。
      
       

     // 根据字体位置设置颜色
     glColor3f(1.0f*float(cos(rot/20.0f)),1.0f*float(sin(rot/25.0f)),1.0f-0.5f*float(cos(rot/17.0f)));

       
    我最喜欢的部分,将文字写到屏幕上。我使用同将位图字体写到屏幕上相同的函数。将文字写在屏幕上,所有你要做的就是glPrint(“你想写的文字”)。很简单。
    在下面的代码中,我们要写的是NeHe,空格,破折号,空格,然后是rot的值除以50后的结果(为了减慢计数器)。如果这个数大于999.99,左边第四个数将被去掉(我们要求只显示小数点左边3位数字)。只显示小数点右边的两位数字。
      
       

     glPrint("NeHe - %3.2f",rot/50);    // 输出文字到屏幕

       
    然后增大旋转变量从而改变颜色并旋转文字。  
       

     rot+=0.5f;      // 增加旋转变量
     return TRUE;      // 成功返回
    }
       
    在这节课结束的时候,你应该已经学会在你的OpenGL程序中使用轮廓字体了。就像第13课,我曾在网上寻找一篇与这一课相似的教程,但是也没有找到。或许我的网站是第一个涉及这个主题同时又把它解释的简单易懂的C代码的网站吧。享用这篇教程,快乐编码!

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/16 12:03:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客4
    发贴心情 
    Lesson 14
       
    This tutorial is a sequel to the last tutorial. In tutorial 13 I taught you how to use Bitmap Fonts. In this tutorial I'll teach you how to use Outline Fonts.

    The way we create Outline fonts is fairly similar to the way we made the Bitmap font in lesson 13. However... Outline fonts are about 100 times more cool! You can size Outline fonts. Outline font's can move around the screen in 3D, and outline fonts can have thickness! No more flat 2D characters. With Outline fonts, you can turn any font installed on your computer into a 3D font for OpenGL, complete with proper normals so the characters light up really nice when light shines on them.

    A small note, this code is Windows specific. It uses the wgl functions of Windows to build the font. Apparently Apple has agl support that should do the same thing, and X has glx. Unfortunately I can't guarantee this code is portable. If anyone has platform independant code to draw fonts to the screen, send it my way and I'll write another font tutorial.

    We start off with the typical code from lesson 1. We'll be adding the stdio.h header file for standard input/output operations; the stdarg.h header file to parse the text and convert variables to text, and finally the math.h header file so we can move the text around the screen using SIN and COS.   
       

    #include <windows.h>  // Header File For Windows
    #include <math.h>  // Header File For Windows Math Library  ( ADD )
    #include <stdio.h>  // Header File For Standard Input/Output ( ADD )
    #include <stdarg.h>  // Header File For Variable Argument Routines ( ADD )
    #include <gl\gl.h>  // Header File For The OpenGL32 Library
    #include <gl\glu.h>  // Header File For The GLu32 Library
    #include <gl\glaux.h>  // Header File For The GLaux Library

    HDC  hDC=NULL; // Private GDI Device Context
    HGLRC  hRC=NULL; // Permanent Rendering Context
    HWND  hWnd=NULL; // Holds Our Window Handle
    HINSTANCE hInstance; // Holds The Instance Of The Application

       
    We're going to add 2 new variables. base will hold the number of the first display list we create. Each character requires it's own display list. The character 'A' is 65 in the display list, 'B' is 66, 'C' is 67, etc. So 'A' would be stored in display list base+65.

    Next we add a variable called rot. rot will be used to spin the text around on the screen using both SIN and COS. It will also be used to pulse the colors.   
       

    GLuint base;   // Base Display List For The Font Set ( ADD )
    GLfloat rot;   // Used To Rotate The Text  ( ADD )

    bool keys[256];  // Array Used For The Keyboard Routine
    bool active=TRUE;  // Window Active Flag Set To TRUE By Default
    bool fullscreen=TRUE; // Fullscreen Flag Set To Fullscreen Mode By Default

       
    GLYPHMETRICSFLOAT gmf[256] will hold information about the placement and orientation for each of our 256 outline font display lists. We select a letter by using gmf[num]. num is the number of the display list we want to know something about. Later in the code I'll show you how to find out the width of each character so that you can automatically center the text on the screen. Keep in mind that each character can be a different width. glyphmetrics will make our lives a whole lot easier.   
       

    GLYPHMETRICSFLOAT gmf[256]; // Storage For Information About Our Font

    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc

       
    The following section of code builds the actual font similar to the way we made our Bitmap font. Just like in lesson 13, this section of code was the hardest part for me to figure out.

    'HFONT font' will hold our Windows font ID.

    Next we define base. We do this by creating a group of 256 display lists using glGenLists(256). After the display lists are created, the variable base will hold the number of the first list.   
       

    GLvoid BuildFont(GLvoid)     // Build Our Bitmap Font
    {
     HFONT font;      // Windows Font ID

     base = glGenLists(256);     // Storage For 256 Characters

       
    More fun stuff. We're going to create our Outline font. We start off by specifying the size of the font. You'll notice it's a negative number. By putting a minus, we're telling windows to find us a font based on the CHARACTER height. If we use a positive number we match the font based on the CELL height.   
       

     font = CreateFont( -12,    // Height Of Font

       
    Then we specify the cell width. You'll notice I have it set to 0. By setting values to 0, windows will use the default value. You can play around with this value if you want. Make the font wide, etc.   
       

        0,    // Width Of Font

       
    Angle of Escapement will rotate the font. Orientation Angle quoted from MSDN help Specifies the angle, in tenths of degrees, between each character's base line and the x-axis of the device. Unfortunately I have no idea what that means :(   
       

        0,    // Angle Of Escapement
        0,    // Orientation Angle

       
    Font weight is a great parameter. You can put a number from 0 - 1000 or you can use one of the predefined values. FW_DONTCARE is 0, FW_NORMAL is 400, FW_BOLD is 700 and FW_BLACK is 900. There are alot more predefined values, but those 4 give some good variety. The higher the value, the thicker the font (more bold).   
       

        FW_BOLD,   // Font Weight

       
    Italic, Underline and Strikeout can be either TRUE or FALSE. Basically if underline is TRUE, the font will be underlined. If it's FALSE it wont be. Pretty simple :)   
       

        FALSE,    // Italic
        FALSE,    // Underline
        FALSE,    // Strikeout

       
    Character set Identifier describes the type of Character set you wish to use. There are too many types to explain. CHINESEBIG5_CHARSET, GREEK_CHARSET, RUSSIAN_CHARSET, DEFAULT_CHARSET, etc. ANSI is the one I use, although DEFAULT would probably work just as well.

    If you're interested in using a font such as Webdings or Wingdings, you need to use SYMBOL_CHARSET instead of ANSI_CHARSET.   
       

        ANSI_CHARSET,   // Character Set Identifier

       
    Output Precision is very important. It tells Windows what type of character set to use if there is more than one type available. OUT_TT_PRECIS tells Windows that if there is more than one type of font to choose from with the same name, select the TRUETYPE version of the font. Truetype fonts always look better, especially when you make them large. You can also use OUT_TT_ONLY_PRECIS, which ALWAYS trys to use a TRUETYPE Font.   
       

        OUT_TT_PRECIS,   // Output Precision

       
    Clipping Precision is the type of clipping to do on the font if it goes outside the clipping region. Not much to say about this, just leave it set to default.   
       

        CLIP_DEFAULT_PRECIS,  // Clipping Precision

       
    Output Quality is very important.you can have PROOF, DRAFT, NONANTIALIASED, DEFAULT or ANTIALIASED. We all know that ANTIALIASED fonts look good :) Antialiasing a font is the same effect you get when you turn on font smoothing in Windows. It makes everything look less jagged.   
       

        ANTIALIASED_QUALITY,  // Output Quality

       
    Next we have the Family and Pitch settings. For pitch you can have DEFAULT_PITCH, FIXED_PITCH and VARIABLE_PITCH, and for family you can have FF_DECORATIVE, FF_MODERN, FF_ROMAN, FF_SCRIPT, FF_SWISS, FF_DONTCARE. Play around with them to find out what they do. I just set them both to default.   
       

        FF_DONTCARE|DEFAULT_PITCH, // Family And Pitch

       
    Finally... We have the actual name of the font. Boot up Microsoft Word or some other text editor. Click on the font drop down menu, and find a font you like. To use the font, replace 'Comic Sans MS' with the name of the font you'd rather use.   
       

        "Comic Sans MS");  // Font Name

       
    Now we select the font by relating it to our DC.   
       

     SelectObject(hDC, font);    // Selects The Font We Created

       
    Now for the new code. We build our Outline font using a new command wglUseFontOutlines. We select our DC, the starting character, the number of characters to create and the 'base' display list value. All very similar to the way we built our Bitmap font.   
       

     wglUseFontOutlines( hDC,    // Select The Current DC
        0,    // Starting Character
        255,    // Number Of Display Lists To Build
        base,    // Starting Display Lists

       
    That's not all however. We then set the deviation level. The closer to 0.0f, the smooth the font will look. After we set the deviation, we get to set the font thickness. This describes how thick the font is on the Z axis. 0.0f will produce a flat 2D looking font and 1.0f will produce a font with some depth.

    The parameter WGL_FONT_POLYGONS tells OpenGL to create a solid font using polygons. If we use WGL_FONT_LINES instead, the font will be wireframe (made of lines). It's also important to note that if you use GL_FONT_LINES, normals will not be generated so lighting will not work properly.

    The last parameter gmf points to the address buffer for the display list data.   
       

        0.0f,    // Deviation From The True Outlines
        0.2f,    // Font Thickness In The Z Direction
        WGL_FONT_POLYGONS,  // Use Polygons, Not Lines
        gmf);    // Address Of Buffer To Recieve Data
    }

       
    The following code is pretty simple. It deletes the 256 display lists from memory starting at the first list specified by base. I'm not sure if Windows would do this for you, but it's better to be safe than sorry :)   
       

    GLvoid KillFont(GLvoid)      // Delete The Font
    {
     glDeleteLists(base, 256);    // Delete All 256 Characters
    }

       
    Now for my handy dandy GL text routine. You call this section of code with the command glPrint("message goes here"). Exactly the same way you drew Bitmap fonts to the screen in lesson 13. The text is stored in the char string fmt.   
       

    GLvoid glPrint(const char *fmt, ...)    // Custom GL "Print" Routine
    {

       
    The first line below sets up a variable called length. We'll use this variable to find out how our string of text is. The second line creates storage space for a 256 character string. text is the string we will end up printing to the screen. The third line creates a pointer that points to the list of arguments we pass along with the string. If we send any variables along with the text, this pointer will point to them.   
       

     float  length=0;    // Used To Find The Length Of The Text
     char  text[256];    // Holds Our String
     va_list  ap;     // Pointer To List Of Arguments

       
    The next two lines of code check to see if there's anything to display? If there's no text, fmt will equal nothing (NULL), and nothing will be drawn to the screen.   
       

     if (fmt == NULL)     // If There's No Text
      return;      // Do Nothing

       
    The following three lines of code convert any symbols in the text to the actual numbers the symbols represent. The final text and any converted symbols are then stored in the character string called "text". I'll explain symbols in more detail down below.   
       

     va_start(ap, fmt);     // Parses The String For Variables
         vsprintf(text, fmt, ap);    // And Converts Symbols To Actual Numbers
     va_end(ap);      // Results Are Stored In Text

       
    Thanks to Jim Williams for suggesting the code below. I was centering the text manually. His method works alot better :)

    We start off by making a loop that goes through all the text character by character. strlen(text) gives us the length of our text. After we've set up the loop, we will increase the value of length by the width of each character. When we are done the value stored in length will be the width of our entire string. So if we were printing "hello" and by some fluke each character was exactly 10 units wide, we'd increase the value of length by the width of the first letter 10. Then we'd check the width of the second letter. The width would also be 10, so length would become 10+10 (20). By the time we were done checking all 5 letters length would equal 50 (5*10).

    The code that gives us the width of each character is gmf[text[loop]].gmfCellIncX. remember that gmf stores information out each display list. If loop is equal to 0 text[loop] will be the first character in our string. If loop is equal to 1 text[loop] will be the second character in our string. gmfCellIncX tells us how wide the selected character is. gmfCellIncX is actually the distance that our display moves to the right after the character has been drawn so that each character isn't drawn on top of eachother. Just so happens that distance is our width :) You can also find out the character height with the command gmfCellIncY. This might come in handy if you're drawing text vertically on the screen instead of horizontally.   
       

     for (unsigned int loop=0;loop<(strlen(text));loop++) // Loop To Find Text Length
     {
      length+=gmf[text[loop]].gmfCellIncX;  // Increase Length By Each Characters Width
     }

       
    Finally we take the length that we calculate and make it a negative number (because we have to move left of center to center our text). We then divide the length by 2. We don't want all the text to move left of center, just half the text!   
       

     glTranslatef(-length/2,0.0f,0.0f);   // Center Our Text On The Screen

       
    We then push the GL_LIST_BIT, this prevents glListBase from affecting any other display lists we may be using in our program.

    The command glListBase(base) tells OpenGL where to find the proper display list for each character.   
       

     glPushAttrib(GL_LIST_BIT);    // Pushes The Display List Bits
     glListBase(base);     // Sets The Base Character to 0

       
    Now that OpenGL knows where the characters are located, we can tell it to write the text to the screen. glCallLists writes the entire string of text to the screen at once by making multiple display list calls for you.

    The line below does the following. First it tells OpenGL we're going to be displaying lists to the screen. strlen(text) finds out how many letters we're going to send to the screen. Next it needs to know what the largest list number were sending to it is going to be. We're still not sending any more than 255 characters. So we can use an UNSIGNED_BYTE. (a byte represents a number from 0 - 255 which is exactly what we need). Finally we tell it what to display by passing the string text.

    In case you're wondering why the letters don't pile on top of eachother. Each display list for each character knows where the right side of the character is. After the letter is drawn to the screen, OpenGL translates to the right side of the drawn letter. The next letter or object drawn will be drawn starting at the last location GL translated to, which is to the right of the last letter.

    Finally we pop the GL_LIST_BIT setting GL back to how it was before we set our base setting using glListBase(base).   
       

     glCallLists(strlen(text), GL_UNSIGNED_BYTE, text); // Draws The Display List Text
     glPopAttrib();      // Pops The Display List Bits
    }

       
    Resizing code is exactly the same as the code in Lesson 1 so we'll skip over it.

    There are a few new lines at the end of the InitGL code. The line BuildFont() from lesson 13 is still there, along with new code to do quick and dirty lighting. Light0 is predefined on most video cards and will light up the scene nicely with no effort on my part :)

    I've also added the command glEnable(GL_Color_Material). Because the characters are 3D objects you need to enable Material Coloring, otherwise changing the color with glColor3f(r,g,b) will not change the color of the text. If you're drawing shapes of your own to the screen while you write text enable material coloring before you write the text, and disable it after you've drawn the text, otherwise all the object on your screen will be colored.   
       

    int InitGL(GLvoid)      // All Setup For OpenGL Goes Here
    {
     glShadeModel(GL_SMOOTH);    // Enable Smooth Shading
     glClearColor(0.0f, 0.0f, 0.0f, 0.5f);   // Black Background
     glClearDepth(1.0f);     // Depth Buffer Setup
     glEnable(GL_DEPTH_TEST);    // Enables Depth Testing
     glDepthFunc(GL_LEQUAL);     // The Type Of Depth Testing To Do
     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations
     glEnable(GL_LIGHT0);     // Enable Default Light (Quick And Dirty) ( NEW )
     glEnable(GL_LIGHTING);     // Enable Lighting    ( NEW )
     glEnable(GL_COLOR_MATERIAL);    // Enable Coloring Of Material   ( NEW )

     BuildFont();      // Build The Font    ( ADD )

     return TRUE;      // Initialization Went OK
    }

       
    Now for the drawing code. We start off by clearing the screen and the depth buffer. We call glLoadIdentity() to reset everything. Then we translate ten units into the screen. Outline fonts look great in perspective mode. The further into the screen you translate, the smaller the font becomes. The closer you translate, the larger the font becomes.

    Outline fonts can also be manipulated by using the glScalef(x,y,z) command. If you want the font 2 times taller, use glScalef(1.0f,2.0f,1.0f). the 2.0f is on the y axis, which tells OpenGL to draw the list twice as tall. If the 2.0f was on the x axis, the character would be twice as wide.   
       

    int DrawGLScene(GLvoid)      // Here's Where We Do All The Drawing
    {
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
     glLoadIdentity();     // Reset The View
     glTranslatef(0.0f,0.0f,-10.0f);    // Move Ten Units Into The Screen

       
    After we've translated into the screen, we want the text to spin. The next 3 lines rotate the screen on all three axes. I multiply rot by different numbers to make each rotation happen at a different speed.   
       

     glRotatef(rot,1.0f,0.0f,0.0f);    // Rotate On The X Axis
     glRotatef(rot*1.5f,0.0f,1.0f,0.0f);   // Rotate On The Y Axis
     glRotatef(rot*1.4f,0.0f,0.0f,1.0f);   // Rotate On The Z Axis

       
    Now for the crazy color cycling. As usual, I make use of the only variable that counts up (rot). The colors pulse up and down using COS and SIN. I divide the value of rot by different numbers so that each color isn't increasing at the same speed. The final results are nice.   
       

     // Pulsing Colors Based On The Rotation
     glColor3f(1.0f*float(cos(rot/20.0f)),1.0f*float(sin(rot/25.0f)),1.0f-0.5f*float(cos(rot/17.0f)));

       
    My favorite part... Writing the text to the screen. I've used the same command we used to write Bitmap fonts to the screen. All you have to do to write the text to the screen is glPrint("{any text you want}"). It's that easy!

    In the code below we'll print NeHe, a space, a dash, a space, and then whatever number is stored in rot divided by 50 (to slow down the counter a bit). If the number is larger that 999.99 the 4th digit to the left will be cut off (we're requesting only 3 digits to the left of the decimal place). Only 2 digits will be displayed after the decimal place.   
       

     glPrint("NeHe - %3.2f",rot/50);    // Print GL Text To The Screen

       
    Then we increase the rotation variable so the colors pulse and the text spins.   
       

     rot+=0.5f;      // Increase The Rotation Variable
     return TRUE;      // Everything Went OK
    }

       
    The last thing to do is add KillFont() to the end of KillGLWindow() just like I'm showing below. It's important to add this line. It cleans things up before we exit our program.   
       

     if (!UnregisterClass("OpenGL",hInstance))  // Are We Able To Unregister Class
     {
      MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
      hInstance=NULL;     // Set hInstance To NULL
     }

     KillFont();      // Destroy The Font
    }

       
    At the end of this tutorial you should be able to use Outline Fonts in your own OpenGL projects. Just like lesson 13, I've searched the net looking for a tutorial similar to this one, and have found nothing. Could my site be the first to cover this topic in great detail while explaining everything in easy to understand C code? Enjoy the tutorial, and happy coding!

    Jeff Molofee (NeHe)

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/16 12:03:00
     
     ririyeye 帅哥哟,离线,有人找我吗?
      
      
      等级:大一新生
      文章:7
      积分:81
      门派:XML.ORG.CN
      注册:2009/12/3

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给ririyeye发送一个短消息 把ririyeye加入好友 查看ririyeye的个人资料 搜索ririyeye在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看ririyeye的博客5
    发贴心情 
    绝对的好东西
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2010/1/8 10:33:00
     
     ririyeye 帅哥哟,离线,有人找我吗?
      
      
      等级:大一新生
      文章:7
      积分:81
      门派:XML.ORG.CN
      注册:2009/12/3

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给ririyeye发送一个短消息 把ririyeye加入好友 查看ririyeye的个人资料 搜索ririyeye在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看ririyeye的博客6
    发贴心情 
    严重支持!
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2010/1/8 10:34:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/11/8 15:58:54

    本主题贴数6,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    218.750ms