网页功能: 加入收藏 设为首页 网站搜索  
Nehe OpenGL Lesson 13 Bitmap Fonts 翻译
发表日期:2007-03-22作者:[转贴] 出处:  

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

  当然,你可以载入一段美术程序,把文字写在一个图片上,再把这幅图片载入你的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 <windows.h> // Header File For Windows
#include <math.h> // Header File For Windows Math Library
#include <stdio.h> // Header File For Standard Input/Output
#include <stdarg.h> // Header File For Variable Argument Routines
#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

  另外,我们还要添加3个变量。base将保存我们创建的第一个显示列表的编号。每个字符都需要有自己的显示列表。例如,字符‘A’在显示列表中是65,‘B’是66,‘C’是67,等等。所以,字符‘A’应保存在显示列表中的base + 65这个位置。
  然后添加两个计数器(cnt1 和 cnt2),它们采用不用的累加速度,通过SIN和COS函数来改变文字在屏幕上的位置。在屏幕上创造出一种看起来像是半随机的移动方式。同时,我们用这两个计数器来改变文字的颜色(后面会进一步解释)。

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

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

下面这段代码用来构建真实的字体,这也是最难写的一部分代码。‘HFONT font’告诉Windows我们将要使用一个Windows字体。Oldfont用来存放字体。

接下来我们在定义base的同时使用glGenLists(96)创建了一组共96个显示列表。

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

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

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

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

    0, // Width Of Font

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, // Font Weight

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

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

Character Set Identifier(字符集标识符)用来描述你要使用的字符集(内码)类型。有太多需要说明的类型了。CHINESEBIG5_CHARSET,GREEK_CHARSET,RUSSIAN_CHARSET,DEFAULT_CHARSET ,等等。我使用的是ANSI,尽管DEFAULT也是很好用的。

如果你有兴趣使用Webdings或Wingdings等字体,你必须使用SYMBOL_CHARSET而不是ANSI_CHARSET。

    ANSI_CHARSET, // Character Set Identifier

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

    OUT_TT_PRECIS, // Output Precision

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

    CLIP_DEFAULT_PRECIS, // Clipping Precision

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

    ANTIALIASED_QUALITY, // Output 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"); // Font Name

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

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

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

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

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

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

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

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

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

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

接下来三行代码将文字中的所有符号转换为它们的字符编号。最后,文字和转换的符号被存储在一个叫做text的字符串中。以后我会多解释一些有关字符的细节。

    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

然后我们将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); // Pushes The Display List Bits
    glListBase(base - 32); // Sets The Base Character to 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);//DrawsThe Display ListText
    glPopAttrib(); // Pops The Display List Bits
}

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

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
}

下面就是画图的代码了。我们从清除屏幕和深度缓存开始。我们调用glLoadIdentity()来重置所有东西。然后我们将坐标系向屏幕里移动一个单位。如果不移动的话无法显示出文字。当你使用透视投影而不是ortho投影的时候位图字体表现的更好。由于ortho看起来不好,所以我用透视投影,并移动坐标系。。

你会注意到如果把坐标系在屏幕里放的更深远,字体并不会想你想象的那样缩小,只是你可以在控制文字位置时有更多的选择。如果你将坐标系移入屏幕一个单位,你就可以字X轴上-0.5到+0.5的范围内调整文字的位置。如果深入10个单位的话,移动范围就从-5到+5。它给了你更多的选择来替代使用小数指定文字的精确位置。什么都不能改变文字的大小,即使是调用glScale(x,y,z)函数.如果你想改变字体的大小,只能在创建它的时候改变它。

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

下面我们使用一些奇妙的数学方法来产生颜色变化脉冲。如果你不懂我在做什么你也不必担心。我喜欢利用教多的变量和教简单的方法来达到我的目的。

这样,我使用那两个用来改变文字在屏幕上位置的计数器来改变红、绿、蓝这些颜色。红色值使用COS和计数器1在-1.0到1.0之间变化。绿色值使用 SIN和计数器2也在-1.0到1.0之间变化。蓝色值使用COS和计数器1和2在0.5到1.5之间变化。于是,蓝色值就永远不会等于0,文字的颜色也永远不会消失。笨办法,但很管用。

    // 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)));

下面是一个新命令。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范围内移动。

    // Position The Text On The Screen
    glRasterPos2f(-0.45f+0.05f*float(cos(cnt1)), 0.32f*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); // Print GL Text To The Screen

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

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

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

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();

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

我来说两句】 【加入收藏】 【返加顶部】 【打印本页】 【关闭窗口
中搜索 Nehe OpenGL Lesson 13 Bitmap Fonts 翻译
本类热点文章
  DDraw和D3D立即模式编程手册
  矩阵求逆的快速算法
  本地化支持:OGRE+CEGUI中文输入:OGRE方..
  Direct3D中实现图元的鼠标拾取
  3D场景中的圆形天空顶
  OpenGL显卡编程
  一种高效的基于大规模地形场景的OCCLUS..
  一个完善的读取3DS文件例子
  如何制作一个可控制的人体骨骼模型
  Direct3D 入门之我见
  Slope(斜坡) 法线生成算法,在地形渲染..
  在Direct3D中渲染文字
最新分类信息我要发布 
最新招聘信息

关于我们 / 合作推广 / 给我留言 / 版权举报 / 意见建议 / 广告投放  
Copyright ©2003-2024 Lihuasoft.net webmaster(at)lihuasoft.net
网站编程QQ群   京ICP备05001064号 页面生成时间:0.00575