- 浏览: 162926 次
- 性别:
- 来自: 广州
最新评论
-
雷蕾_ing:
...
python 模拟POST -
blink31:
TextOut不能换行,请问有其他的解决办法吗?
C++下的OpenGL文字显示的完美解决方案 -
lwz7512:
这么强悍!
在优酷的几道笔试题 -
lwz7512:
下了,谢谢分享!
opengl3D台球源代码 -
xltank:
今天过期了。。。
flash builder 序列号
以前一直用Delphi+OpenGL搞图形开发。最近改用VC++了。比起 Delphi而言,VC++最大的不同就在于没有统一的封装库(在Delphi中一律是VCL),如果仅为一点东西就使用某个库会使整个程序看起来极不协调。这里的介绍的方法原理跟我以前在Delphi中使用的方法是一致的。只不过没有使用任何封装库而已。
我曾在网上看过许多文字的解决方案,它们大多不能让人满意。有一种方法采用wgl函数生成某个具体的文字的显示列表,并在渲染时调用显示列表。这种方法必须为每个文字创建显示列表,文字一多就显得不够灵活。因此我采用的方法是先用GDI把指定的文字绘制到内存中的Bitmap中去,在把Bitmap转换成纹理送给OpenGL。这里也顺便小结一下Windows GDI,如果你对Windows GDI已十分熟悉可以跳过此节。
一提到GDI,很多人肯定会认为这个方法很慢。其实不尽然。GDI的绘图函数比起OpenGL来确实慢了许多,但如果用的好,并不会影响程序的效率。因为大多数情况下,你并不需要在每一帧都要重复使用GDI来绘制文字。
在实际应用中,大多数文字是静态的,少数文字在某些帧会发生改变。因此我们需要这样的一种方法,它不仅能绘制出高质量的字体,而且在需要时可以不影响系统效率地灵活地改变。
单击这里下载本文的代码
概念介绍
首先要解决的问题是如何使用Windows GDI创建位图,然后在位图中绘制文字,并把绘制后的位图读取出来。这一部分跟OpenGL没有任何关系,并且这一操作也无需在每一帧都执行。
这一部分概括如下:
1. 创建Windows GDI 设备环境
2. 创建一个内存中的位图对象,并把它指定到设备环境中去
3. 为设备环境指定绘图参数,如笔的颜色,背景颜色等等
4. 调用Windows GDI绘图函数在设备环境中绘图
5. 把位图对象中的信息抓取出来
先解释一下Windows GDI的一些概念。
设备环境(Device Content):设备环境是GDI绘图函数可以操作的对象。它包括一组Windows GDI子对象。这些子对象指定了绘图的图像,或者绘图的方式。常见的Windows GDI子对象包括:
Bitmap:位图。这里存储了绘图的结果。
Pen : 笔。指定笔的粗细,线条的样式,笔的颜色等等。
Brush: 画刷。指定相关绘图区域的背景填充方式。
Font: 字体对象。指定相关的文字绘制函数使用什么字体来绘制文字。
...
可以把设备环境比作一部绘图的机器,那么Bitmap就是机器里面的一张纸,机器画的图都显示在这张纸上。Pen和Brush对象都很好理解,Pen就是一只笔,它插在机器的孔里面,机器可以控制这只笔来回移动。Brush也类似。
在文章的后面我会进一步深化这些概念。
开始实现
现在要做的第一件事情就是创建设备环境。也就是创建一部用于绘图的机器。调用函数:
HDC CreateCompatibleDC( CDC* pDC ); //如果pDC是NULL,就自动创建一个新的设备环境。
因此你只需调用 Handle = CreateCompatibleDC(NULL);就可以创建一个新的设备环境,它的句柄保存在Handle变量中。
接着你要创建一个Bitmap对象,并把它指定到设备环境中去。这就好像你买了一张纸,然后把它塞进你的绘图机里。
Bitmap的创建过程稍微复杂一些,因为要指定很多参数。而我们需要的很简单:一个不带调色板的RGB格式的位图。使用这个函数创建内存位图:
HBITMAP CreateDIBSection(
HDC hdc, // handle to DC
CONST BITMAPINFO *pbmi, // bitmap data
UINT iUsage, // data type indicator
VOID **ppvBits, // bit values
HANDLE hSection, // handle to file mapping object
DWORD dwOffset // offset to bitmap bit values
);
根据我们的需要,第一个参数是没有用的,你可以指定为0,当然如果你愿意也可以指定为刚才创建的设备环境的句柄(Handle)。第二个参数指定了即将创建的位图的格式。这个数据结构后面再介绍。第三个参数是一个指向一个指针变量的指针。当函数执行完后,这个指针变量将指向位图的像素数据。我们需要使用这个指针来读取位图中的数据。hSection 和 dwOffset是用来从文件中读取位图的,我们均不需要,忽略。
因此你先要创建一个指定位图格式的数据结构 BITMAPINFO 。为了简单起见,这里直接给出我们需要的BITMAPINFO变量。这些项目的具体意义请参阅MSDN。
我们这样创建BITMAPINFO变量:
BITMAPINFO bitInfo;
bitInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
bitInfo.bmiHeader.biWidth=Width;
bitInfo.bmiHeader.biHeight=-Height;
bitInfo.bmiHeader.biPlanes=1;
bitInfo.bmiHeader.biBitCount=24;
bitInfo.bmiHeader.biCompression=BI_RGB;
bitInfo.bmiHeader.biSizeImage=0;
bitInfo.bmiHeader.biXPelsPerMeter=0;
bitInfo.bmiHeader.biYPelsPerMeter=0;
bitInfo.bmiHeader.biClrUsed=0;
bitInfo.bmiHeader.biClrImportant=0;
bitInfo.bmiColors[0].rgbBlue=255;
bitInfo.bmiColors[0].rgbGreen=255;
bitInfo.bmiColors[0].rgbRed=255;
bitInfo.bmiColors[0].rgbReserved=255;
随后我们创建位图:
void * imgptr = NULL;//用来接受位图数据的指针变量。
HBITMAP bitHandle = CreateDIBSection(0,&bitInfo,DIB_RGB_COLORS,&imgptr,NULL,0);
HGDIOBJ OldBmp = SelectObject(Handle,bitHandle);
DeleteObject(OldBmp);
这里,我们使用了CreateDIBSection函数创建了位图对象。随后我们又使用了SelectObject函数将创建的位图对象选择到设备环境中。注意最后一行的DeleteObject,这是什么意思呢?当我们创建设备环境时,新创建的设备环境并不完全是空的。它包含了一个1×1的位图对象。而一个设备环境只能包含一个位图对象。当我们为设备环境指定新的位图时,原来的那个位图就被置换了出来。置换出来的位图对象的句柄就是SelectObject函数的返回值。这个以前的位图是没有任何作用的,我们可以调用 DeleteObject来删除它。
另外,请记住imgptr,以后需要使用这个变量读取位图的内容。
准备工作还没有完成,我们还要相继为设备环境创建画刷和字体对象。画刷对象的创建很简单,因为我们只需要一个用白色填充背景的画刷。因此直接使用 hdlBrush = CreateSolidBrush(RGB(255,255,255));即可。创建完后,我们依然需要调用SelectObject将它选择到设备环境中去。这样今后绘图时相关函数就会使用这个画刷来填充背景。
字体对象创建就要复杂得多。因为描述字体的参数也非常多。先看一下创建字体对象的函数。
HFONT CreateFontIndirect(LOGFONT *font)
LOGFONT 是一个Struct,包含了许多内容。这里我们给出代码并介绍最有用的几项。
LOGFONT font;
font.lfHeight = -MulDiv(FontSize, GetDeviceCaps(Handle, LOGPIXELSY), 72);
//lfHeight项指定了文字的高度。GDI函数随后会根据指定的高度确定使用的字体大小。这对我们并不是十分方便, 因此用上面的表达式来根据字体大小计算相应的字体高度。
font.lfItalic = FontItalic; //是否斜体
font.lfOrientation = 0;
font.lfOutPrecision = OUT_TT_PRECIS; //选择TrueType字体
font.lfPitchAndFamily = DEFAULT_PITCH || FF_DONTCARE;
font.lfQuality = ANTIALIASED_QUALITY; //启用文字反锯齿
font.lfStrikeOut = FontStrikeOut; //删除线
font.lfUnderline = FontUnderline; //下划线
font.lfWeight = (FontBold ?FW_NORMAL:FW_BOLD); //是否粗体
font.lfWidth = 0; //忽略
同样的,创建完字体后,也要用SelectObject将字体对象选择到设备环境中去。
不知你是否注意到,在创建位图对象时,需要指定位图的高度和宽度。而你怎么知道要多大的高度和宽度才能适合要创建的文字的大小呢?因此,我们应该在确定了文字大小之后再创建位图对象。确定文字大小可以使用函数:
BOOL GetTextExtentPoint32(
HDC hdc, // handle to DC
LPCTSTR lpString, // text string
int cbString, // characters in string
LPSIZE lpSize // string size
);
其中lpSize是一个指向SIZE类型的指针,SIZE类型的变量描述了文字的高度和宽度,单位是像素。你可以使用下面的代码计算文字的高度和宽度。
STextSize sText;
sText.cx =0; sText.cy =0;
GetTextExtentPoint32(Handle,Text,(int)_tcslen(Text),&sText);
Handle是设备环境的句柄。
显然,GetTextExtentPoint32必须在字体对象被选入设备环境之后才会起作用。因此我们的流程如下:
1.创建设备环境,得到设备环境的句柄Handle
2.创建字体对象,把字体对象选入设备环境
3.创建画刷对象,并选入设备环境
4.用GetTextExtentPoint32得到要显示的字符串的大小tSize
5.创建位图。指定位图的大小为tSize。把位图选入设备环境
我们必须考虑另外一个问题,就是许多早期的显卡并不支持Non-Power-Of-Two-Textures扩展,这就意味着我们创建的位图大小不应该是任意的,而必须是2的整数次幂。为了支持这一点,我们在得到文字的大小之后应该用下面的函数计算位图的大小。
int GetPO2Value(int value)
{
return Round(Power(2,ceil(log((float)value)/log(2.0f))));
}
//Round 和Power是自己写的数学函数。它们的定义如下:
float Power(float base, float exponent) //计算Base的Exponent次方
{
return exp(log(base)*exponent);
}
int Round(float value) //四舍五入到整数
{
if (value>0.4f)
return ((int)(value+0.5f));
else if (value<-0.4f)
return (-(int)(-value-0.5f));
else
return 0;
}
现在可以在设备环境中绘制文字了。
TextOut(Handle,X,Y,Text,(int)_tcslen(Text)));
绘制文字之后,需要把位图的内容读取出来。下面的代码用于读取位图中的内容。注意我们之前创建位图时得到的imgptr指针。
unsigned char ** ScanLine; //位图的扫描行
ScanLine = new unsigned char *[Height]; // Height是位图的高度,Width是位图的宽度
int rowWidth = Width*bitInfo.bmiHeader.biBitCount/8; //一个扫描行所占用的字节
while (rowWidth %4) rowWidth++; //每个扫描行都是32位对齐的。
for(int i=0;i<Height;i++)
{
ScanLine[i]=(unsigned char *)(imgptr)+rowWidth*i;//得到每个扫描行开始处的指针。
}
//经过上述代码后,ScanLine就可以看作一个二维的数组,ScanLine[i]表示位图第i行的所有像素数据。
//ScanLine[i][j*3] 表示第i行,第j列的像素的Blue分量。
//ScanLine[i][j*3+1] 表示第i行,第j列的像素的Green分量。
//ScanLine[i][j*3+2] 表示第i行,第j列的像素的Red分量。
//现在把ScanLine中的数据读到一个一维数组中,供OpenGL使用。
unsigned char *pic;
pic = new unsigned char[TexWidth*TexHeight*4];
int LineWidth = TexWidth*4;
for (int i=0;i<TexHeight;i++)
{
for (int j=0;j<TexWidth;j++)
{
if (ScanLine[i][j*3+2]!=255) //这样写的目的是为了兼容文字反锯齿
{
pic[i*LineWidth+j*4] = 255;
pic[i*LineWidth+j*4+1] = 255;
pic[i*LineWidth+j*4+2] = 255;
}
else
{
pic[i*LineWidth+j*4] = 0;
pic[i*LineWidth+j*4+1] = 0;
pic[i*LineWidth+j*4+2] = 0;
}
pic[i*LineWidth+j*4+3] = 255- ScanLine[i][j*3+2];
}
}
至此,我们已完成了GDI绘图的部分,并把绘制后的文字位图存储在了一维数组pic中。
剩下的内容就十分简单了。把pic作为纹理传给OpenGL,然后绑定该纹理,设置 OpenGL绘图参数,根据文字位图的大小在屏幕上绘制一个Quad,注意关闭深度缓冲,关闭光照,启动混色,把投影矩阵设置为和屏幕视域一样大的平行投影,然后在想要的地方绘制就性了。下面给出准备和结束屏幕2D绘图的代码,以供参考。
void BeginUIDrawing()
{
int viewport[4];
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glGetIntegerv(GL_VIEWPORT,viewport);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0,viewport[2],viewport[3],0,1,-1);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
}
void EndUIDrawing()
{
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glEnable(GL_DEPTH_TEST);
}
可供参考的渲染循环:
void RenderScene()
{
Draw3D;
BeginUIDrawing();
DrawText(x,y);
EndUIDrawing();
}
归纳整合
把上述内容总结归纳成相应的数据结构,可以参考下面的封装方法。详细的代码可以单击这里下载。通过阅读和使用这些代码,可以让你更好地了解整个工作机制。
class CCanvas //包括字体对象和画刷对象,并封装了绘图函数
{
private:
HFONT hdFont;
HBRUSH hdBrush;
public:
HDC Handle;//设备环境的句柄,由CDIBImage赋值
CCanvas(HDC DC);
~CCanvas();
void ChangeFont(SFont newFont);//改变字体
void TextOut(char *Text, int X, int Y); //绘制文字
STextSize GetTextSize(char *Text); //得到文字的大小
void Clear(int w, int h); //清空位图。
};
class CDIBImage //包括创建设备环境,创建CCanvas对象和位图对象。
//提供的ScanLine指针指向了每一个扫描行的数据
{
private:
void CreateBMP(int Width, int Height); //创建一个位图
public:
HDC Handle; //设备环境的句柄
HBITMAP bitHandle; //位图的句柄
CCanvas *Canvas; //Canvas对象,包括字体对象和画刷对象以及相关绘图函数
unsigned char** ScanLine;//指向位图数据
CDIBImage();
~CDIBImage();
void SetSize(int Width, int Height); //设置位图的大小
};
class CGLText //把GDI中的位图读取出来并作为纹理对象
{
private:
int TexHeight,TexWidth,TextHeight,TextWidth;
GLuint TexID;
CDIBImage *Bit;
int GetPO2Value(int value); // Get a minimum power-of-two value
//that is larger than the specified value.
public:
CGLText();
~CGLText();
void SetFont(SFont sFont); // Set the font styled of this label.
void SetText(char *Text); // Set the text that is going to be displayed.
void Draw(int X, int Y); // Draw the text at specified position
};
- GLText_GDI.rar (3.6 KB)
- 下载次数: 189
发表评论
-
GUI设计禁忌
2010-01-07 12:02 990本文列举Jeff Johnson:《GUI设计禁忌》一书中 ... -
LOD地形设计(三)
2010-01-05 05:03 1022LOD地形根据视点的变化决定是否进行网格分割,因此系统应设计一 ... -
LOD地形设计(二)
2010-01-05 05:02 1212自从LOD地形第一节推出以来,受到不少朋友的关注,本人真是受宠 ... -
LOD地形设计(一)
2010-01-05 05:01 1118在大规模的三维场 ... -
Windows SDK笔记
2009-04-16 16:45 1407应程序需求需要重写窗体内子控件某一特定消息,可怜我苦苦找了 ... -
关于的glut 的配置
2009-02-09 17:46 1701OpenGL 是一套 用于三维作图的API。与Direct3 ... -
OpenGL的消隐与双缓冲(2)
2009-02-09 17:46 1639#include "stdafx.h" ... -
C语言读取bmp位图文件(含bmp格式定义)
2009-02-09 17:45 3904loadbmp.h #ifndef _LOADBMP_H_ ... -
openGL贴图(借助glx),并测试性能(FPS)2
2009-02-09 17:45 1193client.c #include <sys/type ... -
alpha混合技术
2009-02-09 17:44 1500alpha混合技术 alpha混合技术对熟悉游戏的人来说不会 ... -
C语言将raw data(rgb/rgba)写成bmp文件(bmp24或32)
2009-02-09 17:44 3732int bmp_write(unsigned char *im ... -
OpenGL坐标变换专题
2009-02-09 17:43 2530OpenGL通过相机模拟、可 ... -
对话框中OpenGL的设置
2009-02-09 17:43 2204from:http://huhuiowen.blog.16 ... -
OpenGL曲线绘制:线段
2009-02-09 17:42 1895以线段方式绘制曲线。例如:正弦曲线。 glBegin(GL_ ... -
用VC++做OpenGL程序框架时出现非法错误
2009-02-09 17:40 10141.OpenGL.cpp 文件包含的头文件有没有{}不配对的情 ... -
opengl编写游戏的可移植性 具体表现
2009-02-09 17:39 1088OpenGL是个与硬件无关的软件接口,可以在不同的平台如Win ... -
什么是显卡的OpenGL模式
2009-02-09 17:39 1669OpenGL是近几年发展起来的一个性能卓越的三维图形标准。 ... -
关于-opengl在魔兽中的应用
2009-02-09 17:38 2008OpenGL在画质上是优于Direct的,这个“画质”不是指感 ... -
如何让显卡支持OpenGL模式????????
2009-02-09 17:38 3336OPENGL 不支持问题 这也 ... -
opengl3D台球源代码
2009-01-20 09:59 24383D台球,可以平面也可以从不同角度击球,使用OPENGL渲染, ...
相关推荐
在Qt中,我们可以利用QOpenGLWidget和Qt的字体资源来实现文字与OpenGL的结合。QOpenGLWidget是Qt提供的一类用于OpenGL渲染的窗口组件,我们可以在其paintEvent()方法中进行OpenGL绘制操作。同时,通过QFont和...
OpenGl文字显示c++类,包括*.h和*.cpp,可显示中英文,已优化,速度很快。
在C++编程中,与OpenGL相关的任务经常需要获取当前系统上安装的OpenGL版本信息。这在开发图形应用程序或确保代码兼容性时尤为重要。本篇文章将详细介绍两种不同的方法来获取OpenGL版本号,这两种方法都适用于C++环境...
opengl c++opengl c++opengl c++opengl c++opengl c++opengl c++
【作品名称】:使用 C++ 和 OpenGL 实现简单的 3D 赛车游戏 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【项目介绍】: 项目结构...
opengl c++opengl c++opengl c++opengl c++opengl c++opengl c++
基于C++和OpenGL的机器人动画项目源码+sln解决方案(VS打开编译运行).zip基于C++和OpenGL的机器人动画项目源码+sln解决方案(VS打开编译运行).zip基于C++和OpenGL的机器人动画项目源码+sln解决方案(VS打开编译运行)....
"Visual C++ 6 实现 OpenGL 编程" OpenGL 是一个优秀的开放式三维图形接口,包括有 120 多个图形函数,GL 是 GRAPHIC LIBRARY 的缩写,意思是“图形库”。微软在 Visual C++ 5 中已提供了三个 OpenGL 的函数库(glu...
**C++ OpenGL 实例程序详解** OpenGL是一种强大的图形编程接口,用于在各种操作系统和硬件上创建2D和3D图形。C++是实现OpenGL程序的常用语言,它提供了丰富的类库和工具,使得开发人员能够高效地构建复杂的图形应用...
Visual C++结合OpenGL做的一个3D场景。其中包含地形生成,场景漫游,碰撞检测,场景中模拟了河流,跳跃的鱼,喷泉,树木,水草,房屋,塔楼,楼梯,飘动的旗帜等等。第一人称视角,可以跳跃行走碰撞检测,模拟了白天...
visual studio C++ 读取PLY文件,通用性较好,并使用OpenGL对模型进行显示。
3. **glut**: OpenGL Utility Toolkit (GLUT) 是一个跨平台的库,主要用于创建窗口、处理用户输入、管理图形显示等,使得开发者能快速构建OpenGL应用程序。 4. **glert**: 这可能是错误,因为没有标准的“glert”库...
- OpenGL提供深度测试功能,以解决多边形遮挡问题。通过`glEnable(GL_DEPTH_TEST)`开启,`glDepthFunc`设置比较函数。 7. **帧缓冲对象(Frame Buffer Objects, FBOs)**: - 可用于离屏渲染,将渲染结果保存到...
C++作为一种通用编程语言,是实现OpenGL功能的常见选择。本篇文章将详细探讨如何使用C++和OpenGL来实现图形的缩放和平移功能。 首先,要使用OpenGL进行图形操作,我们需要包含必要的头文件并链接相应的库。在C++中...
4. `Lesson1.sln.old`和`Lesson1.sln`:`.sln`文件是Visual Studio解决方案文件,包含了项目的配置信息和所有相关项目。 5. `Lesson1.suo`:这是用户特定的选项文件,保存了IDE的用户界面设置,如窗口布局和断点。 6...
使用C++与openGL库编写的3D程序,实现三维图像效果,并可使用鼠标或键盘动态变换视角,(其中一个人物为电脑游戏CS中的模型),包含执行程序,使用VC或studio可直接打开工程文件运行
本文档详细介绍了如何使用C++和openGL生成贝塞尔曲线的过程。贝塞尔曲线是一种数学曲线,广泛应用于计算机图形学、计算机辅助设计(CAD)和其他领域。本文档将详细讲解如何使用C++语言和openGL库来生成贝塞尔曲线,...
使用C++封装的OpenGL.zip使用C++封装的OpenGL.zip使用C++封装的OpenGL.zip使用C++封装的OpenGL.zip使用C++封装的OpenGL.zip使用C++封装的OpenGL.zip使用C++封装的OpenGL.zip使用C++封装的OpenGL.zip使用C++封装的...