OpenGL笔记16 文字绘制

增加了两个文件,showline.c, showtext.c。分别为第二个和第三个示例程序的main函数相关部分。
在ctbuf.h和textarea.h最开头部分增加了一句#include <stdlib.h>
附件中一共有三个示例程序:
第一个,飘动的“曹”字旗。代码为:flag.c, GLee.c, GLee.h
第二个,带缓冲的显示文字。代码为:showline.c, ctbuf.c, ctbuf.h, GLee.c, GLee.h
第三个,显示歌词。代码为:showtext.c, ctbuf.c, ctbuf.h, textarea.c, textarea.h, GLee.c, GLee.h
其中,GLee.h和GLee.c可以从网上下载,因此这里并没有放到附件中。在编译的时候应该将这两个文件和其它代码文件一起编译。

本课我们来谈谈如何显示文字。
OpenGL并没有直接提供显示文字的功能,并且,OpenGL也没有自带专门的字库。因此,要显示文字,就必须依赖操作系统所提供的功能了。
各种流行的图形操作系统,例如Windows系统和Linux系统,都提供了一些功能,以便能够在OpenGL程序中方便的显示文字。
  最常见的方法就是,我们给出一个字符,给出一个显示列表编号,然后操作系统由把绘制这个字符的OpenGL命令装到指定的显示列表中。当需要绘制字符的时候,我们只需要调用这个显示列表即可。
  不过,Windows系统和Linux系统,产生这个显示列表的方法是不同的(虽然大同小异)。作为我个人,只在Windows系统中编程,没有使用Linux系统的相关经验,所以本课我们仅针对Windows系统。

OpenGL版的“Hello, World!”
写完了本课,我的感受是:显示文字很简单,显示文字很复杂。看似简单的功能,背后却隐藏了深不可测的玄机。
呵呵,别一开始就被吓住了,让我们先从“Hello, World!”开始。
前面已经说过了,要显示字符,就需要通过操作系统,把绘制字符的动作装到显示列表中,然后我们调用显示列表即可绘制字符。
假如我们要显示的文字全部是ASCII字符,则总共只有0到127这128种可能,因此可以预先把所有的字符分别装到对应的显示列表中,然后在需要时调用这些显示列表。
Windows系统中,可以使用wglUseFontBitmaps函数来批量的产生显示字符用的显示列表。函数有四个参数:
第一个参数是HDC,学过Windows GDI的朋友应该会熟悉这个。如果没有学过,那也没关系,只要知道调用wglGetCurrentDC函数,就可以得到一个HDC了。具体的情况可以看下面的代码。
第二个参数表示第一个要产生的字符,因为我们要产生0到127的字符的显示列表,所以这里填0。
第三个参数表示要产生字符的总个数,因为我们要产生0到127的字符的显示列表,总共有128个字符,所以这里填128。
第四个参数表示第一个字符所对应显示列表的编号。假如这里填1000,则第一个字符的绘制命令将被装到第1000号显示列表,第二个字符的绘制命令将被装到第1001号显示列表,依次类推。我们可以先用glGenLists申请128个连续的显示列表编号,然后把第一个显示列表编号填在这里。
还要说明一下,因为wglUseFontBitmaps是Windows系统特有的函数,所以在使用前需要加入头文件:#include <windows.h>。
现在让我们来看具体的代码:

#include <windows.h>

// ASCII字符总共只有0到127,一共128种字符
#define MAX_CHAR       128

void drawString(const char* str) {
    static int isFirstCall = 1;
    static GLuint lists;

    if( isFirstCall ) { // 如果是第一次调用,执行初始化
                        // 为每一个ASCII字符产生一个显示列表
        isFirstCall = 0;

        // 申请MAX_CHAR个连续的显示列表编号
        lists = glGenLists(MAX_CHAR);

        // 把每个字符的绘制命令都装到对应的显示列表中
        wglUseFontBitmaps(wglGetCurrentDC(), 0, MAX_CHAR, lists);
    }
    // 调用每个字符对应的显示列表,绘制每个字符
    for(; *str!='\0'; ++str)
        glCallList(lists + *str);
}


显示列表一旦产生就一直存在(除非调用glDeleteLists销毁),所以我们只需要在第一次调用的时候初始化,以后就可以很方便的调用这些显示列表来绘制字符了。
绘制字符的时候,可以先用glColor*等指定颜色,然后用glRasterPos*指定位置,最后调用显示列表来绘制。

void display(void) {
    glClear(GL_COLOR_BUFFER_BIT);

    glColor3f(1.0f, 0.0f, 0.0f);
    glRasterPos2f(0.0f, 0.0f);
    drawString("Hello, World!");

    glutSwapBuffers();
}

效果如图:


指定字体
在产生显示列表前,Windows允许选择字体。
我做了一个selectFont函数来实现它,大家可以看看代码。

void selectFont(int size, int charset, const char* face) {
    HFONT hFont = CreateFontA(size, 0, 0, 0, FW_MEDIUM, 0, 0, 0,
        charset, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, face);
    HFONT hOldFont = (HFONT)SelectObject(wglGetCurrentDC(), hFont);
    DeleteObject(hOldFont);
}

void display(void) {
    selectFont(48, ANSI_CHARSET, "Comic Sans MS");

    glClear(GL_COLOR_BUFFER_BIT);

    glColor3f(1.0f, 0.0f, 0.0f);
    glRasterPos2f(0.0f, 0.0f);
    drawString("Hello, World!");

    glutSwapBuffers();
}


  最主要的部分就在于那个参数超多的CreateFont函数,学过Windows GDI的朋友应该不会陌生。没有学过GDI的朋友,有兴趣的话可以自己翻翻MSDN文档。这里我并不准备仔细讲这些参数了,下面的内容还多着呢:(

  如果需要在自己的程序中选择字体的话,把selectFont函数抄下来,在调用glutCreateWindow之后、在调用 wglUseFontBitmaps之前使用selectFont函数即可指定字体。函数的三个参数分别表示了字体大小、字符集(英文字体可以用 ANSI_CHARSET,简体中文字体可以用GB2312_CHARSET,繁体中文字体可以用CHINESEBIG5_CHARSET,对于中文的 Windows系统,也可以直接用DEFAULT_CHARSET表示默认字符集)、字体名称。
效果如图:


显示中文
原则上,显示中文和显示英文并无不同,同样是把要显示的字符做成显示列表,然后进行调用。
  但是有一个问题,英文字母很少,最多只有几百个,为每个字母创建一个显示列表,没有问题。但是汉字有非常多个,如果每个汉字都产生一个显示列表,这是不切实际的。
  我们不能在初始化时就为每个字符建立一个显示列表,那就只有在每次绘制字符时创建它了。当我们需要绘制一个字符时,创建对应的显示列表,等绘制完毕后,再将它销毁。
  这里还经常涉及到中文乱码的问题,我对这个问题也不甚了解,但是网上流传的版本中,使用了MultiByteToWideChar这个函数的,基本上都没有出现乱码,所以我也准备用这个函数:)
  通常我们在C语言里面使用的字符串,如果中英文混合的话,例如“this is 中文字符.”,则英文字符只占用一个字节,而中文字符则占用两个字节。用 MultiByteToWideChar函数,可以转化为所有的字符都占两个字节(同时解决了前面所说的乱码问题:))。
