NeHe OpenGL教程第四十三课,DancingWind翻译

Nehe SDK

NeHe Productions
第43课

在OpenGL中使用FreeType库

使用FreeType库可以创建非常好看的反走样的字体,记住暴雪公司就是使用这个库的,就是那个做魔兽世界的。尝试一下吧,我只告诉你了基本的使用方式,你可以走的更远。

在OpenGL中使用FreeType库

这里是一个快速的介绍,它告诉你如何在OpenGL中使用FreeType渲染TrueType字体。使用这个库我们可以渲染反走样的文本,它看起来更加的漂亮。

动机

这里我将给你两个例子,一个是用WGL的bitmap字体渲染得文字,另一个是用FreeType渲染得文字。

使用WGl渲染得文字是一些图像,当你放大它们时看起来如下:

如果你使用GNU的FreeType库(暴雪公司也在它们的游戏中使用这个库),它将看起来更漂亮,如下所示,它具有了反走样:

创建程序

第一步你需要从下面的网站上下载FreeType库:http://gnuwin32.sourceforge.net/packages/freetype.htm

接着在你使用它创建一个新的程序时,你需要链接libfreetype.lib库,并包含FreeType的头文件。

 

现在我们已经能创建基于FreeType的程序了,但我们还不能运行它,因为我们需要freetype-6.dll文件。

好了,现在我们可以开始编写我们的程序了,我们从13课的代码开始,我们添加两个新的文件"freetype.cpp"和"freetype.h"。我们把和FreeType相关的内容放在这两个文件里。

好了,让我们从freetype.h开始吧。

按惯例我们包含一些需要的头文件

#ifndef FREE_NEHE_H
#define FREE_NEHE_H

//FreeType 头文件
#include <ft2build.h>
#include <freetype/freetype.h>
#include <freetype/ftglyph.h>
#include <freetype/ftoutln.h>
#include <freetype/fttrigon.h>

//OpenGL 头文件
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>

//STL 头文件
#include <vector>
#include <string>

//STL异常类
#include <stdexcept>
#pragma warning(disable: 4786)

我们将把每个字符需要的信息封装在一个结构中,这样就像使用WGL字体一样,我们可以分别控制每个字符的显示状态。
// 把所有的操作放在名字空间freetype中,这样可以避免与其他函数的冲突
namespace freetype {

// 使用vector和string名字空间
using std::vector;
using std::string;

// 这个结构保存字体信息
struct font_data
{
float h; // 字体的高度
GLuint * textures; // 使用的纹理
GLuint list_base; // 显示列表的值

// 初始化结构
void init(const char * fname, unsigned int h);

// 清楚所有的资源
void clean();
};

最后一件事是定义我们输出字符串的原形:
// 把字符输出到屏幕
void print(const font_data &ft_font, float x, float y, const char *fmt, ...);

}

#endif

上面就是FreeType的头文件,下面我们看看怎样实现它
#include "freetype.h"

