NeHe OpenGL教程 (十五)

本文介绍如何使用OpenGL为字体添加纹理映射,包括创建带有纹理的字体、使用wingdings字体显示特殊符号、自动生成纹理坐标等关键技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第15课
  
  图形字体的纹理映射:

这一课,我们将在上一课的基础上创建带有纹理的字体,它真的很简单。
 
 在发布了前两篇关于位图字体和轮廓字体的教程以后,我收到很多邮件,很多读者都想知道如何才能给字体赋予纹理贴图。你可以使用自动纹理坐标生成器。它会为字体上的每一个多边形生成纹理坐标。
一个小注释,这段代码是专门针对Windows写的,它使用了Windows的wgl函数来创建字体,显然,Apple机系统有agl,X系统有glx来支持做同样事情的,不幸的是,我不能保证这些代码也是容易使用的。如果哪位有能在屏幕上显示文字且独立于平台的代码,请告诉我,我将重写一个有关字体的教程。

我们将使用第14课的代码来创作纹理字体的演示。如果程序中哪部分的代码有变化,我会重写那部分的所有代码以便看出我做的改动。

 我们还要添加一个叫做texture[]的整型变量。它用于保存纹理。后面3行是第14课中的代码,本课不做改动。
 
 下面的部分做了一些小改动。我打算在这课使用wingdings字体来显示一个海盗旗(骷髅头和十字骨头)的标志。如果你想显示文字的话,就不用改动第14课中的代码了,也可以选择另一种字体。
有些人想知道如何使用wingdings字体,这也是我不用标准字体的一个原因。wingdings是一种符号字体,使用它时需要做一些改动。告诉Windows使用wingdings字体并不太简单。如果你把字体的名字改为wingdings,你会注意到字体其实并没有选到。你必须告诉Windows这种字体是一种符号字体而不是一种标准字符字体。后面会继续解释。

GLvoid BuildFont(GLvoid)                        // 创建位图字体
{
    GLYPHMETRICSFLOAT    gmf[256];                    // 记录256个字符的信息
    HFONT    font;                        // 字体句柄

    base = glGenLists(256);                    // 创建256个显示列表
    font = CreateFont(    -12,                    // 字体高度
                0,                // 字体宽度
                0,                // 字体的旋转角度 Angle Of Escapement
                0,                // 字体底线的旋转角度Orientation Angle
                FW_BOLD,                // 字体的重量
                FALSE,                // 是否使用斜体
                FALSE,                // 是否使用下划线
                FALSE,                // 是否使用删除线

 这就是有魔力的那一行!不使用第14课中的ANSI_CHARSET,我们将使用SYMBOL_CHARSET。这会告诉Windows我们创建的字体并不是由标准字符组成的典型字体。所谓符号字体通常是由一些小图片(符号)组成的。如果你忘了改变这行,wingdings,webdings以及你想用的其它符号字体就不会工作。
 
                SYMBOL_CHARSET,            // 设置字符集

 下面几行没有变化。
 
                OUT_TT_PRECIS,            // 输出精度
                CLIP_DEFAULT_PRECIS,        // 裁剪精度
                ANTIALIASED_QUALITY,        // 输出质量
                FF_DONTCARE|DEFAULT_PITCH,        // Family And Pitch
  
 既然我们已经选择了符号字符集标识符,我们就可以选择wingdings字体了 
  
                "Wingdings");            // 字体名称
  
 剩下几行代码没有变化 
 
    SelectObject(hDC, font);                    // 选择创建的字体

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

 我们允许有更多的误差,这意味着GL不会严格的遵守字体的轮廓线。如果你把误差设置为0.0f,你就会发现严格地在曲面上贴图存在一些问题。但是如果你允许一定的误差,很多问题都可以避免。
 
                0.1f,                // 字体的光滑度,越小越光滑,0.0为最光滑的状态

 下面三行代码还是相同的 
  
                0.2f,                // 在z方向突出的距离
                WGL_FONT_POLYGONS,            // 使用多边形来生成字符,每个顶点具有独立的法线
                gmf);                // 一个接收字形度量数据的数组的地址,每个数组元素用它对应的显示列表字符的数据填充
}

 在ReSizeGLScene()函数之前,我们要加上下面一段代码来读取纹理。你可能会认得这些前几课中的代码。我们创建一个保存位图的地方,读取位图,告诉Windows生成一个纹理,并把它保存在texture[0]中。
我们创建一种细化纹理(mipmapped texture),这样会看起来好些。纹理的名字叫做lights.bmp。
 
