OpenGL中描边字体的显示

 

OpenGL中显示中文的解决方案多种多样,很多文章已经对这个问题进行了深入的研究。总结起来,中文在OpenGL渲染环境中的显示主要采用两种方法:

1、预先将一个或多个表示中文文字的二值位图绑定为纹理,然后在指定位置绘制;

2、将中文绘制为二值位图,采用glbitmap方法,实时地绘制。

方法1需要预先绑定纹理,或是将要显示的字体一个一个绑定,在绘制时拼成一段文字,或是将一段文字绑定为一个纹理绘制。方法1比较简单,但相对而言不是非常灵活,例如,当程序运行时要在屏幕上输出某个没有绑定为纹理的文字时,就无法显示。方法2可以实时地改变需要绘制的文字,因为没有预先绑定纹理,这种方法可以在程序运行时随意改变显示的文字内容。事实上,方法2也更多地被采用。两种方法相同之处是,均采用绘制二值位图方式在OpenGL下绘制,显示的字体比较难以表现出更多样式,如描边的效果。Freetype2中给出了描边中文字体的解决方案,但其实现途径似乎比较复杂。

GDI+为中文字体的绘制提供了很好的解决方案,使用比较简单,能够很容易地和OpenGL结合起来绘制中文字体的各种效果。这里以一个单文档的MFC程序为例,把这种解决方案各个步骤归纳一下。

一、GDI+绘制描边字体

(1)使用GDI+,在视图类头文件中加入如下代码,启用GDI+。

#include <GdiPlus.h>
#pragma comment(lib, "gdiplus.lib")
using namespace Gdiplus;

并声明变量

Gdiplus::GdiplusStartupInput m_gdiplusStartupInput;
	ULONG_PTR m_gdiplusToken;


 

(2)在程序初始化时,启动GDI+。例如,在视图类的构造函数中加入:

Gdiplus::GdiplusStartup(&m_gdiplusToken, &m_gdiplusStartupInput, NULL);


 

(3)在程序结束时,关闭GDI+。同样,可以在视图类的析构函数中加入:

Gdiplus::GdiplusShutdown(m_gdiplusToken);


 

(4)用GDI+绘制描边字体

为视图类添加函数

GLuint GenerateFontTex_Outline( CString strFont, CString strContent, bool bShear, Color fontcolor, Color OutlineColor, float fSize, FontStyle sFontStyle)

 

函数返回一个绑定的纹理ID。其中的参数分别代表:

strFont:字体名称,宋体、黑体、还是其他

strContent:文字内容

bShear:是否反斜体

fontcolor:字体颜色

OutlineColor:描边颜色

fSize:字体大小

sFontStyle:字体风格:正常、加粗、斜体、下划线等等

函数中实现两个主要的功能,一是使用GDI+绘制描边字体到位图,二是利用该位图资源生成一个纹理,返回一个纹理ID。

一、使用GDI+绘制字体:

//*****************************************************************//

     GraphicsPath path;//定义一个图形路径,用来绘制

     Size RectBound;//定义要创建的位图尺寸

     StringFormat strformat;//字符串的格式

     strformat.SetAlignment(StringAlignmentNear);//设置为左对齐,为了准确地量测字符串的长度和宽度

     WCHAR *chName;

     USES_CONVERSION;

     chName = A2W( strContent.GetBuffer(0));//将传入的字符串转换为WCHAR类型

     Gdiplus::FontFamily fontFamily(strFont.AllocSysString()//);定义FontFamily,这里指定了要采用的字体类型

     path.AddString( chName,-1,&fontFamily,sFontStyle,fSize,PointF(0,0),&strformat);//将字符串加入图形路径中,这里指定了字符串、字体、字体样式、字体大小和对齐方式,为了准确地量测字符串的长宽

     RectF rcBound;

     path.GetBounds(&rcBound);//得到图形路径的大小,就是我们要创建的位图的大小

     RectBound.Width = int(rcBound.Width + rcBound.X);

     RectBound.Height = int(rcBound.Height + rcBound.Y);

     RectBound.Width = (int(RectBound.Width) + 31) & (~31);

     RectBound.Height = (int(RectBound.Height) + 31) & (~31);

 

     //创建一个带Alpha通道的位图

     PixelFormat PF;

     PF = PixelFormat32bppARGB;

     Bitmap bmp( RectBound.Width, RectBound.Height, PF );

 

     BitmapData * pBitmapData = new BitmapData;

     //在该位图上绘制字体

     Graphics graphic(&bmp); 

     graphic.SetSmoothingMode(SmoothingModeAntiAlias);

     graphic.SetInterpolationMode(InterpolationModeHighQualityBicubic);

//判断是否要反斜体绘制,如果是,则将图形的矩阵乘以一个切变矩阵,以实现斜体字的绘制

     if ( bShear )

     {

         Matrix matrix;

         matrix.Shear( 0.5,0 );

         graphic.MultiplyTransform( &matrix );

     }

 

     //定义GDI+字体绘制的大小,与位图等大

     RectF TitleRc(0, 0, (REAL) RectBound.Width, (REAL) RectBound.Height);

 

     //绘制外围轮廓

     Pen pen( OutlineColor, Gdiplus::REAL( fSize / 8.0));

     graphic.DrawPath(&pen, &path);

     ////绘制字体

     SolidBrush brush(fontcolor);

     graphic.FillPath(&brush, &path);

     bmp.RotateFlip( Rotate180FlipX );//这里记得要绕X轴旋转180度,因为位图的存储是上下翻转的;当然,也可以不在这里翻转,只是在绘制的时候需要让纹理坐标与顶点坐标的顺序反向。

//*****************************************************************//


 

至此,我们已经在一个创建的、带有Alpha通道的位图上绘制上了我们要的字符串。这个过程的主要步骤包括:根据字符串、字体大小、格式等参数得到需要创建的位图大小,然后创建等大的、带Alpha通道的位图,最后将字符串绘制到该位图上。这里使用Alpha通道的原因是为了在OpenGL绘制时,利用OpenGL的Blend功能将位图中背景去掉。接下来,就该将这个位图绑定为纹理。

//*****************************************************************//

 

     //得到位图的数据

     Gdiplus::Rect rc(0,0, RectBound.Width, RectBound.Height); 

     bmp.LockBits(&rc,ImageLockModeRead,PF,pBitmapData); 

     bmp.UnlockBits(pBitmapData);

     UCHAR * pData =(UCHAR *) pBitmapData->Scan0;

     GLuint nTexID;

     //绑定为纹理

     glGenTextures(1,&nTexID);

     glBindTexture(GL_TEXTURE_2D,nTexID);

     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);

     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

 

//绑定为四通道的纹理

     glTexImage2D(GL_TEXTURE_2D,0,4, RectBound.Width, RectBound.Height,0,GL_RGBA, GL_UNSIGNED_BYTE,pData);

     //释放开辟的内存

if( pBitmapData!= NULL) delete pBitmapData;

return nTexID;

//*****************************************************************//


这样,我们就得到了一个RGBA格式四通道的纹理。这个函数在视图类的OnCreate函数中调用,进行纹理的初始化。最后,在OpenGL中以混合方式绘制这个纹理就实现了描边字体的绘制。在上面的函数中,最好再返回一个位图的大小,以便绘制的时候知道纹理的大小。下面是最后的效果图。

 注:代码的工程文件为VS2008的C++工程,用VS2008打开。由于代码无法附在文后,所以放在资源里,可免费下载。

猜你喜欢

转载自blog.csdn.net/stonylhy2011/article/details/6628743