转化的代码如下:

// 计算字符的个数
// 如果是双字节字符的(比如中文字符),两个字节才算一个字符
// 否则一个字节算一个字符
len = 0;
for(i=0; str[i]!='\0'; ++i)
{
    if( IsDBCSLeadByte(str[i]) )
        ++i;
    ++len;
}

// 将混合字符转化为宽字符
wstring = (wchar_t*)malloc((len+1) * sizeof(wchar_t));
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len);
wstring[len] = L'\0';

// 用完后记得释放内存
free(wstring);

// 加上前面所讲到的wglUseFontBitmaps函数,即可显示中文字符了。
void drawCNString(const char* str) {
    int len, i;
    wchar_t* wstring;
    HDC hDC = wglGetCurrentDC();
    GLuint list = glGenLists(1);

    // 计算字符的个数
    // 如果是双字节字符的(比如中文字符),两个字节才算一个字符
    // 否则一个字节算一个字符
    len = 0;
    for(i=0; str[i]!='\0'; ++i)
    {
        if( IsDBCSLeadByte(str[i]) )
            ++i;
        ++len;
    }

    // 将混合字符转化为宽字符
    wstring = (wchar_t*)malloc((len+1) * sizeof(wchar_t));
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len);
    wstring[len] = L'\0';

    // 逐个输出字符
    for(i=0; i<len; ++i)
    {
        wglUseFontBitmapsW(hDC, wstring[i], 1, list);
        glCallList(list);
    }

    // 回收所有临时资源
    free(wstring);
    glDeleteLists(list, 1);
}


  注意我用了wglUseFontBitmapsW函数,而不是 wglUseFontBitmaps。wglUseFontBitmapsW是wglUseFontBitmaps函数的宽字符版本,它认为字符都占两个字节。因为这里使用了MultiByteToWideChar,每个字符其实是占两个字节的,所以应该用wglUseFontBitmapsW。

void display(void) {
    glClear(GL_COLOR_BUFFER_BIT);

    selectFont(48, ANSI_CHARSET, "Comic Sans MS");
    glColor3f(1.0f, 0.0f, 0.0f);
    glRasterPos2f(-0.7f, 0.4f);
    drawString("Hello, World!");

    selectFont(48, GB2312_CHARSET, "楷体_GB2312");
    glColor3f(1.0f, 1.0f, 0.0f);
    glRasterPos2f(-0.7f, -0.1f);
    drawCNString("当代的中国汉字");

    selectFont(48, DEFAULT_CHARSET, "华文仿宋");
    glColor3f(0.0f, 1.0f, 0.0f);
    glRasterPos2f(-0.7f, -0.6f);
    drawCNString("傳統的中國漢字");

    glutSwapBuffers();
}


效果如图:

纹理字体
把文字放到纹理中有很多好处,例如,可以任意修改字符的大小(而不必重新指定字体)。
  对一面飘动的旗帜使用带有文字的纹理,则文字也会随着飘动。这个技术在“三国志”系列游戏中经常用到,比如关羽的部队,旗帜上就飘着个“关”字,张飞的部 队,旗帜上就飘着个“张”字,曹操的大营,旗帜上就飘着个“曹”字。三国人物何其多,不可能为每种姓氏都单独制作一面旗帜纹理,如果能够把文字放到纹理上,则可以解决这个问题。(参见后面的例子:绘制一面“曹”字旗)
  如何把文字放到纹理中呢?自然的想法就是:“如果前面所用的显示列表,可以直接往纹理里面绘制,那就好了”。不过,“绘制到纹理”这种技术要涉及的内容可不少,足够我们专门拿一课的篇幅来讲解了。这里我们不是直接绘制到纹理,而是用简单一点的办法:先把汉字绘制出来,成为像素,然后用glCopyTexImage2D把像素复制为纹理。
  glCopyTexImage2D与glTexImage2D的用法是类似的,不过前者是直接把绘制好的像素复制到纹理中,后者是从内存传送数据到纹理中。要使用到的代码大致如下:

// 先把文字绘制好
glRasterPos2f(XXX, XXX);
drawCNString("關");

// 分配纹理编号
glGenTextures(1, &texID);
// 指定为当前纹理
glBindTexture(GL_TEXTURE_2D, texID);
// 把像素作为纹理数据
// 将屏幕(0, 0) 到 (64, 64)的矩形区域的像素复制到纹理中
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 64, 64, 0);
// 设置纹理参数
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR);

然后,我们就可以像使用普通的纹理一样来做了。绘制各种物体时,指定合适的纹理坐标即可。