if (TextureImage[0]=LoadBMP("Data/Lights.bmp"))            // 载入位图

 下面四行代码将为我们绘制在屏幕上的任何物体自动生成纹理坐标。函数glTexGen非常强大,而且复杂,如果要完全讲清楚它的数学原理需要再写一篇教程。不过,你只要知道GL_S和GL_T是纹理坐标就可以了。默认状态下,它被设置为提取物体此刻在屏幕上的x坐标和y坐标,并把它们转换为顶点坐标。你会发现到物体在z平面没有纹理,只显示一些斑纹。正面和反面都被赋予了纹理,这些都是由glTexGen函数产生的。(X(GL_S)用于从左到右映射纹理,Y(GL_T)用于从上到下映射纹理。
GL_TEXTURE_GEN_MODE允许我们选择我们想在S和T纹理坐标上使用的纹理映射模式。你有3种选择:

GL_EYE_LINEAR - 纹理会固定在屏幕上。它永远不会移动。物体将被赋予处于它通过的地区的那一块纹理。

GL_OBJECT_LINEAR - 这种就是我们使用的模式。纹理被固定于在屏幕上运动的物体上。
GL_SPHERE_MAP - 每个人都喜欢。创建一种有金属质感的物体。

        // 设置纹理映射模式
        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        glEnable(GL_TEXTURE_GEN_S);                // 使用自动生成纹理
        glEnable(GL_TEXTURE_GEN_T);       
  
 在InitGL()的最后有几行新代码。BuildFont()被放到了读取纹理的代码之后。glEnable(GL_COLOR_MATERIAL) 这行被删掉了,如果你想使用glColor3f(r,g,b)来改变纹理的颜色,那么就把glEnable(GL_COLOR_MATERIAL)这行重新加到这部分代码中。 
  
int InitGL(GLvoid)                            // 此处开始对OpenGL进行所有设置
{
    if (!LoadGLTextures())                    // 载入纹理
    {
        return FALSE;                    // 失败则返回
    }
    BuildFont();                        // 创建字体显示列表

    glShadeModel(GL_SMOOTH);                    // 启用阴影平滑
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);            // 黑色背景
    glClearDepth(1.0f);                    // 设置深度缓存
    glEnable(GL_DEPTH_TEST);                    // 启用深度测试
    glDepthFunc(GL_LEQUAL);                    // 所作深度测试的类型
    glEnable(GL_LIGHT0);                    // 使用第0号灯
    glEnable(GL_LIGHTING);                    // 使用光照
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);        // 告诉系统对透视进行修正
  
 启动2D纹理映射,并选择第一个纹理。这样就把第一个纹理映射到我们绘制在屏幕上的3D物体上了。如果你想加入更多的操作,可以按自己的意愿启动或禁用纹理映射。 
  
    glEnable(GL_TEXTURE_2D);                    // 使用二维纹理
    glBindTexture(GL_TEXTURE_2D, texture[0]);            // 选择使用的纹理
    return TRUE;                        // 初始化成功
}

 重置大小的代码没有变化,但DrawGLScene这部分代码有变化。 
  
 这里是第一处变动。我们打算使用COS和SIN让物体绕着屏幕旋转而不是把它固定在屏幕中间。我们将把物体向屏幕里移动3个单位。在x轴,我们将移动范围限制在-1.1到+1.1之间。我们使用rot变量来控制左右移动。我们把上下移动的范围限制在+0.8到-0.8之间。同样使用rot变量来控制上下移动(最好充分利用你的变量)。 
  
    // 设置字体的位置
    glTranslatef(1.1f*float(cos(rot/16.0f)),0.8f*float(sin(rot/20.0f)),-3.0f);

 下面做常规的旋转。这会使符号在X,Y和Z轴旋转。 
  
    glRotatef(rot,1.0f,0.0f,0.0f);                // 沿X轴旋转
    glRotatef(rot*1.2f,0.0f,1.0f,0.0f);                // 沿Y轴旋转
    glRotatef(rot*1.4f,0.0f,0.0f,1.0f);                // 沿Z轴旋转

 我们将物体相对观察点向左向下移动一点,以便于把符号定位于每个轴的中心。否则,当我们旋转它的时候,看起来就不像是在围绕它自己的中心在旋转。-0.35只是一个能让符号正确显示的数。我也试过一些其它数,因为我不知道这种字体的宽度是多少,可以适情况作出调整。我不知道为什么这种字体没有一个中心。 
  
    glTranslatef(-0.35f,-0.35f,0.1f);                // 移动到可以显示的位置

 最后,我们绘制海盗旗的符号,然后增加rot变量,从而使这个符号在屏幕中旋转和移动。如果你不知道我是如何从字母‘N’中得到海盗旗符号的,那就打开Microsoft Word或是写字板。在字体下拉菜单中选择Wingdings字体。输入大写字母‘N’,就会显示出海盗旗符号了。 
  
    glPrint("N");                        // 绘制海盗旗符号
    rot+=0.1f;                        // 增加旋转变量

 最后要做的事就是在KillGLWindow()的最后添加KillFont()函数,如下所示。添加这行代码很重要。它将在我们退出程序之前做清理工作。
 
 尽管我没有讲的细致入微,但我想你应该很好的理解了如何让OpenGL为你生成纹理坐标。在给你的字体或者是同类物体赋予纹理映射时,应该没有问题了,而且只需要改变两行代码,你就可以启用球体映射了,它的效果简直酷毙了!

 


