首页
登录 | 注册

在MFC中添加OpenGL窗口:DC和RC是什么鬼

转自http://blog.sina.com.cn/s/blog_6ccd0a1101012dy4.html

虽然MFC已经落伍好多年,而且用来做界面非常的不好用。。。但是我既不会C#也不会QT,又需要使用OpenGL,就只能将就用了。。。


一、首先介绍Windows图像程序设计中几个重要的概念:

GDI(Graphics Device Interface,图形设备接口):这是Windows API的一个库。当Windows应用程序需要显示点、线、图像、文字等内容,在显示器或打印输入这些内容时,就需要用到GDI。Windows应用程序不能直接操作系统的硬件(比如显卡),GDI就为应用程序提供了相关的接口。
其相关的函数接口、数据类型等都在WinGDI.h中声明(已经由Windows.h引入),在程序开发时,需要链接到Gdi32.lib。

DC(Device Contexts,设备上下文):是GDI库中最基本也是最重要的概念。DC是一个对象,设定了图形输出的特性和属性。
系统中可以有多个DC,每一个DC都必须关联到一个特定的图像输出设备。这些设备可以是真实存在的物理设备(显示器、打印机、绘图仪等),也可以使虚拟设备。这些反应在DC的类型上,DC具有4种类型:“显示”、“打印机”、“内存”、“信息”
其中显示类型DC是最常用的,它被关联到了显示设备上,所有的图像输出操作将直接反映在显示器上。
注:DC也可以只是设备全部输出范围的一部分。比如界面上某个窗口的客户区也可以有DC与之对应,对这样的DC进行操作只会影响到窗口客户区。

如果要将图像输出到特定的设备只需要创建相应类型的DC即可(注:对不同类型DC的操作是统一的),我们只关注获取显示器相关DC的操作:(以下的函数都是GDI库中的接口函数)
1. 获取DC - GetDC(HWND hWnd)
调用该函数会返回hWnd参数所指定的窗口的客户区所对应的DC的句柄。
如果hWnd参数设置为NULL,那么函数会返回整个桌面的DC。

2. 另一种获取DC的方法 - CreateDC
该函数也是用来获取DC句柄,与GetDC不同的是,CreateDC可以获取非显示器输出DC。只需指定不同的参数即可。
获取显示器的DC:HDC hScreenDC = CreateDC("DISPLAY", NULL, NULL, NULL);
若想获取打印机的DC,一般是将第一个参数改为"WINSPOOL"。

3. 释放DC - ReleaseDC(HWND hWnd, HDC hDC)
该函数的作用是释放DC,使其他应用程序可以使用。

4. 释放DC的相关系统资源 - DeleteDC(HDC hDC)
这个函数并不常用,一般做图结束后,我们只需要调用ReleaseDC释放DC即可。


也许看到这里,对DC具体是个什么东西还没有什么清楚的概念。下面大概讲一下。。。
     DC实际上是包含了一系列的图形对象,比如位图(Bitmap)、画刷(Brushe)、画笔(Pen)、字体(Font)、逻辑调色板(Logical palette)等等,GDI库中还定义了一系列接口函数,应用程序通过调用这些接口函数来操作当前DC中的图形对象,完成期望的绘图操作,最终影响放映到对应设备的输出上
     比如先创建一个窗口,然后得到该窗口对应的DC,这时候系统会为DC创建默认的图形对象(位图和路径除外),此时如果不进行任何操作,那么显示的窗口是一片白(也就是说刚开始是一块白色画布)。然后我们可以调用GDI库中的接口函数进行画图操作,或者载入位图(相当于在白色画布上作画)。DC中就是提供了对画布进行作画的工具
注:DC对应设备的显示信息都存储在位图对象中。