有一个细节问题需要特别注意。大家看上面的代码,指定文字显示的位置,写的是glRasterPos2f(XXX, XXX);这里来讲讲如何计算这个显示坐标。
  让我们首先从计算文字的大小谈起。大家知道即使是同一字号的同一个文字,大小也可能是不同的,英文字母尤其如此,有的字体中大写字母O和小写字母l是一样宽的(比如Courier New),有的字体中大写字母O比较宽,而小写字母l比较窄(比如Times New Roman),汉字通常比英文字母要宽。
  为了计算文字的宽度,Windows专门提供了一个函数GetCharABCWidths,它计算一系列连续字符的ABC宽度。所谓ABC宽度,包括了 a, b, c三个量,a表示字符左边的空白宽度,b表示字符实际的宽度,c表示字符右边的空白宽度,三个宽度值相加得到整个字符所占宽度。如果只需要得 到总的宽度,可以使用GetCharWidth32函数。如果要支持汉字,应该使用宽字符版本,即GetCharABCWidthsW和 GetCharWidth32W。在使用前需要用MultiByteToWideChar函数,将通常的字符串转化为宽字符串,就像前面的 wglUseFontBitmapsW那样。
  解决了宽度,我们再来看看高度。本来,在指定字体的时候指定大小为s的话,所有的字符高度都为s,只有宽度不同。但是,如果我们使用glRasterPos2i(-1, -1)从最左下角开始显示字符的话,其实是不能得到完整的字符的:(。我们知道英文字母在写的时候可以分上中下三栏,这时绘制出来只有上、中两栏是可见的,下面一栏则不见了,字母g尤其明显。见下图:


  所以,需要把绘制的位置往上移一点,具体来说就是移动下面一栏的高度。这个高度是多少像素呢?这个我也不知道有什么好办法来计算,根据我的经验,移动整个字符高度的八分之一是比较合适的。例如字符大小为24,则移动3个像素。
  还要注意,OpenGL 2.0以前的版本,通常要求纹理的大小必须是2的整数次方,因此我们应该设置字体的高度为2的整数次方,例如16, 32, 64,这样用起来就会比较方便。
  现在让我们整理一下思路。首先要做的是将字符串转化为宽字符的形式,以便使用wglUseFontBitmapsW和GetCharWidth32W函数。 然后设置字体大小,接下来计算字体宽度,计算实际绘制的位置。然后产生显示列表,利用显示列表绘制字符,销毁显示列表。最后分配一个纹理编号,把字符像素复制到纹理中。
呵呵,内容已经不少了,让我们来看看代码。 

#define FONT_SIZE       64
#define TEXTURE_SIZE    FONT_SIZE

GLuint drawChar_To_Texture(const char* s) {
    wchar_t w;
    HDC hDC = wglGetCurrentDC();

    // 选择字体字号、颜色
    // 不指定字体名字,操作系统提供默认字体
    // 设置颜色为白色
    selectFont(FONT_SIZE, DEFAULT_CHARSET, "");
    glColor3f(1.0f, 1.0f, 1.0f);

    // 转化为宽字符
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, s, 2, &w, 1);

    // 计算绘制的位置
    {
        int width, x, y;
        GetCharWidth32W(hDC, w, w, &width);    // 取得字符的宽度
        x = (TEXTURE_SIZE - width) / 2;
        y = FONT_SIZE / 8;
        glWindowPos2iARB(x, y); // 一个扩展函数
    }

    // 绘制字符
    // 绘制前应该将各种可能影响字符颜色的效果关闭
    // 以保证能够绘制出白色的字符
    {
        GLuint list = glGenLists(1);

        glDisable(GL_DEPTH_TEST);
        glDisable(GL_LIGHTING);
        glDisable(GL_FOG);
        glDisable(GL_TEXTURE_2D);

        wglUseFontBitmaps(hDC, w, 1, list);
        glCallList(list);
        glDeleteLists(list, 1);
    }

    // 复制字符像素到纹理
    // 注意纹理的格式
    // 不使用通常的GL_RGBA,而使用GL_LUMINANCE4
    // 因为字符本来只有一种颜色,使用GL_RGBA浪费了存储空间
    // GL_RGBA可能占16位或者32位,而GL_LUMINANCE4只占4位
    {
        GLuint texID;
        glGenTextures(1, &texID);
        glBindTexture(GL_TEXTURE_2D, texID);
        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE4,
            0, 0, TEXTURE_SIZE, TEXTURE_SIZE, 0);
        glTexParameteri(GL_TEXTURE_2D,
            GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,
            GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        return texID;
    }
}

  为了方便,我使用了glWindowPos2iARB这个扩展函数来指定绘制的位置。如果某个系统中OpenGL没有支持这个扩展,则需要使用较多的代码来 实现类似的功能。为了方便的调用这个扩展,我使用了GLEE。详细的情形可以看本教程第十四课,最后的那一个例子。GL_ARB_window_pos扩展在OpenGL 1.3版本中已经成为标准的一部分,而几乎所有现在还能用的显卡在正确安装驱动后都至少支持OpenGL 1.4,所以不必担心不支持的问题。
  另外,占用的空间也是需要考虑的问题。通常,我们的纹理都是用GL_RGBA格式,OpenGL会保存纹理中每个像素的红、绿、蓝、 alpha四个值,通常,一个像素就需要16或32个二进制位才能保存,也就是2个字节或者4个字节才保存一个像素。我们的字符只有“绘制”和“不绘制” 两种状态,因此一个二进制位就足够了,前面用16个或32个,浪费了大量的空间。缓解的办法就是使用GL_LUMINANCE4这种格式,它不单独保存 红、绿、蓝颜色,而是把这三种颜色合起来称为“亮度”,纹理中只保存这种亮度,一个像素只用四个二进制位保存亮度,比原来的16个、32个要节省不少。注意这种格式不会保存alpha值,如果要从纹理中取alpha值的话,总是返回1.0。


应用纹理字体的实例:飘动的旗帜
(提示:这一段需要一些数学知识)
  有了纹理,只要我们绘制一个正方形,适当的设置纹理坐标,就可以轻松的显示纹理图象了(参见第十一课),因为这里纹理图象实际上就是字符,所以我们也就显示出了字符。并且,随着正方形大小的变化,字符的大小也会随着变化。
直接贴上纹理,太简单了。现在我们来点挑战性的:画一个飘动的曹操军旗帜。效果如下图,很酷吧?呵呵。


效果是不错,不过它也不是那么容易完成的,接下来我们一点一点的讲解。 

为了完成上面的效果,我们需要具备以下的知识:
1. 用多个四边形(实际上是矩形)连接起来,制作飘动的效果
2. 使用光照,计算法线向量
3. 把纹理融合进去

因为要使用光照,法线向量是不可少的。这里我们通过不共线的三个点来得到三个点所在平面的法线向量。
  从数学的角度看,原理很简单。三个点v1, v2, v3,可以用v2减v1,v3减v1,得到从v1到v2和从v1到v3的向量s1和s2。然后向量s1和s2进行叉乘,得到垂直于s1和s2所在平面的向量,即法线向量。
为了方便使用,应该把法线向量缩放至单位长度,这个也很简单,计算向量的模,然后向量的每个分量都除以这个模即可。

#include <math.h>

// 设置法线向量
// 三个不在同一直线上的点可以确定一个平面
// 先计算这个平面的法线向量,然后指定到OpenGL
void setNormal(const GLfloat v1[3], const GLfloat v2[3], const GLfloat v3[3]) {
    // 首先根据三个点坐标,相减计算出两个向量
    const GLfloat s1[] = {v2[0]-v1[0], v2[1]-v1[1], v2[2]-v1[2]};
    const GLfloat s2[] = {v3[0]-v1[0], v3[1]-v1[1], v3[2]-v1[2]};

    // 两个向量叉乘得到法线向量的方向
    GLfloat n[] = {
        s1[1]*s2[2] - s1[2]*s2[1],
        s1[2]*s2[0] - s1[0]*s2[2],
        s1[0]*s2[1] - s1[1]*s2[0]
    };

    // 把法线向量缩放至单位长度
    GLfloat abs = sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]);
    n[0] /= abs;
    n[1] /= abs;
    n[2] /= abs;

    // 指定到OpenGL
    glNormal3fv(n);
}

 

