1.Windows操作系统对OpenGL的支持
具有Windows编程经验的人都知道,在Windows下用GDI作图必须通过设备上下文(DeviceContext简写DC)调用相应的函数;用OpenGL作图也是类似,OpenGL函数是通过"渲染上下文"(RenderingContext简写RC)完成三维图形的绘制。Windows下的窗口和设备上下文支持"位图格式"(PIXELFORMAT)属性,和RC有着位图结构上的一致。只要在创建RC时与一个DC建立联系(RC也只能通过已经建立了位图格式的DC来创建),OpenGL的函数就可以通过RC对应的DC画到相应的显示设备上。创建RC的过程如下:
-
获取一个DC
-
选择并且设置DC的像素格式
-
通过API:wglCreateContext(DC)创建一个RC
-
设置当前的RC,通过API:wglMakeCurrent(RC, DC)来设置.
实现释放或者选择其它RC:
-
调用API:wglMakeCurrent(NULL, NULL)使DC不再有RC。
-
调用API:wglMakeCurrent(AnotherRC, DC)选择其他RC为当前RC,一般情况下,我们都是一个RC对应一个DC,当然,在某些场合里面是可以有多个RC。
-
调用API:wglDeleteContent(RC) 直接删除,这个方法比较直截了当。
这里还有以下需要注意的方面:
1.一个线程只能拥有一个渲染上下文(RC),也就是说,用户如果在一个线程内对不同设备作图,只能通过更换与RC对应的DC来完成,而RC在线程中保持不变。(当然,删除旧的RC后再创建新的是可以的)与此对应,一个RC也只能属于一个线程,不能被不同线程同时共享。
2.设定DC位图格式等于设定了相应的窗口的位图格式,并且DC和窗口的位图格式一旦确定就不能再改变。这一点只能期望以后的Windows版本改进了。
3.一个RC虽然可以更换DC,在任何时刻只能利用一个DC(这个DC称为RC的当前DC),但由于一个窗口可以让多个DC作图从而可以让多个线程利用多个RC在该窗口上执行OpenGL操作。
4.现在的Windows下的OpenGL版本对OpenGL和GDI在同一个DC上作图有一定的限制。当使用双缓存用OpenGL产生动画时,不能使用GDI函数向该DC作图。
5.不建议用ANSIC在Windows下编写OpenGL程序。这样的程序虽然具有跨平台的可移植性(比如很多SGI的例子程序),但是它们不能利用 Windows操作系统的很多特性,实用价值不大。
2.用VC来编写OpenGL程序
经过上面的分析,用VC来调用OpenGL作图的方法就很显然了。步骤如下:
1.先设置显示设备DC的位图格式(PIXELFORMAT)属性。这通过填充一个PIXELFORMATDESCRIPTOR的结构来完成(关于PIXELFORMATDESCRIPTOR中 各项数据的意义,请参照VC的帮助信息),该结构决定了OpenGL作图的物理设备的属性,比如该结构中的数据项dwFlags中PFD_DOUBLEBUFFER位如果没有设置(置1),通过该设备的DC上作图的OpenGL命令就不可能使用双缓冲来做动画。有一些位图格式(PIXELFORMAT)是DC支持的,而有一些DC就不支持了。所以程序必须先用ChoosePixelFormat来选择DC所支持的与指定位图格式最接近的位图格式,然后用SetPixelFormat设置DC的位图格式。
2.利用刚才的设备DC建立渲染上下文RC(wglCreateContext),使得RC与DC建立联系(wglMakeCurrent)。
3.调用OpenGL函数作图。由于线程与RC一一对应,OpenGL函数的参数中都不指明本线程RC的句柄(handle)
4.作图完毕以后,先通过置当前线程的RC为NULL(::wglMakeCurrent(NULL,NULL);),断开当前线程和该渲染上下文的联系,由此断开与DC的联系。此时RC句柄的有效性在微软自己的文档中也没有讲清楚,所以在后面删除RC的时候要先判断以下RC句柄的有效性(f(m_hrc)::wglDeleteContext(m_hrc);)。再根据情况释放(ReleaseDC)或者删除(DeleteDC)DC 。
3.一些说明
1.一旦设定了一个DC的位图格式,该DC所联系的窗口的位图格式随之设定。该窗口若含有子窗口或者有兄弟窗口,这些兄弟/子窗口的位图格式没有设成与对应RC一致的格式,OpenGL在它们上面作图就容易出错。故而OpenGL作图的窗口必须具有WS_CLIPCHILDREN和WS_CLIPSIBLINGS风格,程序中在主框窗的构造函数中用LoadFrame(IDR_MAINFRAME,WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN|WS_CLIPSIBLINGS,NULL,NULL);指定了主窗口的风格。
2.在ANSIC的OpenGL编程中,由auxReshapeFunc定义设置OpenGL视口大小和作图尺寸的回调函数。在MFC中应该由WM_SIZ消息的处理函数来完成。在ANSIC的OpenGL编程中,由EauxMainLoop定义作图的回调函数。在MFC中应该由WM_PAINT消息的处理函数来处理。相应的,由OpenGL定义的键盘、鼠标处理函数都应该由相应的Windows处理函数来响应。
3.OpenGL自己有刷新背景的函数glClear,故而应禁止Windows刷新窗口背景。否则,当窗口需要重画时,Windows会自动先发送WM_ERASEBKGND ,而缺省的处理函数使用白色的背景刷。当OpenGL使用的背景颜色不是白色时,作图时有一帧白色的闪烁。这种现象在做动画时特别明显。程序中只需要在WM_ERASEBKGND的消息处理函数中禁止父窗口类的消息处理,简单的返回一个TRUE即可。
4.由于OpenGL的跨平台性,它必须用操作系统的调色板。所以如果GL_INDEX_MODE作图时,必须用VC自己定义调色板。不过一般情况下,用GL_RGBA_MODE模式比较方便,很少用到GL_INDEX_MODE模式。
5.在OpenGL作图期间,RC对应的DC不能删除或者释放。
6.由于OpenGL作图时需要长时间占用DC,所以最好把作图窗口类设成CS_OWNDC。MFC缺省的窗口类风格中没有设这一属性,程序中在主窗口C++ 类的PreCreateWindow方法中自己注册了一个窗口类,除了设定了CS_OWNDC属性以外,还设定了CS_HREDRAW、CS_VREDRAW和CS_SAVEBITS。设定CS_HREDRAW、CS_VREDRAW是为了让窗口缩放时产生WM_PAINT消息,修正OpenGL视口和作图尺寸;由于OpenGL作图需要很多计算,设定CS_SAVEBITS是为了在OpenGL窗口被遮盖后显现出来时,不产生WM_PAINT消息,用内存存储的图象来填充,从而用空间消耗换取计算时间。
7.本程序中没有对OpenGL函数的出错情况作出处理。OpenGL出错后返回错误码,不会抛出异常;而且在某一个函数出错以后,后继函数也一般不会出现异常,只是返回错误码,一不小心就可能忽略某些错误。而对每一个OpenGL函数都做出错与否的判断比较麻烦,所以编程序时对OpenGL的函数应当非常小心。
4.像素格式
像素格式是OpenGL窗口的重要属性,它包括是否使用双缓冲,颜色位数和类型以及深度位数等。像素格式可由Windows系统定义的所谓像素格式描述子结构来定义(PIXELFORMATDESCRIPTOR),该结构定义在windows.h中。
在该结构中包含有26个属性信息,其形式为:
typedef struct tagPIXELFORMATDESCRIPTOR
{
WORD nSize;
WORD nVersion;
DWORD dwFlags; //如果使用了双缓冲,则需要在绘制代码结束后使用SwapBuffers(HDC hDc)换页
BYTE iPixelType;
BYTE cColorBits;
BYTE cRedBits;
BYTE cRedShift;
BYTE cGreenBits;
BYTE cGreenShift;
BYTE cBlueBits;
BYTE cBlueShift;
BYTE cAlphaBits;
BYTE cAlphaShift;
BYTE cAccumBits;
BYTE cAccumRedBits;
BYTE cAccumGreenBits;
BYTE cAccumBlueBits;
BYTE cAccumAlphaBits;
BYTE cDepthBits;
BYTE cStencilBits;
BYTE cAuxBuffers;
BYTE iLayerType;
BYTE bReserved;
DWORD dwLayerMask;
DWORD dwVisibleMask;
DWORD dwDamageMask;
} PIXELFORMATDESCRIPTOR;
各变量的含义如下:
nSize:该结构所占内存空间。
nVersion:版本号,当前为1。
dwFlags:指定像素格式属性,可选参量如表1.1所示。
像素格式属性
标识符 解释
PFD_DRAW_TO_BITMAP 支持内存中绘制位图
PFD_DRAW_TO_WINDOW 支持屏幕绘图
PFD_DOUBLEBUFFER 支持双缓冲
PFD_CENERIC_FORMAT 指定选择GDI支持的像素格式
PFD_NEED_PALETTE 指定需要逻辑调色板
PFD_NEED_SYSTEM_PALETTE 指定需要硬件调色板
PFD_STEREO NT不支持
PFD_SUPPORT_OPENGL 支持OpenGL
PFD_SUPPORT_GDI 支持GDI,此时不可使用PFD_DOUBLEBUFFER
iPixelType:像素颜色模式,可选项为PFD_TYPE_RGBA或PFD_TYPE_INDEX,分别对应于RGBA模式和颜色索引模式。
cColorBits:指定颜色的位数。
cRedBits:采用RGBA模式时,红色组分占用位数。
cRedShift:采用RGBA模式时,红色组分偏移量。
cGreenBits:采用RGBA模式时,绿色组分占用位数。
cGreenShift:采用RGBA模式时,绿色组分偏移量。
cBlueBits:采用RGBA模式时,蓝色组分占用位数。
cBlueShift: 采用RGBA模式时,蓝色组分偏移量。
cAlphaBits:采用RGBA模式时,Alpha组分占用位数。
cAlphaShift:采用RGBA模式时,Alpha组分偏移量。
cAccumBits:指定累积缓冲区表示一个像素所用位数。
cAccumRedBits:指定累积缓冲区表示红色组分占用位数。
cAccumGreenBits:指定累积缓冲区表示绿色组分占用位数。
cAccumBlueBits:指定累积缓冲区表示蓝色组分占用位数。
cAccumAlphaBits:指定累积缓冲区表示Alpha组分占用位数。
cDepthBits:指定深度缓冲区表示一个像素所用位数。
cStencilBits:指定模板缓冲区表示一个像素所用位数。
cAuxBuffers:指定辅助缓冲区,Windows9x、NT不支持。
iLayerType:Windows9x、NT下只能是PFD_MAIN_PLANE。
bReserved:=0。
dwLayerMask:指定覆盖层的屏蔽,Windows9x、NT不支持。
dwVisibleMask:Windows9x、NT不支持。
dwDamageMask:Windows9x、NT不支持。
Windows提供了四个像素格式管理函数,分别介绍如下:
(1) int ChoosePixelFormat(HDC hdc,
PIXELFORMATDESCRIPTOR *ppdf)
该函数比较传过来的像素格式描述和OpenGL支持的像素格式,返回一个最佳匹配的像素格式索引。该索引值可传给SetPixelFormat为DC设置像素格式。返回值为0表示失败。
在比较像素格式时,匹配优先级顺序为像素格式描述子结构中的下述各域:
dwFlags->cColorBits->cAlphaBits->cAccumBits
->cDepthBits->cStencilBits->cAuxBuffers->iLayerType
硬件支持的像素格式优先。
(2) int DescribePixelFormat(HDC hdc, int iPixelFormat, UINT nBytes,
LPPIXELFORMATDESCRIPTOR *ppfd)
该函数用格式索引iPixelFormat说明的像素格式来填写由ppfd所指向的像素格式描述子结构,利用该函数可以枚举像素格式。
(3) int GetPixelFormat(HDC hdc)
该函数用于获取hdc的格式索引。
(4) BOOL SetPixelFormat(HDC hdc, int iPixelFormat,
LPPIXELFORMATDESCRIPTOR *ppfd)
该函数用格式索引iPixelFormat来设置hdc的像素格式。在使用该函数之前应该调用ChoosePixelFormat来获取像素格式索引。另外,OpenGL窗口风格必须包含WS_CLIPCHILDREN和WS_CLIPSIBLINGS类型,否则设置失败。
应该注意的是ChoosePixelFormat函数并不一定返回一个最佳的像素格式值,可以利用DescribePixelFormat来枚举系统所支持的所有像素格式。OpenGL的通常支持24种不同的像素格式,如果系统安装了OpenGL硬件加速器,它可能会支持其它的像素格式