|
第14课 |
|
|
图形字体:
在一课我们将教你绘制3D的图形字体,它们可像一般的3D模型一样被变换。 |
|
|
|
这节课继续上一节课课的内容。在第13课我们学习了如何使用位图字体,这节课,我们将学习如何使用轮廓字体。
创建轮廓字体的方法类似于在第13课中我们创建位图字体的方法。但是,轮廓字体看起来要酷100倍!你可以指定轮廓字体的大小。轮廓字体可以在屏幕中以3D方式运动,而且轮廓字体还可以有一定的厚度!而不是平面的2D字符。使用轮廓字体,你可以将你的计算机中的任何字体转换为OpenGL中的3D字体,加上合适的法线,在有光照的时候,字符就会被很好的照亮了。
一个小注释,这段代码是专门针对Windows写的,它使用了Windows的wgl函数来创建字体,显然,Apple机系统有agl,X系统有glx来支持做同样事情的,不幸的是,我不能保证这些代码也是容易使用的。如果哪位有能在屏幕上显示文字且独立于平台的代码,请告诉我,我将重写一个有关字体的教程。
我们从第一课的典型代码开始,添加上stdio.h头文件以便进行标准输入/输出操作,另外,stdarg.h头文件用来解析文字以及把变量转换为文字。最后加上math.h头文件,这样我们就可以使用SIN和COS函数在屏幕中移动文字了。 |
|
|
另外,我们还要添加2个变量。base将保存我们创建的第一个显示列表的编号。每个字符都需要有自己的显示列表。例如,字符‘A’在显示列表中是65,‘B’是66,‘C’是67,等等。所以,字符‘A’应保存在显示列表中的base
+ 65这个位置。
我们再添加一个叫做rot的变量。用它配合SIN和COS函数在屏幕上旋转文字。我们同时用它来改变文字的颜色。
|
|
GLuint base;
GLfloat rot;
|
GLYPHMETRICSFLOAT gmf[256]用来保存256个轮廓字体显示列表中对应的每一个列表的位置和方向的信息。我们通过gmf[num]来选择字母。num就是我们想要了解的显示列表的编号。在稍后的代码中,我将说明如何如何检查每个字符的宽度,以便自动将文字定位在屏幕中心。切记,每个字符的宽度可以不相同。Glyphmetrics会大大简化我们的工作。 |
|
GLYPHMETRICSFLOAT gmf[256];
|
下面这段用来构建真正的字体的代码类似于我们创建位图字体的方法。和13课一样,只是使用wglUseFontOutlines函数替换wglUseFontBitmaps函数。
|
|
base = glGenLists(256);
wglUseFontOutlines( hDC,
0,
255,
base,
|
That's not all however. We then
set the deviation level. The closer to 0.0f, the smooth the font will look. After we set
the deviation, we get to set the font thickness. This describes how thick the font is on
the Z axis. 0.0f will produce a flat 2D looking font and 1.0f will produce a font with
some depth.
The parameter WGL_FONT_POLYGONS tells OpenGL to create a solid font using polygons. If we
use WGL_FONT_LINES instead, the font will be wireframe (made of lines). It's also
important to note that if you use GL_FONT_LINES, normals will not be generated so lighting
will not work properly.
The last parameter gmf points to the address buffer for the display list data. |
|
0.0f,
0.2f,
WGL_FONT_POLYGONS,
gmf);
}
|
The following code is pretty
simple. It deletes the 256 display lists from memory starting at the first list specified
by base. I'm not sure if Windows would do this for you, but it's better to be safe than
sorry :) |
|
GLvoid KillFont(GLvoid)
{
glDeleteLists(base, 256);
}
|
下面就是我优异的GL文字程序了。你可以通过调用glPrint(“需要写的文字”)来调用这段代码。文字被存储在字符串text[]中。 |
|
GLvoid glPrint(const char *fmt, ...)
{
|
下面的第一行定义了一个叫做length的变量。我们使用这个变量来查询字符串的长度。第二行创建了一个大小为256个字符的字符数组,里面保存我们想要的文字串。第三行创建了一个指向一个变量列表的指针,我们在传递字符串的同时也传递了这个变量列表。如果我们传递文字时也传递了变量,这个指针将指向它们。 |
|
float length=0;
char text[256];
va_list ap;
|
下面两行代码检查是否有需要显示的内容,如果什么也没有,屏幕上也就什么都没有。
|
|
if (fmt == NULL)
return;
|
接下来三行代码将文字中的所有符号转换为它们的字符编号。最后,文字和转换的符号被存储在一个叫做“text”的字符串中。以后我会多解释一些有关字符的细节。 |
|
va_start(ap, fmt);
vsprintf(text, fmt, ap);
va_end(ap);
|
感谢Jim Williams对下面一段代码的建议。以前我是用手工将文字置于中心的,而他的办法要好的多。
我们从一个循环开始,它将逐个检查文本中的字符。我们通过strlen(text)得到文本的长度。设置好了循环以后,我们将通过加上每个字符的长度来增加length的值。当循环结束以后,被保存在length中的值就是整个字符串的长度。所以,如果我们要写的是“hello”,假设每个字符的长度都为10个单位,我们先给length的值加上第一个字母的长度10。然后,我们检查第二个字母的长度,它的长度也是10,所以length就变成10
+ 10(20)。当我们检查完所有5个字母以后,length的值就会等于50(5 *10)。
给出我们每个字符的长度的代码是gmf[text[loop]].gmfCellIncX。记住,gmf存储了我们每个显示列表的信息。如果loop等于0,text[loop]就是我们的字符串中的第一个字符。如果loop等于1,text[loop]就是我们的字符串中的第二个字符。gmfCellIncX告诉我们被选择的字符的长度。GmfCellIncX表示显示位置从已绘制上的上一个字符向右移动的真正距离,这样,字符之间就不会重叠在一起。同时,这个距离就是我们想得到的字符的宽度。你还可以通过gmfCelllncY命令来得到字符的高度。如果你是在垂直方向绘制文本而不是在水平方向时,这会很方便。
|
|
for (unsigned int loop=0;loop<(strlen(text));loop++)
{
length+=gmf[text[loop]].gmfCellIncX;
}
|
最后我们取出计算后得到的length,并把它变成负数(因为我们要将文本从屏幕中心左移从而把整个文本置于屏幕中间)。然后我们把length除以2。我们并不想移动整个文本的长度,只需要一半! |
|
glTranslatef(-length/2,0.0f,0.0f);
|
然后我们将GL_LIST_BIT压入属性堆栈,它会防止glListBase影响到我们的程序中的其它显示列表 |
|
glPushAttrib(GL_LIST_BIT);
glListBase(base);
|
现在OpenGL知道字符的存放位置了,我们就可以让它在屏幕上显示文字了。GlCallLists会调用多个显示列表从而把整个文字的内容同时显示在屏幕上。
下面的代码做后续工作。首先,它告诉OpenGL我们将要在屏幕上显示出显示列表中的内容。Strlen(text)函数用来计算我们将要显示在屏幕上的文字的长度。然后,OpenGL需要知道我们允许发送给它的列表的最大值。我们依然不能发送长度大于255的字符串。所以我们使用UNSIGNED_BYTE。(用0
- 255来表示我们需要的字符)。最后,我们通过传递字符串文字告诉OpenGL显示什么内容。
也许你想知道为什么字符不会彼此重叠堆积在一起。那时因为每个字符的显示列表都知道字符的右边缘在那里,在写完一个字符后,OpenGL自动移动到刚写过的字符的右边,在写下一个字或画下一个物体时就会从GL移动到的最后的位置开始,也就是最后一个字符的右边。
最后,我们将GL_LIST_BIT属性弹出堆栈,将GL恢复到我们使用glListBase(base)设置base之前的状态。
|
|
glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);
glPopAttrib();
}
|
下面就是画图的代码了。我们从清除屏幕和深度缓存开始。我们调用glLoadIdentity()来重置所有东西。然后我们将坐标系向屏幕里移动十个单位。轮廓字体在透视图模式下表现非常好。你将文字移入屏幕越深,文字开起来就更小。文字离你越近,它看起来就更大。
也可以使用glScalef(x,y,z)命令来操作轮廓字体。如果你想把字体放大两倍,可以使用glScalef(1.0f,2.0f,1.0f).
2.0f 作用在y轴, 它告诉OpenGL将显示列表的高度绘制为原来的两倍。如果2.0f作用在x轴,那么文本的宽度将变成原来的两倍。
|
|
int DrawGLScene(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatef(0.0f,0.0f,-10.0f);
|
在向屏幕里移动以后,我们希望文本能旋转起来。下面3行代码用来在3个轴上旋转屏幕。我将rot乘以不同的数,以便每个方向上的旋转速度不同。 |
|
glRotatef(rot,1.0f,0.0f,0.0f);
glRotatef(rot*1.5f,0.0f,1.0f,0.0f);
glRotatef(rot*1.4f,0.0f,0.0f,1.0f);
|
下面是令人兴奋的颜色循环了。照常,我们使用唯一递增的变量(rot)。颜色通过使用COS和SIN来循环变化。我将rot除以不同的数,这样每种颜色会以不同的速度递增。最终的效果非常好。
|
|
glColor3f(1.0f*float(cos(rot/20.0f)),1.0f*float(sin(rot/25.0f)),1.0f-0.5f*float(cos(rot/17.0f)));
|
我最喜欢的部分,将文字写到屏幕上。我使用同将位图字体写到屏幕上相同的函数。将文字写在屏幕上,所有你要做的就是glPrint(“你想写的文字”)。很简单。
在下面的代码中,我们要写的是NeHe,空格,破折号,空格,然后是rot的值除以50后的结果(为了减慢计数器)。如果这个数大于999.99,左边第四个数将被去掉(我们要求只显示小数点左边3位数字)。只显示小数点右边的两位数字。 |
|
glPrint("NeHe - %3.2f",rot/50);
|
然后增大旋转变量从而改变颜色并旋转文字。 |
|
rot+=0.5f;
return TRUE;
}
|
在这节课结束的时候,你应该已经学会在你的OpenGL程序中使用轮廓字体了。就像第13课,我曾在网上寻找一篇与这一课相似的教程,但是也没有找到。或许我的网站是第一个涉及这个主题同时又把它解释的简单易懂的C代码的网站吧。享用这篇教程,快乐编码!
|
版权与使用声明:
我是个对学习和生活充满激情的普通男孩,在网络上我以DancingWind为昵称,我的联系方式是[email protected],如果你有任何问题,都可以联系我。
引子
网络是一个共享的资源,但我在自己的学习生涯中浪费大量的时间去搜索可用的资料,在现实生活中花费了大量的金钱和时间在书店中寻找资料,于是我给自己起了个昵称DancingWind,其意义是想风一样从各个知识的站点中吸取成长的养料。在飘荡了多年之后,我决定把自己收集的资料整理为一个统一的资源库。
版权声明
所有DancingWind发表的内容,大多都来自共享的资源,所以我没有资格把它们据为己有,或声称自己为这些资源作出了一点贡献。故任何人都可以复制,修改,重新发表,甚至以自己的名义发表,我都不会追究,但你在做以上事情的时候必须保证内容的完整性,给后来的人一个完整的教程。最后,任何人不能以这些资料的任何部分,谋取任何形式的报酬。
发展计划
在国外,很多资料都是很多人花费几年的时间慢慢积累起来的。如果任何人有兴趣与别人共享你的知识,我很欢迎你与我联系,但你必须同意我上面的声明。
感谢
感谢我的母亲一直以来对我的支持和在生活上的照顾。
感谢我深爱的女友田芹,一直以来默默的在精神上和生活中对我的支持,她甚至把买衣服的钱都用来给我买书了,她真的是我见过的最好的女孩,希望我能带给她幸福。
源码 RAR格式 |
|
|