好的,飘动的旗帜已经做好,现在来看最后的步骤,将纹理贴到旗帜上。

细心的朋友可能会想到这样一个问题:明明绘制文字的时候使用的是白色,放到纹理中也是白色,那个“曹”字是如何显示为黄色的呢?
  这就要说到纹理的使用方法了。大家在看了第十一课“纹理的使用入门”以后,难免认为纹理就是用一幅图片上的像素颜色来替换原来的颜色。其实这只是纹理最简单的一种用法,它还可以有其它更复杂但是实用的用法。
  这里我们必须提到一个函数:glTexEnv*。从OpenGL 1.0到OpenGL 1.5,每个OpenGL版本都对这个函数进行了修改,如今它的功能已经变的非常强大(但同时也非常复杂,如果要全部讲解,只怕又要花费一整课的篇幅了)。
最简单的用法就是:

glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

它指定纹理的使用方式为“代替”,即用纹理中的颜色代替原来的颜色。
我们这里使用另一种用法:

GLfloat color[] = {1.0f, 1.0f, 0.0f, 1.0f};
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color);

  其中第二行指定纹理的使用方式为“混合”,它与OpenGL的混合功能类似,但源因子和目标因子是固定的,无法手工指定。最终产生的颜色为:纹理的颜色*常量颜色 + (1.0-纹理颜色)*原来的颜色。常量颜色是由第三行代码指定为黄色。
  因为我们的纹理里面装的是文字,只有黑、白两种颜色。如果纹理中某个位置是黑色,套用上面的公式,发现结果就是原来的颜色,没有变化;如果纹理中某个位置是白色,套用上面的公式,发现结果就是常量颜色。所以,文字的颜色就由常量颜色决定。我们指定常量颜色,也就指定了文字的颜色。

主要的知识就是这些了,结合前面课程讲过的视图变换(设置观察点)、光照(设置光源、材质),以及动画,飘动的旗帜就算制作完成。
呵呵,代码已经比较庞大了,限于篇幅,完整的版本这里就不发上来了,不过附件里面有一份源代码flag.c。

缓冲机制
  走出做完旗帜的喜悦后,让我们回到二维文字的问题上来。
  前面说到因为汉字的数目众多,无法在初始化时就为每个汉字都产生一个显示列表。不过,如果每次显示汉字时都重新产生显示列表,效率上也说不过去。一个好的办法就是,把经常使用的汉字的显示列表保存起来,当需要显示汉字时,如果这个汉字的显示列表已经保存,则不再需要重新产生。如果有很多的汉字都需要产生显示列表,占用容量过多,则删除一部分最近没有使用的显示列表,以便释放出一些空间来容纳新的显示列表。
  学过操作系统原理的朋友应该想起来了,没错,这与内存置换的算法是一样的。内存速度快但是容量小,硬盘(虚拟内存)速度慢但是容量大,需要找到一种机制,使性能尽可能的达到最高。这就是内存置换算法。
  常见的内存置换算法有好几种,这里我们选择一种简单的。那就是随机选择一个显示列表并且删除,空出一个位置用来装新的显示列表。
  还要说一下,我们不再直接用显示列表来显示汉字了,改用纹理。因为纹理更加灵活,而且根据实验,纹理比显示列表更快。一个显示列表只能保存一个字符,但是纹理只要足够大,则可以保存很多的字符。假设字符的高度是32,则宽度不超过32,如果纹理是256*256的话,就可以保存8行8列,总共64个汉字。
我们要做的功能:
1. 缓冲机制的初始化
2. 缓冲机制的退出
3. 根据一个文字字符,返回对应的纹理坐标。如果字符本身不在纹理中,则应该先把字符加入到纹理中(如果纹理已经装不下了,则先删除一个),然后返回纹理坐标。
要改进缓冲机制的性能,则应该使用更高效的置换算法,不过这个已经远超出OpenGL的范围了。大家如果有空也可以看看linux源码什么的,应该会找到好的置换算法。
  即使我们使用最简单的置换算法,完整的代码仍然有将近200行,其实这些都是算法基本功了,跟OpenGL关系并不太大。仍然是由于篇幅限制,仅在附件中给 出,就不贴在这里了。文件名为ctbuf.h和ctbuf.c,在使用的时候把这两个文件都加入到工程中,并调用ctbuf.h中声明的函数即可。
这里我们仅仅给出调用部分的代码。

#include "ctbuf.h"

void display(void) {
    static int isFirstCall = 1;

    if( isFirstCall ) {
        isFirstCall = 0;
        ctbuf_init(32, 256, "黑体");
    }

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glEnable(GL_TEXTURE_2D);
    glPushMatrix();
    glTranslatef(-1.0f, 0.0f, 0.0f);
    ctbuf_drawString("美好明天就要到来", 0.1f, 0.15f);
    glTranslatef(0.0f, -0.15f, 0.0f);
    ctbuf_drawString("Best is yet to come", 0.1f, 0.15f);
    glPopMatrix();

    glutSwapBuffers();
}



  注意这里我们是用纹理来实现字符显示的,因此文字的大小会随着窗口大小而变化。最初的Hello, World程序就不会有这样的效果,因为它的字体硬性的规定了大小,不如纹理来得灵活。 
 

显示大段的文字

有了缓冲机制,显示文字的速度会比没有缓冲时快很多,这样我们也可以考虑显示大段的文字了。
基本上,前面的ctbuf_drawString函数已经可以快速的显示一个较长的字符串,但是它有两个缺点。
第一个缺点是不会换行,一直横向显示到底。
第二个缺点是即使字符在屏幕以外,也会尝试在缓冲中查找这个字符,如果没找到,还会重新生成这个字符。

让我们先来看看第一个问题,换行。所谓换行其实就是把光标移动到下一行的开头,如果知道每一行开头的位置的话,只需要很短的代码就可以实现。
不过,OpenGL显示文字的时候并不会保存每一行开头的位置,所以这个需要我们自己动手来做。
  第二个问题是关于性能的,如果字符本身不会显示出来,那么为它产生显示列表和纹理就是一种浪费,如果为了容纳它的显示列表或者纹理,而把缓冲区中其它有用的字符的显示列表或者纹理给删除了,那就更加得不偿失。
