MFC中使用OpenGL

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

一、首先介绍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,在此线程中其后所有的OpenGL命令都使用相同的当前RC。

下面来讲一下RC的创建。正如前面所说的,RC只能通过建立了位图格式的DC来创建。

  1. **首先,我们需要创建一个窗口,然后使用GetDC函数得到这个窗口的DC。**我们这里讨论在MFC使用OpenGL,因此窗口类是由工程自动创建的COpenglDemoView类,由于我们做的是绘图前的窗口初始化工作,因此我们将之后所有的操作都在OnCreate函数中完成。(至于一些必要的成员数据初始化工作,应放在View类的构造函数中)

    HWND hWnd = this->GetSafeHwnd();
    HDC hDC = ::GetDC(hWnd);

  2. **然后,我们需要指定DC中的位图像素格式。**这个工作是由GDI库中名为PIXELFORMATDESCRIPTOR的类来实现的。
    第一步是填充这个数据结构:

    PIXELFORMATDESCRIPTOR
    pixelDesc={

    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所支持的像素格式列表中的索引值。

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

首先创建RC:
this->m_hGLContext = wglCreateContext(hDC);
**若创建RC失败,**通过GetLastError得到错误代码是2000,那么创建的像素格式是无效的,检测是否调用SetPixelFormat

然后选择新创建的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函数中的绘图操作一般都是静态的,如果想绘制动画,则需要程序员自己写定时函数来控制窗口的重绘。

4.程序结束要释放资源,如果忘记释放,将会导致内存泄露,随着程序运行次数的增多,内存占有量越来越大。这里在WM_DESTORY的消息响应函数中释放资源:

// when the rendering context is no longer needed ...  

// make the rendering context not current
wglMakeCurrent (NULL, NULL) ;

// delete the rendering context
wglDeleteContext (hglrc);       

你可能感兴趣的:(MFC中使用OpenGL)