姑且不谈论OpenGL的名贵背景和光明前途,单凭其实用性和有效性就足以使其成为我们图形输出编程的首选。但是在实际工程应用中程序员没必要仔细地深究OpenGL的运作机制,也往往不需要掌握各种高级的效果制作,真正需要的是最简捷地利用这个得力的工具实现输出数据的可视化,如波形、谱图、立体统计图表等的显示。有鉴于此,本文总结出了在C++Builder中OpenGL编程的实用框架。笔者经过实践,对于一般的图形输出的应用,此框架足以应付。对于复杂些的程序可以在本框架的基础上进行扩充。
在Windows环境下用OpenGL编程至少要了解如下预备知识:
OpenGL本身:对于一般的应用,我们可以认为OpenGL就是一套与窗口系统和操作系统无关的三维图形函数库。
DC和RC:每个Win32应用程序都有一个设备描述表(Device Context)简称DC,在这个设备描述表中包含了图形怎样显示在窗口的设置( GDI)信息。调用OpenGL函数必须使用设备描述表才能正确地在窗口输出。图形操作描述表(Rendering Context)简称RC,是一种设备描述表的形式,在图形操作描述表中存放一些OpenGL和操作系统相联系的信息。图形操作描述表是传递所有OpenGL命令的端口。
像素格式:像素格式定义了OpenGL绘图的属性,创建图形操作描述表首先要定义象素格式。它由这样一个数据结构实现
typedef struct tagPIXELFORMATDESCRIPTOR
{
WORD nSize; //结构大小
WORD nVersion; //版本
DWORD dwFlags; //象素缓冲的位标志
BYTE iPixelType; //RGBA模式或颜色索引模式
BYTE cColorBits; //颜色位数
BYTE cRedBits; //RGBA模式下R所占位数
BYTE cRedShift; //RGBA模式下R位数偏移
BYTE cGreenBits; //RGBA模式下G所占位数
BYTE cGreenShift; //RGBA模式下G位数偏移
BYTE cBlueBits; //RGBA模式下B所占位数
BYTE cBlueShift; //RGBA模式下B位数偏移
BYTE cAlphaBits; //RGBA模式下Alpha所占位数
BYTE cAlphaShift; //RGBA模式下Alpha位数偏移
BYTE cAccumBits; //累计缓冲区位面总数
BYTE cAccumRedBits; //累计缓冲区R位面总数
BYTE cAccumGreenBits; //累计缓冲区G位面总数
BYTE cAccumBlueBits; //累计缓冲区B位面总数
BYTE cAccumAlphaBits; //累计缓冲区Alpaha位面总数
BYTE cDepthBits; //深度缓冲位数
BYTE cStencilBits; //模板缓冲位数
BYTE cAuxBuffers; //Win32 下不支持
BYTE iLayerType; //不再使用
BYTE bReserved; //0
DWORD dwLayerMask; //不再使用
DWORD dwVisibleMask; //0
DWORD dwDamageMask; //不再使用
} PIXELFORMATDESCRIPTOR;
双缓冲技术:OpenGL支持一个显示缓冲和一个非显示缓冲。缺省的情况是所有的OpenGL绘制命令在非显示缓冲中绘制,绘制完成后再将其内容拷贝到显示缓冲区中(使用SwapBuffers函数)。双缓冲使图象转换更平滑,这就是在快速动画(如波形等的实时输出)时没有屏幕闪烁的奥妙所在。
反走样技术:实际中需要画出的往往是曲线,由于计算机以离散点生成图形,曲线上会有锯齿,这就是一种走样现象。在用一般语言画图时,这一现象是难以避免的。OpenGL中利用混合技术,把原来边界的锯齿部分用低饱和度的点补上从而实现反走样,达到平滑的边界效果。
好,我们现在可以启用下面的程序框架了。
在*.h文件的类声明中添加private成员:
private:
HGLRC hRC;
HDC hDC;
以下是相应*.cpp文件
首先加上两个包含文件:
#include <glgl.h> //程序使用OpenGL的核心函数
#include <glglu.h> //程序使用实用库中的函数
一、在FormCreate()函数中完成OpenGL的初始化
使用OpenGL必须首先进行一些初始化工作,具体包含以下步骤:
1、创建DC
hDC=GetDC(Handle);
此句获取一个设备描述表,TForm1->Handle中保存有Form的窗口句柄;很多情况下我们希望在一个Panel中输出图形,那么可以用Panel1->Handle作为此函数的参数。
2、创建RC
(1)定义像素格式
static PIXELFORMATDESCRIPTOR pfd={
sizeof(PIXELFORMATDESCRIPTOR), //此结构的大小
1, //此结构的版本
PFD_DRAW_TO_WINDOW| //在窗口上绘图(而不是在位图上)
PFD_SUPPORT_OPENGL| //在窗口中支持使用OpenGL
PFD_DOUBLEBUFFER, //使用双缓冲模式
PFD_TYPE_RGBA, //使用RGBA色彩模式
24, //存储颜色数据的位数
0,0,0,0,0,0,
0,0,0,0,0,0,0,
32, //深度缓冲区大小
0,0,
PFD_MAIN_PLANE, //在主平面上绘图
0,
0,0,0
};
(2)选择最佳像素格式
int iPixelFormat=ChoosePixelFormat(hDC,&pfd);
选择一最适合上述pfd结构的像素格式,并把保存索引号。
SetPixelFormat(hDC,iPixelFormat,&pfd);
按选择的索引号设置设备描述表的像素格式。
(3)用DC创建RC
hRC=wglCreateContext(hDC);
用指定的设备描述表产生一个图形操作描述表,使它在该设备描述表上绘图,并且有与此设备描述表相同的像素格式。
3、指定当前的DC、RC
wglMakeCurrent(hDC,hRC);
把产生的图形操作描述表置为当前的,程序此后的所有OpenGL函数都通过此图形操作描述表执行,并将图形绘制在设备描述表引用的设备上。
到此就完成了初始化工作,这些步骤基本上是固定的(像素格式的参数设置也是如此),对于一般的应用可以直接使用上述语句。
二、在FormDestroy()中作清理工作以释放资源
1、清屏
glClearColor(0.0,0.0,0.0,1.0);
设置背景色为黑色。
glClear(GL_COLOR_BUFFER_BIT);
清屏以防止对以后窗口操作的影响。
2、当前DC、RC置空
wglMakeCurrent(NULL,NULL);
使不再有当前的图形操作描述表。
3、删除DC、RC
wglDeleteContext(hRC);
删除该图形操作描述表。
DeleteObject(hDC);
删除该设备描述表。
如果在同一个程序里对多个窗体用绘图必须严格进行清理,否则输出会出现混乱。
三、在FormPaint()中实施绘图的相关操作
每当窗体重画时进行绘图的动作。OnPaint事件可能由系统触发例如置为当前窗口;也可以由程序触发即在需要改变绘图时调用TForm->FormPaint(Sender)。把所有绘图操作统一归入FormPaint()事件的响应函数中使我们很容易控制绘图的时机,程序变得很有条理。
1、绘图准备
(1)指定DC
HDC hDC;
hDC=wglGetCurrentDC();
在多个设备描述表如多个绘图面板时有必要指定此后OpenGL命令输出的目标。
(2)清屏
动态的图形输出必须有清屏的操作将上此绘图的结果以背景色覆盖掉,以便画新的图形。
glClearColor(0.0,0.0,0.0,1.0);
背景色以黑色为例。
glClear(GL_COLOR_BUFFER_BIT);
清屏命令。
(3)启动反走样功能(可选)
glEnable(GL_BLEND);
启动混合。
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
指定混合的属性。
glEnable(GL_LINE_SMOOTH);
启动线反走样。
glHint(GL_LINE_SMOOTH_HINT,GL_NICEST);
指定为线反走样且为最好质量。
注意在绘图结束后要有相应的关闭反走样的操作,以防止影响程序以后的操作,语句如下
glDisable(GL_LINE_SMOOTH);
glDisable(GL_BLEND);
2、绘图
调用自定义的绘图函数RenderSence()。
3、缓冲操作
glFlush();
强制完成绘图工作。
SwapBuffers(hDC);
完成两个图形缓冲区的交换,把画完的非显示缓冲图形显示出来。
四、编写绘图函数
即定义RenderSence()函数,完成真正的绘图操作。实际上是把工程计算的结果作为参数调用OpenGL的各种绘图库函数。具体函数与画法请参阅有关书籍。我们把单纯的绘图和效果设定单列出来作为一个函数供FormPaint()调用,这样的结构使画图编程变得非常灵活。更复杂的情况可以写多个自定义绘图函数,也可以引入参数列表。
五、视口变换
OpenGL中有多种图形变换,其中视口变换是比较简单而且常用的变换方式,所以也归为本框架的一部分。事实上只需要使用一个函数
glViewport(0,0, ClientWidth, ClientHeight);
此函数可以指定全部图形最后投影的一个矩形区域,上句以整个窗体的客户区为例。在FormResize()事件响应函数中调用这个函数可以使图形在窗体大小形状变化时保持相同比例的缩放。