所以,判断字符是否会显示也是很重要的。像我们的浏览器,如果显示一个巨大的网页,其实它也只绘制最必要的部分。
  为了解决上面两个问题,我们再单独的编写一个模块。初始化的时候指定显示区域的大小、每行多少个字符、每列多少个字符,在模块内部判断是否需要换行,以及判断每个文字是否真的需要显示。

呃,小小的感慨一下,为什么每当我做好一份代码,就发现它实在太长,长到我不想贴出来呢?唉……
先看看图:


注意观察就可以发现,歌词分为多行,只有必要的行才会显示,不会从头到尾的显示出来。
  代码中主要是算法和C语言基本功,跟OpenGL关系并不大。还是照旧,把主要的代码放到附件里,文件名为textarea.h和textarea.c,使用时要与前面的ctbuf.h和ctbuf.c一起使用。
这里仅给出调用部分的代码。 

const char* g_string =
    "《合金装备》(Metal Gear Solid)结尾曲歌词\n"
    // 歌词很多很长
    "因为。。。。。。。。 \n"
    "美好即将到来\n";

textarea_t* p_textarea = NULL;

void display(void) {
    static int isFirstCall = 1;

    if( isFirstCall ) {
        isFirstCall = 0;
        ctbuf_init(24, 256, "隶书");
        p_textarea = ta_create(-0.7f, -0.5f, 0.7f, 0.5f, 20, 10, g_string);
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    }

    glClear(GL_COLOR_BUFFER_BIT);

    // 显示歌词文字
    glEnable(GL_TEXTURE_2D);
    ta_display(p_textarea);

    // 用半透明的效果显示一个方框
    // 这个框是实际需要显示的范围
    glEnable(GL_BLEND);
    glDisable(GL_TEXTURE_2D);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
    glRectf(-0.7f, -0.5f, 0.7f, 0.5f);
    glDisable(GL_BLEND);

    // 显示一些帮助信息
    glEnable(GL_TEXTURE_2D);
    glPushMatrix();
    glTranslatef(-1.0f, 0.9f, 0.0f);
    ctbuf_drawString("歌词显示程序", 0.1f, 0.1f);
    glTranslatef(0.0f, -0.1f, 0.0f);
    ctbuf_drawString("按W/S键实现上、下翻页", 0.1f, 0.1f);
    glTranslatef(0.0f, -0.1f, 0.0f);
    ctbuf_drawString("按ESC退出", 0.1f, 0.1f);
    glPopMatrix();

    glutSwapBuffers();
}

 

轮廓字体