为了更清楚DC的作用,下面举个很简单的例子:在屏幕上画一条线
首先,获得整个显示器的DC:
HDC hdC = GetDC(NULL);
创建新的画笔对象:
COLORREF cPen = RGB(0,0,0); //指定画笔颜色为黑色
HPEN hpen = CreatePen(PS_SOLID, 10, cPen); //创建新的画笔,返回新画笔的句柄
将新创建的画笔指定为DC的当前画笔:(对同一种类型的图形对象,DC中只能有一个当前对象)
HPEN hpenOld = SelectObject(hdc, hpen);
画线:
LineTO(hdc, 500, 500);
画图操作结束,还原画笔:
SelectObject(hdc, hpenOld);
释放画笔资源:
DeleteObject(hpen);
释放DC:
ReleaseDC(NULL, hdc);


二、下面来讨论使用OpenGL来绘图的相关知识:

使用OpenGL绘图与使用GDI库绘图是不同的,主要体现在:OpenGL采用的是RC(Render Context,渲染上下文)绘图。
DC和RC的区别和联系
1. 在Windows中使用GDI绘图时必须指定在哪个DC中绘制,同样地,在使用OpenGL函数时也必须指定一个所谓的RC。正如设备上下文DC要存储GDI的绘制环境信息如笔、刷和字体等,RC也必须存储OpenGL所需的渲染信息如像素格式等。
2. Windows下的窗口和DC支持的位图格式(PIXELFORMAT)属性,和RC有位图结构上的一致。只要在创建RC时与一个DC建立联系,OpenGL函数就可以通过与RC对应的DC绘制到相应设备上。而实际上,RC只能通过建立了位图格式的DC来创建
3. 一个DC对应的是一个图像输出设备,而一个RC对应的则是一个线程。一个线程只能拥有一个RC,而一个RC也只能属于一个线程,不能在线程中共有。若一个线程想要在不同的设备上绘图,只能通过更改与RC对应的DC来实现,而RC在线程总保持不变(当然也可以删除旧的RC,再利用其它设备的DC创建新的RC)。