创建一个OpenGL窗口: 在这个教程里,我将教你在Windows环境中创建OpenGL程序.它将显示一个空的OpenGL窗口,可以在窗口和全屏模式下切换,按ESC退出.它是我们以后应用程序的框架. 理解OpenGL如何工作非常重要,你可以在教程的末尾下载源程序,但我强烈建议你至少读一遍教程,然后再开始编程. 2.你的第一个多边形: 在第一个教程的基础上,我们添加了一个三角形和一个四边形。也许你认为这很简单,但你已经迈出了一大步,要知道任何在OpenGL中绘制的模型都会被分解为这两种简单的图形。 读完了这一课,你会学到如何在空间放置模型,并且会知道深度缓存的概念。 3.添加颜色: 作为第二课的扩展,我将叫你如何使用颜色。你将理解两种着色模式,在左图中,三角形用的是光滑着色,四边形用的是平面着色。 注意三角形上的颜色是如何混合的。 颜色为OpenGlL 工程增加很多。通过理解平面着色(flat coloring)和平滑着色(smooth coloring),你能显著的改善你的OpenGL Demo的样子。 4.旋转: 在这一课里,我将教会你如何旋转三角形和四边形。左图中的三角形沿Y轴旋转,四边形沿着X 轴旋转。 这一章将引入两个变量, rtri 被用来存储三角形的角度, rquad存储四边形的角度。 和容易创建一个多边形组成的场景。让这些物体动起来是整个场景变得生动起来。在后面的课程钟我将教给你如何绕屏幕上的一个点旋转物体,使得物体绕屏幕而不是它的轴转动。 5.3D形体: 既然我们已经领会到多边形,方形,色彩和旋转。现在该建立3D物体了。我将使用多边形和矩形c创建3D物体。这次我们将扩展上一章的教程,并且将三角形转换成一个彩色的棱锥,把正方形变为一个实心正方体。棱锥使用混合色,正方体每个面使用一种颜色。在3D空间创建物体可能很费时间,但是所获得的结果(收获)值得这样做。充分发挥你的想象力吧。 6.纹理映射: 你想要它,它现在就在这里了,那就是 ... 纹理映射!!!在这一章我将教会你如何将一幅位图(bitmap)映射到正方体的六个面上去。我们将使用第一章的OpenGL代码来创建工程。创建一个空的窗口比修改上一课的代码更容易。 你将会发现第一章的代码在对于快速创建工程来说是及其有价值的。第一章的代码为你设置好了一切,你所需要做的只是集中精力为效果编程。 7.纹理滤波, 光照和键盘控制: 好的,我希望到现在你已经理解了所有的东西,因为这是一个巨大的教程。我想教给你两个新的方法来过滤(filter)你的纹理,简单的光照,键盘控制并且还可能更多 :) .如果你对到这一课为止你所学的东西并不充满信心,那就回头复习一下。玩一下其它课程的代码,不要操之过急。最好专心把每一课学好,而不是蜻蜓点水,只知道如何把东西做出来。 8.混合 有理由等一下,一个来自很酷的Hypercosm的程序员伙伴问(我)他是否可以写一章关于混合的教程。第八课通常正是讲混合的,所以太巧了。这一章教程扩展了第七章。混合是一项很酷的技术 .. 我希望你们能好好享受这一章教程。这一章的作者是Tom Stanis他在这制作一章上花费了很多精力,所以让他知道你觉得怎么样。混合可不是一个好讲的话题。 9.在3D空间中移动位图: 这一章覆盖了一些你们要求的主题,你想知道如何移动你在3D屏幕空间上创造的物体。你想要知道如何在屏幕上绘制一幅位图,并且位图的黑色部分不会覆盖它后面的东西。你想要简单的动画,想要更多的混合的应用,这一章将教会你所有这些。You'll notice there's no spinning boxes(yaker:很惭愧这一句我不是很明白)。前面的课程覆盖了OpenGL的基础,每一章都基于前面的内容。前面的课程涵盖了基础的OpenGL,每一课都是在前一课的基础上创建的。这一课是前面几课知识的综合,当你学习这课时,请确保你已经掌握了前面几课的知识。 10.加载3D世界,并在其中漫游: 你一直期待的教程来了!这一章友一个叫Lionel Brites的伙伴制作。这一课里你讲学到如何导入一个3D世界。代码仍然使用第一章的,但是,课程页面只是解释了新的部分,包括导入3D场景,在3D世界中移动。下载VC++代码并且在你阅读教程的同时阅读代码。按[B]键控制混合,[F]键控制滤波,[L]键控制光照(但光并不随场景移动),还有[Page UP]和[Page Down]键。我希望你能喜欢Lionel对网站的贡献。我有空的时候我会让这个教程更容易学习。 11.旗帜效果
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值