其实上面我们所讲那么多,只讲了一类字体,即像素字体,此外还有轮廓字体。所以,这个看似已经很长的课程,其实只讲了“显示文字”这个课题的一半。估计大家已经看不下去了,其实我也写不下去了。好长……
那么,本课就到这里吧。有种虎头蛇尾的感觉:(
小结

本课的内容不可谓不多。列表如下:
1. 以Hello, World开始,说明英文字符(ASCII字符)是如何绘制的。
2. 给出了一个设置字体的函数selectFont。
3. 讲了如何显示中文字符。
4. 讲了如何把字符保存到纹理中。
5. 给出了一个大的例子,绘制一面“曹”字旗。(附件flag.c)
6. 讲解了缓冲机制,其实跟内存的置换算法原理是一样的。我们给出了一个最简单的缓冲实现,采用随机的置换算法。(做成了模块,附件ctbuf.h,ctbuf.c,调用的例子在本课正文中可以找到)
7. 通过缓冲机制,实现显示大段的文字。主要是注意换行的处理,还有就是只显示必要的行。(做成了模块,附件textarea.h,textarea.c,调用的例子在本课正文中可以找到)
  最后两个模块虽然是以附件形式给出的,但是原理我想我已经说清楚了,并且这些内容跟OpenGL关系并不大,主要还是相关专业的知识,或者C语言基本功。主要是让大家弄清楚原理,附件代码只是作为参考用。
  说说我的感受:显示文字很简单,显示文字很复杂。除了最基本的显示列表、纹理等OpenGL常识外,更多的会涉及到数学、数据结构与算法、操作系统等各个领域。一个大型的程序通常都要实现一些文字特殊效果,仅仅是调用几个显示列表当然是不行的,需要大量的相关知识来支撑。
本课的门槛突然提高,搞得我都不知道这还算不算是“入门教程”了,希望各位不要退缩哦。祝大家愉快。

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.创建一个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.旗帜效果 (飘动的纹理): 这一章教程由Bosco带给你。他就是那个创造了很酷的小Demo: worthless的家伙。他喜欢每个人对他的Demo的反映,并且决定更进一步,在他的Demo的最后解释他怎么实现这么酷的效果的。这一章教程构建在第六章的代码之上。阅读完这一章之后,你将能弯曲,折叠以及操纵你自己的纹理。这绝对是个很漂亮的效果,并且比纹理固定的旗帜好得多。如果你喜欢这一章,请发邮件给bosco让他知道。 12.显示列表 想知道如何加速你的OpenGL程序么?每次为放置一个物体在屏幕上而写很多代码让你厌烦了吧?如果是这样,这一章就是为你准备的。学习如何用OpenGL来显示列表。只用一行代码预构建和显示物体。使用预编译物体加速你的程序。不要再一次又一次写重复的代码。让显示列表为你做所有的工作吧!这一章里我们将建造Q-bert金字塔(Q-bert是一款游戏),感谢显示列表,我们只需要用不多的几行代码。 13.位图字体 这一课我们将创建一些基于2D图像的字体,它们可以缩放,但不能旋转,并且总是面向前方,但作为基本的显示来说,我想已经够了。 14.图像字体 在一课我们将教你绘制3D的图形字体,它们可像一般的3D模型一样被变换。 15.图形字体的纹理映射: 这一课,我们将在上一课的基础上创建带有纹理的字体,它真的很简单。 16.看起来很酷的雾 这一课是基于第7课的代码的,你将学会三种不同的雾的计算方法,以及怎样设置雾的颜色和雾的范围。 17.2D 图像文字 在这一课中,你将学会如何使用四边形纹理贴图把文字显示在屏幕上。你将学会如何把256个不同的文字从一个256x256的纹理图像中分别提取出来,并为每一个文字创建一个显示列表,接着创建一个输出函数来创建任意你希望的文字。 18.二次几何体 利用二次几何体,你可以很容易的创建球,圆盘,圆柱和圆锥。 19.粒子系统 你是否希望创建爆炸,喷泉,流星之类的效果。这一课将告诉你如何创建一个简单的例子系统,并用它来创建一种喷射的效果。 20.蒙板 到目前为止你已经学会如何使用alpha混合,把一个透明物体渲染到屏幕上了,但有的使用它看起来并不是那么的复合你的心意。使用蒙板技术,将会按照你蒙板的位置精确的绘制。 21.线,反走样,计时,正投影和简单的声音 这是我第一个大的教程,它将包括线,反走样,计时,正投影和简单的声音。希望这一课中的东西能让每个人感到高兴。 22.凹凸映射,多重纹理扩展 这是一课高级教程,请确信你对基本知识已经非常了解了。这一课是基于第六课的代码的,它将建立一个非常酷的立体纹理效果。 23.球面映射 这一个将教会你如何把环境纹理包裹在你的3D模型上,让它看起来象反射了周围的场景一样。 24.符号,扩展,剪裁和TGA图像文件的加载 在这一课里,你将学会如何读取你显卡支持的OpenGL的扩展,并在你指定的剪裁区域把它显示出来。 25.变形和从文件中加载3D物体 在这一课中,你将学会如何从文件加载3D模型,并且平滑的从一个模型变换为另一个模型。 26.剪裁平面,蒙板缓存和反射 在这一课中你将学会如何创建镜面显示效果,它使用剪裁平面,蒙板缓存等OpenGL中一些高级的技巧。 27.阴影 这是一个高级的主题,请确信你已经熟练的掌握了基本的OpenGL,并熟悉蒙板缓存。当然它会给你留下深刻的印象的。 28.贝塞尔曲面 这是一课关于数学运算的,没有别的内容了。来,有信心就看看它吧。 29.Blitter 函数 类似于DirectDraw的blit函数,过时的技术,我们有实现了它。它非常的简单,就是把一块纹理贴到另一块纹理上。 30.碰撞检测 这是一课激动的教程,你也许等待它多时了。你将学会碰撞剪裁,物理模拟太多的东西,慢慢期待吧。 31.模型加载 你知道大名鼎鼎的Milkshape3D建模软件么,我们将加载它的模型,当然你可以加载任何你认为不错的模型。 32.拾取, Alpha混合, Alpha测试, 排序 这又是一个小游戏,交给的东西会很多,慢慢体会吧 33.加载压缩和未压缩的TGA文件 在这一课里,你将学会如何加载压缩和为压缩的TGA文件,由于它使用RLE压缩,所以非常的简单,你能很快地熟悉它的。 34.从高度图生成的美丽地形 这一课将教会你如何从一个2D的灰度图创建地形 35.在OpenGL中播放AVI视频 在OpenGL中如何播放AVI呢?利用Windows的API把每一帧作为纹理绑定到OpenGL中,虽然很慢,但它的效果不错。你可以试试。 36.放射模糊和渲染到纹理 如何实现放射状的滤镜效果呢,看上去很难,其实很简单。把渲染得图像作为纹理提取出来,在利用OpenGL本身自带的纹理过滤,就能实现这种效果,不信,你试试。 37.卡通映射 什么是卡通了,一个轮廓加上少量的几种颜色。使用一维纹理映射,你也可以实现这种效果。 38.从资源文件中载入图像 如何把图像数据保存到*.exe程序中,使用Windows的资源文件吧,它既简单又实用。 39.物理模拟简介 还记得高中的物理吧,直线运动,自由落体运动,弹簧。在这一课里,我们将创造这一切。 40.绳子的模拟 怎样模拟一根绳子呢,把它想象成一个个紧密排列的点,怎么样有了思路了吧,在这一课你你将学会怎样建模,简单吧,你能模拟更多。 41.体积雾 把雾坐标绑定到顶点,你可以在雾中漫游,体验一下吧。 42.多重视口 画中画效果,很酷吧。使用视口它变得很简单,但渲染四次可会大大降低你的显示速度哦:) 43.在OpenGL中使用FreeType库 使用FreeType库可以创建非常好看的反走样的字体,记住暴雪公司就是使用这个库的,就是那个做魔兽世界的。尝试一下吧,我只告诉你了基本的使用方式,你可以走的更远。 44.3D 光晕 当镜头对准太阳的时候就会出现这种效果,模拟它非常的简单,一点数学和纹理贴图就够了。好好看看吧。 45.顶点缓存 你想更快地绘制么?直接操作显卡吧,这可是前沿的图形技术,不要犹豫,我带你入门。接下来,你自己向前走吧。 46.全屏反走样 当今显卡的强大功能,你几乎什么都不用做,只需要在创建窗口的时候该一个数据。看看吧,驱动程序为你做完了一切。 47.CG 顶点脚本 nVidio的面向GPU的C语言,如果你相信它就好好学学吧,同样这里也只是个入门。记住,类似的语言还有微软的HLSL,OpenGLGLSL,ATI的shaderMonker。不要选错哦:) 48.轨迹球实现的鼠标旋转
本书介绍了应用OpenGL开发三维图形的方法。内容包括:怎样利用Visual C 5.0 MFC中的类建立OpenGL应用程序的基本格式,建立和控制2D形状和3D形体、标准二次曲面、Bezier曲线及曲面、NURBS曲线及曲面、点光源、无限光源、聚光、基本材质、贴图材质、Mip贴图、位图字符、文字OpenGL动画等,观察物体,操作像素、位图、图像,融合和反走样物体,雾化场景,利用帧缓存进行颜色屏蔽、深度控制、遮掩、全局场景反走样、景深模拟和场景对焦效果处理,利用对话框、菜单、鼠标选择物体、信息反馈实现OpenGL基本的交互图形程序设计。最后,本书通过6个实用例程说明如何将OpenGL的建模技术、图像处理技术和人机交互技术等3大技术进行结合。 本书涉及了所有的OpenGL核心函数和实用库函数,注重OpenGL的内存联系和实际运用。书中的每个工程文件模式固定且十分简洁,内容循序渐进。本书配有CD-ROM一张,包含了书中所有源程序和18个工程文件所生成的可执行文件等内容,帮助读者学习。本书适合于具有基本计算机图形学基础知识及Visual C 5.0 MFC编程基础知识的计算机软件开 图书目录: 第1章 走进三维真彩世界 第2章 OpenGL概述 第3章 极小化OpenGL 第4章 绘制二维图形 第5章 OpenGL变换 第6章 显示列表 第7章 模拟真实世界 第8章 OpenGL动画 第9章 OpenGL图像处理基础 第10章 纹理贴图 第11章 增强图像效果 第12章 实用库函数 第13章 曲线和曲面 第14章 高级制作技术 第15章 交互技术 第16章 制作技术与应用实例 附录 配书CD-ROM内容介绍
创建一个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.旗帜效果
第1章 初识openscenegraph(osg). 1 1.1 场景图形初步 2 1.1.1 场景图形的概念 2 1.1.2 具体实现:三维渲染引擎 2 1.1.3 主流渲染引擎介绍 3 1.2 openscenegraph概述 4 1.2.1 诞生与发展 4 1.2.2 优势与不足 5 1.3 openscenegraph的组成结构 6 1.3.1 核心结构 6 1.3.2 资源获取 8 1.3.3 中文社区 8 第2章 osg的安装与调试 9 2.1 快速安装和使用 10 2.1.1 下载预编译包 10 2.1.2 设置环境变量 11 2.1.3 建立工程环境 13 2.1.4 范例:第一个程序 15 2.2 从源代码进行编译 16 2.2.1 osg源代码的获取与更新 16 .2.2.2 编译环境生成工具cmake 19 2.2.3 基本编译选项 22 2.2.4 高级编译选项 25 2.3 调试输入与输出 28 2.3.1 命令行输入 28 2.3.2 调试输出 29 第3章 开发预备知识 31 3.1 基本数学组件 32 3.1.1 二维与多维向量 32 3.1.2 四元数 35 3.1.3 矩阵 37 3.1.4 包围体 41 3.2 数组对象 44 3.2.1 数据数组 44 3.2.2 数据索引数组 46 3.3 内存管理机制 47 3.3.1 智能指针 48 3.3.2 参照对象 51 3.3.3 范例:智能指针的使用 52 第4章 场景的组织结构 55 4.1 节点的定义与种类 56 4.1.1 场景图形bvh树 56 4.1.2 节点的父子关系 58 4.1.3 叶节点与组节点 59 4.1.4 节点的功能与分类 62 4.2 节点的访问 65 4.2.1 访问器机制 65 4.2.2 节点的遍历函数 67 4.2.3 范例:节点属性访问器 68 4.2.4 节点的更新与事件回调 70 4.2.5 范例:使用回调实现旋转动画 71 4.3 重要节点的功能实现 74 4.3.1 空间变换节点 74 4.3.2 范例:使用空间变换节点 79 4.3.3 开关节点 81 4.3.4 范例:使用开关节点 82 4.3.5 细节层次节点(lod) 83 4.3.6 范例:使用lod节点 85 4.3.7 范例:节点代理 86 第5章 绘制几何对象与文字 89 5.1 几何元素的储存 90 5.1.1 顶点属性 90 5.1.2 顶点数组、显示列表和vbo 91 5.1.3 构建几何体对象 94 5.1.4 范例:简易房屋 100 5.2 几何元素的绘制与访问 103 5.2.1 几何体的绘制实现函数 103 5.2.2 数据的更新显示 108 5.2.3 几何体的更新回调 109 5.2.4 范例:跃动的线 110 5.2.5 信息获取和统计 112 5.2.6 范例:使用仿函数遍历几何体 113 5.3 位图的显示 116 5.3.1 图像与图像的绘制 116 5.3.2 范例:在场景中绘制位图 119 5.4 文字的显示 120 5.4.1 文字绘制方法 120 5.4.2 文字绘制实现函数 123 5.4.3 字符编码格式 124 5.4.4 范例:一首古诗 127 第6章 设置纹理和渲染属性 131 6.1 渲染属性与模式 132 6.1.1 opengl中的渲染状态设置 132 6.1.2 节点的渲染状态集合 132 6.1.3 渲染属性概览 135 6.2 纹理与纹理属性 139 6.2.1 纹理的实现方法 139 6.2.2 纹理的分类 143 6.2.3 范例:场景中的纹理设置 146 6.2.4 范例:纹理的明细层次(mipmap) 149 6.3 属性的实现与访问 152 6.3.1 将属性应用到场景 152 6.3.2 渲染状态集回调 153 6.3.3 范例:雾参数的实时更新 153 6.4 osg与opengl着色语言 155 6.4.1 opengl着色语言 155 6.4.2 着色器属性 159 6.4.3 一致变量回调 162 6.4.4 范例:在场景中使用glsl着色语言 162 第7章 观察我们的世界 167 7.1 场景的观察与变换 168 7.1.1 opengl中的变换 168 7.1.2 相机节点 171 7.1.3 范例:鸟瞰图相机 174 7.2 图形设备接口 176 7.2.1 图形设备与相机 176 7.2.2 窗口与像素缓存(pixel buffer) 179 7.2.3 渲染到纹理(render to texture) 181 7.2.4 范例:将场景渲染到纹理 183 7.3 视景器 186 7.3.1 视景器的主要工作 186 7.3.2 单视景器与多视景器 188 7.3.3 范例:投影墙显示 191 7.3.4 范例:多视景器系统 192 7.3.5 视景器辅助部件 194 第8章 人机交互与图形用户接口 197 8.1 获取鼠标和键盘消息 198 8.1.1 事件适配器 198 8.1.2 动作适配器 202 8.1.3 事件队列与处理器 203 8.1.4 范例:处理键盘事件.. 205 8.2 三维人机交互工具 207 8.2.1 漫游器 207 8.2.2 拖曳器 210 8.2.3 范例:场景拖曳器的实现 214 8.3 二维图形用户接口 217 8.3.1 窗口设备 217 8.3.2 windows下窗口设备的实现 219 8.3.3 范例:使用windows api构建渲染窗口 221 第9章 场景中的动画效果 225 9.1 场景动画基本组件 226 9.1.1 关键帧 226 9.1.2 采样与插值 228 9.1.3 动画频道 231 9.1.4 动画更新回调 236 9.1.5 范例:关键帧路径动画 239 9.2 刚体动画 242 9.2.1 简单路径动画 242 9.2.2 范例:使用路径动画回调 244 9.2.3 动画的多频道融合 245 9.2.4 范例:基本动画管理器 246 9.3 角色与变形动画 249 9.3.1 骨骼动画 249 9.3.2 范例:骨骼运动 252 9.3.3 变形体 255 9.3.4 范例:对折硬纸 257 9.4 渲染状态与纹理动画 259 9.4.1 渐进动画(ease motion) 259 9.4.2 范例:物体的淡入淡出 262 9.4.3 纹理动画 264 9.4.4 范例:纹理动画效果 266 第10章 文件的读写机制 269 10.1 数据文件支持机制 270 10.1.1 文件格式概述 270 10.1.2 osg支持的文件格式 272 10.1.3 基本文件读写接口 277 10.2 文件读写插件 279 10.2.1 插件的编写和注册 279 10.2.2 插件的职责链机制 283 10.2.3 文件读写回调 285 10.3 插件设计方法 287 10.3.1 范例:简单插件设计 287 10.3.2 范例:文件读取进度 290 10.4 osg(即.osg)格式及其扩展 292 10.4.1 封装器 292 10.4.2 场景扩展库插件 295 第11章 场景的动态更新与裁减 297 11.1 场景的更新流程 298 11.1.1 人机交互事件的更新 298 11.1.2 用户请求与系统调度的更新 299 11.2 场景的裁减流程 300 11.2.1 裁减的意义与常用技术 300 11.2.2 裁减访问器 303 11.2.3 状态树与状态节点 305 11.2.4 状态树的构建 309 11.2.5 裁减回调 312 11.3 数据的动态调度 313 11.3.1 动态调度技术概述 313 11.3.2 分页数据库 314 11.3.3 范例:分页lod节点 316 11.3.4 分页图像库 318 第12章 场景的多线程渲染 319 12.1 多线程开发技术概述 320 12.1.1 多线程开发的常用概念 320 12.1.2 openthreads库简介 321 12.1.3 范例:线程的创建与控制 324 12.1.4 osg操作线程 325 12.2 基本场景渲染流程 327 12.2.1 osg状态机 327 12.2.2 构建场景渲染树 333 12.2.3 渲染树的优化排序 338 12.2.4 范例:广告牌森林 339 12.3 多种线程模型的讨论与实现 341 12.3.1 渲染器与场景视图 341 12.3.2 单线程模型 347 12.3.3 多设备裁减/绘制模型 348 12.3.4 多设备绘制模型 349 12.3.5 多相机绘制模型 350 12.3.6 数据变度 351 第13章 开源社区与未来 353 13.1 基于osg的开源工程 354 13.1.1 地形与地理信息 354 13.1.2 特效实现 356 13.1.3 扩展节点组件 358 13.1.4 数据和场景管理 358 13.1.5 其他语言封装 360 13.2 开发者资源 360 13.2.1 实用网址 360 13.2.2 用户群体简介 361 13.3 十条箴言 363 主要参考资料... 365
一、第一个OPENGL程序...................................................................................................................4 1.1、OPENGL的优点..........................................................................................................................4 1.2、OPENGL的安装..........................................................................................................................4 1.3、第一个OPENGL程序.................................................................................................................5 二、点、直线和多边形..........................................................................................................................8 2.1、点、直线和多边形.....................................................................................................................8 2.2、在OPENGL中指定顶点.............................................................................................................8 2.3、绘制曲线....................................................................................................................................9 三、绘制几何图形的细节....................................................................................................................14 3.1、关于点......................................................................................................................................14 3.2、关于直线..................................................................................................................................14 3.3、关于多边形...............................................................................................................................15 四、颜色的选择...................................................................................................................................20 4.1、RGBA颜色...............................................................................................................................20 4.2、索引颜色..................................................................................................................................21 4.3、指定清除屏幕用的颜色...........................................................................................................22 4.4、指定着色模型...........................................................................................................................23 五、 三维变换.....................................................................................................................................25 5.1、模型变换和视图变换...............................................................................................................25 5.2、投影变换..................................................................................................................................26 5.3、视口变换..................................................................................................................................28 5.4、操作矩阵堆栈...........................................................................................................................29 5.5、综合举例..................................................................................................................................29 六、 动画制作.....................................................................................................................................33 6.1、双缓冲技术...............................................................................................................................33 6.2、实现连续动画...........................................................................................................................34 6.3、垂直同步..................................................................................................................................35 6.4、计算帧速..................................................................................................................................36 七、光照...............................................................................................................................................40 7.1、建立光照模型...........................................................................................................................40 7.2、法线向量..................................................................................................................................41 7.3、控制光源..................................................................................................................................41 7.4、控制材质..................................................................................................................................43 7.5、选择光照模型...........................................................................................................................43 7.6、示例程序..................................................................................................................................44 八、 显示列表.....................................................................................................................................48 8.1、分配显示列表编号...................................................................................................................48 8.2、创建显示列表...........................................................................................................................49 8.3、调用显示列表...........................................................................................................................49 8.4、销毁显示列表...........................................................................................................................50 九、 混合.............................................................................................................................................56 9.1、源因子和目标因子...................................................................................................................56 9.2、二维图形混合举例...................................................................................................................57 9.3、实现三维混合...........................................................................................................................58 十、像素操作.......................................................................................................................................63 10.1、BMP文件格式简单介绍........................................................................................................63 10.2、简单的OPENGL像素操作.....................................................................................................64 10.3、GLREADPIXELS的用法和举例................................................................................................64 10.4、GLDRAWPIXELS的用法和举例..............................................................................................68 10.5、GLCOPYPIXELS的用法和举例................................................................................................70 十一、 纹理.........................................................................................................................................73 11.1、启用纹理和载入纹理.............................................................................................................74 11.2、纹理坐标................................................................................................................................75 11.3、纹理参数................................................................................................................................76 11.4、纹理对象................................................................................................................................77 11.5、示例程序................................................................................................................................79 十二、 测试操作.................................................................................................................................87 12.1、剪裁测试................................................................................................................................87 12.2、ALPHA测试.............................................................................................................................88 12.3、模板测试................................................................................................................................92 12.4、深度测试................................................................................................................................97 十三、 OPENGL的状态....................................................................................................................99 13.1、取得OPENGL的当前状态...................................................................................................100 13.2、设置OPENGL状态...............................................................................................................101 13.3、OPENGL工作流程................................................................................................................101 十四、 OPENGL的版本和OPENGL的扩展...............................................................................104 14.1、OPENGL的版本....................................................................................................................104 14.2、OPENGL的扩展....................................................................................................................106 14.3、工具软件...............................................................................................................................110 十五、OPENGL的演进....................................................................................................................112 15.1、从原始一点的办法开始.......................................................................................................112 15.2、顶点数组...............................................................................................................................115 15.3、顶点缓冲区对象...................................................................................................................118 十六、显示文字.................................................................................................................................122 16.1、OPENGL版的“HELLO, WORLD!”.........................................................................................122 16.2、指定字体...............................................................................................................................124 16.3、显示中文...............................................................................................................................126 16.4、纹理字体...............................................................................................................................128 16.5、应用纹理字体的实例:飘动的旗帜...................................................................................132 16.6、缓冲机制...............................................................................................................................137 16.7、轮廓字体...............................................................................................................................141

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值