下面来讲一下RC的创建。正如前面所说的,RC只能通过建立了位图格式的DC来创建。
1. 首先,我们需要创建一个窗口,然后使用GetDC函数得到这个窗口的DC。我们这里讨论在MFC使用OpenGL,因此窗口类是由工程自动创建的COpenglDemoView类,由于我们做的是绘图前的窗口初始化工作,因此我们将之后所有的操作都在OnCreate函数中完成。(至于一些必要的成员数据初始化工作,应放在View类的构造函数中
HWND hWnd = this->GetSafeHwnd();
HDC hDC = ::GetDC(hWnd);

2. 然后,我们需要指定DC中的位图像素格式。这个工作是由GDI库中名为PIXELFORMATDESCRIPTOR的类来实现的。
第一步是填充这个数据结构:
PIXELFORMATDESCRIPTORpixelDesc={
sizeof(PIXELFORMATDESCRIPTOR),//nsize:像素格式描述子结构的大小
1, //nVersion:PIXELFORMATDESCRIPTOR结构的版本,一般设为1
//dwFlags:一组表明像素缓冲特性的标志位
PFD_DRAW_TO_WINDOW | //使之能在窗口或者其他设备窗口画图
PFD_SUPPORT_OPENGL,// |//使之能使用OpenGL函数
//PFD_DOUBLEBUFFER, //指明使用了双缓冲
PFD_TYPE_RGBA, //PixelType:定义了显示颜色的方法
24, //cColorBits:指定了一个颜色的位数
0,0, //cRedBits,cRedShift:每个RGBA颜色缓冲区中红色位平面的数目和偏移数
0,0, //cGreenBits,cGreenShift:每个RGBA颜色缓冲区中绿色位平面的数目和偏移数
0,0, //cBlueBits,cBlutShift:每个RGBA颜色缓冲区中蓝色位平面的数目和偏移数
0,0, //cAlphaBits,cAlphaShift:每个RGBA颜色缓冲区中Alpah位平面的数目和偏移数
0, //cAccumBits:累加缓冲区中全部位平面的数目
0,0,0,0, //cAccumRedBits, cAccumGreenBits, cAccumBlueBits,cAccumAlphaBits
32, //cDepthBits:Z(深度)缓冲区的深度
0, //cStencilBits:模板缓冲区的深度
0, //cAuxBuffers:轴向缓冲区的数量(一般1.0版本不支持)
PFD_MAIN_PLANE, //iLayerType:忽略,为了一致性而包含的
0, //bReserved:表层和底层平面的数量
0,0,0 //dwLayerMask, dwVisibleMask, dwDamageMask
};

这个类中的大部分成员变量我们并不关心,最重要的是第三个参数dwFlags,其中指定的PFD_SUPPORT_OPENGL使得我们可以在这个窗口中使用OpenGL函数
而指定的PFD_DOUBLEBUFFER则使得我们可以使用OpenGL中的双缓存机制(这个机制在制作动画时非常重要)。但奇怪的是当我选择了这个标志位时,生成的窗口没有图像(一片空白),而将这个标志位注释掉,就能显示我所绘制的图了。非常诡异,我也不明白为什么。。。

第二步是将这个像素格式选为当前DC的使用像素格式:
this->m_GLPixelIndex = ChoosePixelFormat(hDC, &pixelDesc);
if(this->m_GLPixelIndex==0){
this->m_GLPixelIndex = 1;
if(DescribePixelFormat(hDC,
       this->m_GLPixelIndex,
       sizeof(PIXELFORMATDESCRIPTOR),
       &pixelDesc) == 0)
{
return false;
}
}

if(SetPixelFormat(hDC, this->m_GLPixelIndex, &pixelDesc) == false){
return false;
}

这段代码首先调用了ChoosePixelFormat函数来寻找OpenGL所支持的像素格式中,最接近所设置的像素格式,如果没有找到,就调用DescribePixelFormat函数来选择索引值为1的像素格式来填充设置的像素格式。这些操作完成后,保证了所设置的像素格式是OpenGL所支持的。
最后就可以调用SetPixelFormat函数来为指定当前DC的像素格式

注:代码中的this->m_GLPixelIndex是我们自己添加的View类的protected型的成员变量,类型为int,用于保存所设置的像素格式在OpenGL所支持的像素格式列表中的索引值。


3. 设置像素格式完成后,就可以创建RC了。这里面要用到两个GDI库中的函数 wglCreateContext 和 wglMakeCurrent

首先创建RC:
this->m_hGLContext = wglCreateContext(hDC);
然后选择新创建的RC成为当前DC对应的RC
wglMakeCurrent(hDC, this->m_hGLContext);

:代码中的this->m_hGLContext是我们自己添加的View类的protected型的成员变量,类型为HGLRC,用于保存当前RC的句柄。

这样,RC就创建成功了。



RC创建成功后,我们就可以在View窗口中执行绘图操作了。其中,视窗、投影方式等设置放在OnSize函数中,而绘制操作则放在OnPaint函数中。

这里需要注意的是,MFC中没有提供类似GLUT中glutIdleFunc()的函数,而OnPaint函数只会在窗口创建或者窗口需要重绘的时候才会被调用。因此OnPaint函数中的绘图操作一般都是静态的,如果想绘制动画,则需要程序员自己写定时函数来控制窗口的重绘。



最后列一下参考的资料

如果想快速实现在MFC添加OpenGL窗口的功能,参考:基于MFC的OpenGL绘图

如果想比较详细地了解其中的原理,参考:OpenGL在MFC下的编程原理 和 OpenGL与MFC编程思想(后一篇还提供了解决重绘时屏幕闪烁问题的方法

如果想了解其中用到的函数,参考:OpenGL RC与像素格式


2020 jeepxie.net webmaster#jeepxie.net
10 q. 0.009 s.
京ICP备10005923号