OpenGL Texture Mapped Outline Fonts 翻译

具有纹理贴图的轮廓字体(Texture Mapped Outline Fonts)

  在发布了前两篇关于位图字体和轮廓字体的教程以后,我收到很多邮件,很多读者都想知道如何才能给字体赋予纹理贴图。你可以使用自动纹理坐标生成器。它会为字体上的每一个多边形生成纹理坐标。

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

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

下面这部分代码类似于第14课的代码,但是这次我们还要加上stdarg.h头文件。

#include <windows.h> // Header File For Windows
#include <math.h> // Header File For Math Library
#include <stdio.h> // Header File For Standard Input/Output
#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

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


我们还要添加一个叫做texture[]的整型变量。它用于保存纹理。后面3行是第14课中的代码,本课不做改动。

GLuint texture[1]; // One Texture Map
GLuint base; // Base Display List For The Font Set
GLfloat rot; // Used To Rotate The Text
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);// Declaration For WndProc


  下面的部分做了一些小改动。我打算在这课使用wingdings字体来显示一个海盗旗(骷髅头和十字骨头)的标志。如果你想显示文字的话,就不用改动第14课中的代码了,也可以选择另一种字体。

  有些人想知道如何使用wingdings字体,这也是我不用标准字体的一个原因。wingdings是一种符号字体,使用它时需要做一些改动。告诉Windows使用wingdings字体并不太简单。如果你把字体的名字改为wingdings,你会注意到字体其实并没有选到。你必须告诉Windows这种字体是一种符号字体而不是一种标准字符字体。后面会继续解释。

GLvoid BuildFont(GLvoid) // Build Our Bitmap Font
{
    GLYPHMETRICSFLOAT gmf[256]; // Address Buffer For Font Storage
    HFONT font; // Windows Font ID

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

    font = CreateFont( -12, // Height Of Font
                                    0, // Width Of Font
                                    0, // Angle Of Escapement
                                    0, // Orientation Angle
                                    FW_BOLD, // Font Weight
                                    FALSE, // Italic
                                    FALSE, // Underline
                                    FALSE, // Strikeout

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

                                    SYMBOL_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

既然我们已经选择了符号字符集标识符,我们就可以选择wingdings字体了!

                                    "Wingdings"); // Font Name

剩下几行代码没有变化

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

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

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

                                        0.1f, // 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
}

在ReSizeGLScene()函数之前,我们要加上下面一段代码来读取纹理。你可能会认得这些前几课中的代码。我们创建一个保存位图的地方,读取位图,告诉Windows生成一个纹理,并把它保存在texture[0]中。

我们创建一种细化纹理(mipmapped texture),这样会看起来好些。纹理的名字叫做lights.bmp。

AUX_RGBImageRec *LoadBMP(char *Filename) // Loads A Bitmap Image
{
    FILE *File=NULL; // File Handle

    if (!Filename) // Make Sure A Filename Was Given
    {
        return NULL; // If Not Return NULL
    }

    File=fopen(Filename,"r"); // Check To See If The File Exists

    if (File) // Does The File Exist?
    {
        fclose(File); // Close The Handle
        return auxDIBImageLoad(Filename);// Load The Bitmap And Return A Pointer
    }
    return NULL; // If Load Failed Return NULL
}

int LoadGLTextures() // Load Bitmaps And Convert To Textures
{
    int Status=FALSE; // Status Indicator

    AUX_RGBImageRec *TextureImage[1]; // Create Storage Space For The Texture

    memset(TextureImage,0,sizeof(void *)*1); // Set The Pointer To NULL

    if (TextureImage[0]=LoadBMP("Data/Lights.bmp")) // Load the Bitmap
    {
        Status=TRUE; // Set The Status To TRUE

        glGenTextures(1, &texture[0]); // Create The Texture

        // Build Linear Mipmapped Texture

        glBindTexture(GL_TEXTURE_2D, texture[0]);
        gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);

下面四行代码将为我们绘制在屏幕上的任何物体自动生成纹理坐标。函数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 - 每个人都喜欢。创建一种有金属质感的物体。

需要注意的是我省略了很多代码。我们还需要设置GL_OBJECT_PLANE,但是,默认参数就是我们想要的参数。如果你想了解更多,那就买一本好书,或者查阅MSDN。

        // Texturing Contour Anchored To The Object

        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);

        // Texturing Contour Anchored To The Object

        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        glEnable(GL_TEXTURE_GEN_S);
        glEnable(GL_TEXTURE_GEN_T);
    }

    if (TextureImage[0]) // If Texture Exists
    {
        if (TextureImage[0]->data) // If Texture Image Exists
        {
            free(TextureImage[0]->data); // Free The Texture Image Memory
        }
        free(TextureImage[0]); // Free The Image Structure
    }
    return Status; // Return The Status
}


在InitGL()的最后有几行新代码。BuildFont()被放到了读取纹理的代码之后。glEnable(GL_COLOR_MATERIAL) 这行被删掉了,如果你想使用glColor3f(r,g,b)来改变纹理的颜色,那么就把glEnable(GL_COLOR_MATERIAL)这行重新加到这部分代码中。

    int InitGL(GLvoid) // All Setup For OpenGL Goes Here
    {
        if (!LoadGLTextures()) // Jump To Texture Loading Routine
        {
            return FALSE; // If Texture Didn't Load Return FALSE
        }
        BuildFont(); // Build The Font

        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
        glEnable(GL_LIGHT0); // Quick And Dirty Lighting (Assumes Light0 Is Set Up)
        glEnable(GL_LIGHTING); // Enable Lighting
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations

启动2D纹理映射,并选择第一个纹理。这样就把第一个纹理映射到我们绘制在屏幕上的3D物体上了。如果你想加入更多的操作,可以按自己的意愿启动或禁用纹理映射。

        glEnable(GL_TEXTURE_2D); // Enable Texture Mapping
        glBindTexture(GL_TEXTURE_2D, texture[0]); // Select The Texture
        return TRUE; // Initialization Went OK

重置大小的代码没有变化,但DrawGLScene这部分代码有变化。

    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

这里是第一处变动。我们打算使用COS和SIN让物体绕着屏幕旋转而不是把它固定在屏幕中间。我们将把物体向屏幕里移动3个单位。在x轴,我们将移动范围限制在-1.1到+1.1之间。我们使用rot变量来控制左右移动。我们把上下移动的范围限制在+0.8到-0.8之间。同样使用rot变量来控制上下移动(最好充分利用你的变量)。

        Position The Texture
        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); // Rotate On The X Axis
        glRotatef(rot*1.2f,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

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

        glTranslatef(-0.35f,-0.35f,0.1f); // Center On X, Y, Z Axis

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

        glPrint("N"); // Draw A Skull And Crossbones Symbol
        rot+=0.1f; // Increase The Rotation Variable
        return TRUE; // Keep Going
    }

最后要做的事就是在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为你生成纹理坐标。在给你的字体或者是同类物体赋予纹理映射时,应该没有问题了,而且只需要改变两行代码,你就可以启用球体映射了,它的效果简直酷毙了!

posted on 2006-01-20 00:22 zmj 阅读(787) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理