namespace freetype {

我们使用纹理去显示字符,在OpenGL中纹理大小必须为2的次方,这个函数用来字符的大小近似到这个值。所以我们有了如下的方程:
// 这个函数返回比a大的,并且是最接近a的2的次方的数
inline int next_p2 (int a )
{
int rval=1;
// rval<<=1 Is A Prettier Way Of Writing rval*=2;
while(rval<a) rval<<=1;
return rval;
}

下面一个函数为make_dlist, 它是这个代码的核心。它包含FT_Face对象,它是FreeType用来保存字体信息的类,接着创建一个显示列表。
// 为给定的字符创建一个显示列表
void make_dlist ( FT_Face face, char ch, GLuint list_base, GLuint * tex_base ) {

// 载入给定字符的轮廓
if(FT_Load_Glyph( face, FT_Get_Char_Index( face, ch ), FT_LOAD_DEFAULT ))
throw std::runtime_error("FT_Load_Glyph failed");

// 保存轮廓对象
FT_Glyph glyph;
if(FT_Get_Glyph( face->glyph, &glyph ))
throw std::runtime_error("FT_Get_Glyph failed");

// 把轮廓转化为位图
FT_Glyph_To_Bitmap( &glyph, ft_render_mode_normal, 0, 1 );
FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph;

// 保存位图
FT_Bitmap& bitmap=bitmap_glyph->bitmap;

}
现在我们已经从FreeType中获得了位图,我们需要把它转化为一个满足OpenGL纹理要求的位图。你必须知道,在OpenGL中位图表示黑白的数据,而在FreeType中我们使用8位的颜色表示位图,所以FreeType的位图可以保存亮度信息。
// 转化为OpenGl可以使用的大小
int width = next_p2( bitmap.width );
int height = next_p2( bitmap.rows );

// 保存位图数据
GLubyte* expanded_data = new GLubyte[ 2 * width * height];

// 这里我们使用8位表示亮度8位表示alpha值
for(int j=0; j <height;j++) {
for(int i=0; i < width; i++){
expanded_data[2*(i+j*width)]= expanded_data[2*(i+j*width)+1] =
(i>=bitmap.width || j>=bitmap.rows) ?
0 : bitmap.buffer[i + bitmap.width*j];
}
}

接下来我们选则字体纹理,并生成字体的贴图纹理
// 设置字体纹理的纹理过滤器
glBindTexture( GL_TEXTURE_2D, tex_base[ch]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

// 邦定纹理
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data );

// 释放分配的内存
delete [] expanded_data;

接着创建一个显示列表,它用来绘制一个字符
// 创建显示列表
glNewList(list_base+ch,GL_COMPILE);

glBindTexture(GL_TEXTURE_2D,tex_base[ch]);

//首先我们向左移动一点
glTranslatef(bitmap_glyph->left,0,0);

//接着我们向下移动一点,这只队'g','y'之类的字符有用
//它使得所有的字符都有一个基线

glPushMatrix();
glTranslatef(0,bitmap_glyph->top-bitmap.rows,0);

// 计算位图中字符图像的宽度
float x=(float)bitmap.width / (float)width,
y=(float)bitmap.rows / (float)height;

//绘制一个正方形,显示字符
glBegin(GL_QUADS);
glTexCoord2d(0,0); glVertex2f(0,bitmap.rows);
glTexCoord2d(0,y); glVertex2f(0,0);
glTexCoord2d(x,y); glVertex2f(bitmap.width,0);
glTexCoord2d(x,0); glVertex2f(bitmap.width,bitmap.rows);
glEnd();
glPopMatrix();
glTranslatef(face->glyph->advance.x >> 6 ,0,0);

//结束显示列表的绘制
glEndList();
}

下面的函数将使用make_dlist创建一个字符集的显示列表,fname为你要使用的FreeType字符文件。
void font_data::init(const char * fname, unsigned int h) {
// 保存纹理ID.
textures = new GLuint[128];

this->h=h;

// 创建FreeType库
FT_Library library;
if (FT_Init_FreeType( &library ))
throw std::runtime_error("FT_Init_FreeType failed");

// 在FreeType库中保存字体信息的类叫做face
FT_Face face;

// 使用你输入的Freetype字符文件初始化face类
if (FT_New_Face( library, fname, 0, &face ))
throw std::runtime_error("FT_New_Face failed (there is probably a problem with your font file)");

// 在FreeType中使用1/64作为一个像素的高度所以我们需要缩放h来满足这个要求
FT_Set_Char_Size( face, h << 6, h << 6, 96, 96);

// 创建128个显示列表
list_base=glGenLists(128);
glGenTextures( 128, textures );
make_dlist(face,i,list_base,textures);

// 释放face类
FT_Done_Face(face);

// 释放FreeType库
FT_Done_FreeType(library);
}

下面的函数完成释放资源的工作
void font_data::clean() {
glDeleteLists(list_base,128);
glDeleteTextures(128,textures);
delete [] textures;}
在print函数中要用到下面的两个方程,pushScreenCoordinateMatrix函数用来保存当前的矩阵,并设置视口与当前的窗口大小匹配。pop_projection_matrix函数用来返回pushScreenCoordinateMatrix保存的矩阵。reference manual.
// 保存当前的矩阵,并设置视口与当前的窗口大小匹配
inline void pushScreenCoordinateMatrix() {
glPushAttrib(GL_TRANSFORM_BIT);
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluOrtho2D(viewport[0],viewport[2],viewport[1],viewport[3]);
glPopAttrib();
}

