李迎春 孙 卡
(1.南昌航空大学软件学院 南昌 330063)(2.南昌航空大学信息工程学院 南昌 330063)
由于OpenGL没有直接的文字(尤其是汉字)绘制支持,在需要输出文字的地方,需要进行特定的处理[1]。
使用OpenGL绘制文字主要有四种方法:1)使用glBitmap函数绘制单色位图文字。吴仁彪[2]等利用多核处理器的优势,针对实时性要求较高或进行大批量文本数据的绘制,提出基于多线程的OpenGL文本绘制方法;梁丽[3]等主要研究了3D字幕技术,采用OpenGL将具有视差的字母叠加在立体图像上实现3D效果;李雪[4]、马莉[5]、赵常寿[6]等采用Vega或Vega Primer与OpenGL混合编程,实现了Vega中汉字的二维显示。2)使用wglUseFontBitmap函数绘制平面2D文字。芮小平[7]等研究了实现平面栅格字体的绘制方法;李自胜[8]研究了基于OpenGL的Windows字体处理方法,并实现了2D汉字的显示。3)使用wglUseFontOutlines函数绘制3D轮廓文字。芮小平[7]、李杰[9]等结合显示列表技术实现了三维轮廓字体的显示;李雪[4]、马莉[5]等结合Vega软件和OpenGL图形库,实现了Vega中汉字的三维显示;李自胜[8]实现了三维汉字的绘制和显示;张秀山[10]等使用OpenGL在Windows下设计与实现了一个三维字幕动画函数库。4)使用纹理贴图绘制纹理文字。陈飞[11]等提出基于SDL和OpenGL跨平台图形库,采用生产者-消费者模型对中文字符串进行处理的实时绘制方法;兰一麟涛[12]等研究了OpenGL中的三维纹理贴图绘制技术。另外,侯学隆[13]等研究了基于GDI的OpenGL多国文字渲染技术;薛娟[14]等使用TrueType字体,实现了基于VxWorks的中文图形界面设计;刘亚丽[15]等提出了在VxWorks嵌入式系统下基于OpenGL的汉字输入及显示的实现方案。函数glBitmap只能处理单色位图,不便修改,数据会在主存与显存之间流动,效率不高。函数wglUseFontBitmap、wglUseFontOutlines结合显示列表技术可以提高绘制速度,但只适用Windows操作系统,且不提供字体修改,速度比较慢。使用纹理文字速度更快,纹理对象常驻GPU,而且能更好地控制几何状态,使用纹理文字比使用显示列表速度更快。
本文在综合分析以上几种文字绘制方法的基础上,提出了一种GDI和OpenGL结合的二维文字绘制方法[16],采用面向对象的思想,以C++作为开发语言,开发出了文字绘制的动态链接库,具有较强的灵活性和通用性。
文字的属性主要包括字体、字形、字号、加粗、下划线、删除线、颜色、边框、位置、对齐等。Windows提供了结构体LOGFONT来表达逻辑上的一个字体,其结构定义如表1所示,其中lfCharSet用来指定字符集,可以使用四种预定义值:ANSI_CHARSET,OEM_CHARSET,SYMBOL_CHARSET,UNICODE_CHARSET,CHINESEBIG5_CHARSET(繁体中文),GB2312_CHARSET(简体中文),DEFAULT_CHARSET等。
基于上述字体,使用Windows GDI创建空白位图,将文字在位图中渲染,渲染后的位图记录了文字的轮廓信息,文字轮廓覆盖的区域用1表示,文字轮廓未覆盖的区域用0表示,其创建流程如下。
1)创建Windows GDI设备环境;
表1 文字的属性
2)选择绘制当前文字所用的字体;
3)调用GetTextExtentPoint32函数,获得所绘文字的高度和宽度信息,该信息以像素为单位;
4)根据步骤3)获得位图范围,创建一个内存中的位图对象,并把它指定到设备环境中去;
5)为设备环境指定绘图参数,如背景颜色、文字颜色、背景模式等;
6)调用Windows GDI绘图函数TextOut在设备环境中绘图;此时创建的文字位图为设备相关位图(DDB),此种位图不具有自己的调色板信息,颜色模式必须与输出设备相一致,高度依赖输出设备,只能存在于内存中;
7)把将设备相关位图对象转换为设备无关位图(DIB),此类位图具有自己的调色板信息,不依赖系统的调色板,独立于设备。可以使用BITMAPINFO结构体来定义设备相关位图和设备无关位图的转换规则,BITMAPINFO结构体中的bmiHeader属性中包含了维度和与设备无关位图的颜色格式信息,bmiColors属性指定了数组位图中定义的颜色的数据类型;
8)将转换后的文字位图存储在一维数组中,OpenGL可以利用该位图并调用glBitmap函数直接绘制出基于位图的文字。由于该方法在屏幕上直接输出的是文字的位图信息(也称栅格信息),无法实现文字的放大、缩小、旋转和变形等操作,为解决该问题,需要将文字位图转换为文字纹理。
文字纹理创建的思路是将2.2节所创建的单色位图转换为RGBA格式。单色位图上的每个像素数据的长度为1位(即1/8个字节),文字轮廓处的数据值为二进制的1,非轮廓处的数值为二进制的0;RGBA格式中每个像素数据的长度为32位(即4个字节),以8位为1颜色单元,分别对应颜色R、G、B、A的数值,单色位图格式与RGBA格式的转换如图1所示,其步骤如下:
1)开辟存储RGBA格式数据的内存空间,其单位为unsigned char,长度为单色位图宽度乘以高度的4倍,即每个像素点占据32位的空间。
2)设置当前文字的颜色,该值使用4个字节来表示,按顺序分别为R、G、B、A,每种颜色的取值范围为[0,255];
3)按位遍历单色位图的数据,若当前单色位图上数据的值为1,则将RGBA格式数据存储空间上的连续4个字节用当前的颜色值来替换;若当前单色位图上数据的值为0,则将RGBA格式数据存储空间上的连续4个字节用(0,0,0,0)来替换;完成遍历后,即实现了单色位图到RGBA格式的转换,转换后的RGBA格式数据可以作为几何图形的纹理来使用。
图1 单色位图与RGBA格式的转换
文字绘制流程如图2所示,先用GDI把指定的文字绘制到内存中的Bitmap中,再把Bitmap转换成纹理,通过修改粘贴该纹理的几何图形的形状来实现文字的各种显示效果。由于该方法不涉及显示列表,在渲染常规(非变形、非旋转)文字时可以按图形的方式输出;在渲染非常规(变形、旋转)文字时,采用纹理贴图的方式进行,可以绘制出倾斜、旋转、缩放、对齐等效果。
图2 文字的绘制流程
根据2.2中的步骤创建文字位图之后,即可在OpenGL环境中进行文字的绘制。使用glRaster-Pos2d(GLdouble x,GLdouble y)函数来确定所绘文字在屏幕上的位置,其中的参数x、y为绘制文字的定位点,即该文字绘制在屏幕的何处;使用glBitmap(GLsizei width,GLsizei height,GLfloat xorig,GLfloat yorig,GLfloat xmove,GLfloat ymove,const GLubyte*bitmap)函数来实现位图的绘制。使用该方法绘制出的文字可以实现添加边框和对齐的特效,但无法实现旋转、缩放、变形的特效。
在实现边框特效时,只需要根据2.2节中步骤3)中位图宽度和高度,相对于文字定位点的位置,使用屏幕坐标进行绘制矩形即可。边框的绘制以文字位图的左下角的位置为参考点。
在实现对齐特效时,需要根据位图的宽度和高度对定位点进行平移,通过改变定位点的方式,达到文字的某种对齐效果。针对不同的对齐方式,只需以像素为单位,平移0个、1/2个或1个位图的高度和宽度即可。在本系统中定义了9种对齐方式,如图3所示,其中黑色的点代表定位点,对齐名称分别为:(a)左下对齐;(b)左中对齐;(c)左上对齐;(d)中下对齐;(e)居中对齐;(f)中上对齐;(g)右下对齐;(h)右中对齐;(i)右上对齐。
图3 文字的对齐方式
为了实现文字的缩放、旋转、变形等特效,需要使用纹理贴图的方式对文字进行绘制。使用纹理贴图的步骤如下:
1)使用glGenTextures(GLsizei n,GLuint*textures)函数,创建纹理ID;
2)使用glBindTextures(GLsizei n,GLuint*textures)函数实现纹理ID与当前纹理的绑定;
3)使用glTexParameteri(GLenum target,GLenumpname,GLint param)函数设置纹理参数;
4)使 用glTexEnvf(GLenum target,GLenum pname,GLfloat param)函数设置纹理环境;
5)使用glTexImage2D(GLenum target,GLint level,GLint internalformat,GLsizei width,GLsizei height,GLint border,GLenum format,GLenum type,const GLvoid*pixels)函数实现内存中纹理数据的传递。
6)使用glEnable(GLenum cap)函数分别启动纹理模式(GL_TEXTURE_2D)和透明测试模式(GL_ALPHA_TEST),并 调 用glAlphaFunc(GLenumfunc,GLclampf ref)函数设置透明贴图。
7)使用glRotated(GLdouble angle,GLdouble x,GLdouble y,GLdouble z)函数控制所绘纹理的旋转角度;
8)使用glScaled(GLdouble x,GLdouble y,GLdouble z)函数控制所绘文字的缩放比例;
9)使用glTexCoord2f(GLfloat s,GLfloat t)函数和glVertex2d(GLdouble x,GLdouble y)函数相结合,来控制纹理数据和几何数据之间的坐标映射,通过不同的映射方式,从而达到纹理变形的特效。
为了实现文字的绘制,本文利用C++语言,设计了9个与绘制相关的类,并依据这些类创建了动态链接库,类之间的关系如图4所示,每个类的功能如下:
1)顶点类(CVertex):定义二维的x、y坐标,用来记录某点与原点(0,0)的相对值。
2)颜色类(CColor):包含四个BYTE类型变量,表示RGBA颜色的四个分量,用来记录颜色值。
3)包围盒(CBoundBox):包含两个CVertex对象,分别记录左下角点和右上角点的位置,可以计算出所包围对象的宽度、高度、中心点,供绘制文字轮廓、对齐时使用。
4)位图字体类(CBitmapFont):用来创建文字的位图,先通过其包含的LOGFONT属性,用来设置所绘文字的字体、字形、字号、加粗、下划线、删除线等效果;然后调用其包含的CreateText方法,创建出所绘文字对应的位图。
5)对齐方式:对齐方式定义为枚举类型,记录文本内容与定位点的相对位置关系,列出了9种可选值,分别为左下对齐、左中对齐、左上对齐、中下对齐、居中对齐、中上对齐、右下对齐、右中对齐、右上对齐。
6)纹理类(CTexture):提供将位图数据转换为RGBA数据的接口,记录创建的纹理的宽度、高度、ID等参数,只记录纹理的参数信息,不负责纹理的绘制。
7)点状要素类(CPoint):包含一个CVertex对象,用来记录所绘文字的定位点,作为基类。
8)栅格文字类(CRasterText):用来绘制基于位图的文字,为CPoint类的子类,包含所绘文字的内容、字体、颜色、对齐方式、位图大小、是否绘制轮廓等属性,提供设置文本、设置颜色、设置字体、计算包围盒、平移、设置对齐方式、绘制等接口。该类为
(9)纹理文字类(CTextureText):用来绘制基于纹理的文字,为CRasterText类的子类,在其父类的基础上,包含X轴上的缩放系数、Y轴上的缩放系数、旋转角度、变形系数等属性,提供设置旋转角度、设置缩放系数、变形系数等接口,可以实现文字的旋转、缩放、变形等特效。该类为3.2节所描述绘制方法的代码实现。
图4 文字绘制类关系图
本文的试验环境:操作系统Windows XPSP3简体中文专业版;处理器:Interl(R)Core(TM)i7-2600;屏幕分辨率为1600×900;开发环境Visual Studio 2008;开发语言:Visual C++;库函数:GDI和OpenGL;窗口背景:调用glClearColor(0,0,0,0)函数,设置屏幕背景为黑色;绘制颜色:白色(255,255,255,255);对齐方式测试:文中所定义的9种对齐方式;旋转测试:每间隔45°旋转一次,旋转360°;缩放测试:整体放大2倍、缩小2倍、轴向放大;倾斜测试:将文字左右各倾斜30°;变形测试:采用顶部放大和底部放大的方式,构建出正梯形和倒梯形的文字效果;使用的逻辑字体的参数为
LOGFONTlFont;
lFont.lfHeight=32;
lFont.lfWidth=0;
lFont.lfEscapement=0;
lFont.lfOrientation=0;
lFont.lfWeight=400;
lFont.lfItalic=0;
lFont.lfUnderline=0;l
Font.lfStrikeOut=0;
lFont.lfCharSet=DEFAULT_CHARSET;
lFont.lfOutPrecision=OUT_TT_PRECIS;
lFont.lfClipPrecision= CLIP_DEFAULT_PRECIS;
lFont.lfQuality=DEFAULT_QUALITY;
lFont.lfPitchAndFamily= DEFAULT_PITCH |FF_MODERN;
strcpy_s(lFont.lfFaceName,32,“黑体”)。
本实验的运行结果如图5所示,实现了常规文字和非常规文字的输出,证明了该方法的正确性和有效性。与概述中提到的其他几种方法相比,可以方便修改文字字体、设置文字旋转、变形、对齐方式等效果,绘制速度更快,具有很好的灵活性和通用性。
图5 文字绘制测试结果
本文介绍了OpenGL中绘制二维文字的通用方法,设计了文字绘制类及动态链接库,使用GDI实现了位图文字到纹理文字的转换,达到了文字的各种特效。该方法已经在地图编制、地物标绘、信号模拟、仪表显示等相关项目中得到应用,取得了良好的效果。