//返回pushScreenCoordinateMatrix保存的矩阵
inline void pop_projection_matrix() {
glPushAttrib(GL_TRANSFORM_BIT);
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glPopAttrib();

我们的print函数和13课的函数非常的像,但在实现上有一些不同。我们实际上是使用2通道的纹理而不是图像。
// 输出文字
void print(const font_data &ft_font, float x, float y, const char *fmt, ...) {

// 保存当前矩阵
pushScreenCoordinateMatrix();

GLuint font=ft_font.list_base;
float h=ft_font.h/.63f;
char text[256];
va_list ap;

if (fmt == NULL)
*text=0;
else {
va_start(ap, fmt);
vsprintf(text, fmt, ap);
va_end(ap);
}

// 把输入的字符串按回车分割
const char *start_line=text;
vector<string> lines;
for(const char *c=text;*c;c++) {
if(*c=='\n') {
string line;
for(const char *n=start_line;n<c;n++) line.append(1,*n);
lines.push_back(line);
start_line=c+1;
}
}
if(start_line) {
string line;
for(const char *n=start_line;n<c;n++) line.append(1,*n);
lines.push_back(line);
}

glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT | GL_TRANSFORM_BIT);
glMatrixMode(GL_MODELVIEW);
glDisable(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glListBase(font);

	float modelview_matrix[16];     
glGetFloatv(GL_MODELVIEW_MATRIX, modelview_matrix);

// 下面的代码完成具体的绘制过程
for(int i=0;i<lines.size();i++) {
glPushMatrix();
glLoadIdentity();
glTranslatef(x,y-h*i,0);
glMultMatrixf(modelview_matrix);

//调用显示列表绘制
glCallLists(lines[i].length(), GL_UNSIGNED_BYTE, lines[i].c_str());

glPopMatrix();
}

glPopAttrib();

pop_projection_matrix();
}

}

}
FreeType库我们就写好了,现我们在13课的代码上来做一些修改,当然首先我们需要包含freetype.h的头文件
#include "freetype.h"
现在我们就可以调用freetype库绘制字符串了
// 保存我们创建的字体的信息
freetype::font_data our_font;
接下来使用test.ttf文件初始化字体
our_font.init("Test.ttf", 16);
在程序结束时记得释放内存资源
our_font.clean();
下面是我们具体的绘制函数
int DrawGLScene(GLvoid)										
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatef(0.0f,0.0f,-1.0f);

// 蓝色文字
glColor3ub(0,0,0xff);

// 绘制WGL文字
glRasterPos2f(-0.40f, 0.35f);
glPrint("Active WGL Bitmap Text With NeHe - %7.2f", cnt1);

// 红色文字
glColor3ub(0xff,0,0);

glPushMatrix();
glLoadIdentity();
glRotatef(cnt1,0,0,1);
glScalef(1,.8+.3*cos(cnt1/5),1);
glTranslatef(-180,0,0);
//绘制freetype文字
freetype::print(our_font, 320, 200, "Active FreeType Text - %7.2f", cnt1);
glPopMatrix();

cnt1+=0.051f;
cnt2+=0.005f;
return TRUE; // 成功返回
}

最后我们介绍一些实用的创建字体的相关站点

OGLFT 非常漂亮的基于FreeType2的字体库,下面是它的站点http://oglft.sourceforge.net.

FTGL 是为OS X设计的第三方字体库. http://homepages.paradise.net.nz/henryj/code/#FTGL.

FNT 一个非FreeType库,它使用自己定义的字体格式,但它具有非常好的界面http://plib.sourceforge.net/fnt.

版权与使用声明:
我是个对学习和生活充满激情的普通男孩,在网络上我以DancingWind为昵称,我的联系方式是[email protected],如果你有任何问题,都可以联系我。

引子
网络是一个共享的资源,但我在自己的学习生涯中浪费大量的时间去搜索可用的资料,在现实生活中花费了大量的金钱和时间在书店中寻找资料,于是我给自己起了个昵称DancingWind,其意义是想风一样从各个知识的站点中吸取成长的养料。在飘荡了多年之后,我决定把自己收集的资料整理为一个统一的资源库。

版权声明
所有DancingWind发表的内容,大多都来自共享的资源,所以我没有资格把它们据为己有,或声称自己为这些资源作出了一点贡献。故任何人都可以复制,修改,重新发表,甚至以自己的名义发表,我都不会追究,但你在做以上事情的时候必须保证内容的完整性,给后来的人一个完整的教程。最后,任何人不能以这些资料的任何部分,谋取任何形式的报酬。

发展计划
在国外,很多资料都是很多人花费几年的时间慢慢积累起来的。如果任何人有兴趣与别人共享你的知识,我很欢迎你与我联系,但你必须同意我上面的声明。

感谢
感谢我的母亲一直以来对我的支持和在生活上的照顾。
感谢我深爱的女友田芹,一直以来默默的在精神上和生活中对我的支持,她甚至把买衣服的钱都用来给我买书了,她真的是我见过的最好的女孩,希望我能带给她幸福。

源码 RAR格式