OpenGL在MFC中的使用总结(一)——基本框架

原创 2014年08月21日 16:10:21
项目中要画3D显示的模型,于是要用到OpenGL,加上是在MFC中,而且是在MFC中的ActiveX中使用,再而且鉴于他们程序主框架的设定,常规的方法还不一定能实现。所以还是查过不少资料,在此一一总结一下。首先总结最基础的一些东西。

一、按照讲课的逻辑,先讲点原理性的东西~

  GDI是通过设备描述表(Device Context,以下简称"DC")来绘图,而OpenGL是通过渲染描述表(Rendering Context,以下简称"RC")。每一个GDI命令需要传给它一个DC,与GDI不同,OpenGL使用当前渲染描述表(RC)。一旦在一个线程中指定了一个当前RC,在此线程中其后所有的OpenGL命令都使用相同的当前RC。虽然在单一窗口中可以使用多个RC,但在单一线程中只有一个当前RC。下面我将首先产生一个OpenGL RC并使之成为当前RC,这将分为三个步骤:
设置窗口像素格式;产生RC;设置为当前RC。

二、MFC中的OpenGL基本框架

首先要把OpenGL文件和库加入到工程中
加入以下头文件
#include <gl\gl.h> 
#include <gl\glu.h>

加入以下库文件(不一定都用的到)
opengl32.lib 
glu32.lib 
glut.lib 
glaux.lib

准备工作做好,就开始配置环境并初始化喽~
1.改写OnPreCreate函数并给视图类添加成员函数和成员变量
  OpenGL需要窗口加上WS_CLIPCHILDREN(创建父窗口使用的Windows风格,用于重绘时裁剪子窗口所覆盖的区域)和 WS_CLIPSIBLINGS(创建子窗口使用的Windows风格,用于重绘时剪裁其他子窗口所覆盖的区域)风格。

把OnPreCreate改写成如下所示:

BOOL COpenGLDemoView::PreCreateWindow(CREATESTRUCT& cs)
{
    // TODO: Modify the Window class or styles here by modifying
    //  the CREATESTRUCT cs
    cs.style |= (WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
    return CView::PreCreateWindow(cs);
}

  2.定义窗口的像素格式

产生一个RC的第一步是定义窗口的像素格式。像素格式决定窗口着所显示的图形在内存中是如何表示的。由像素格式控制的参数包括:颜色深度、缓冲模式和所支持的绘画接口。在下面将有对这些参数的设置。我们先在COpenGLDemoView的类中添加一个保护型的成员函数BOOL SetWindowPixelFormat(HDC hDC)(用鼠标右键添加)和保护型的成员变量:int m_GLPixelIndex;

并编辑其中的代码如下:

BOOL COpenGLDemoView::SetWindowPixelFormat(HDC hDC)
{
	//定义窗口的像素格式
    PIXELFORMATDESCRIPTOR pixelDesc=
    {
        sizeof(PIXELFORMATDESCRIPTOR),
        1,
        PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|
        PFD_DOUBLEBUFFER|PFD_SUPPORT_GDI,
        PFD_TYPE_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
    };

    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;
    }
    return TRUE;
}

代码解释:
  现在我们可以看一看Describe-PixelFormat提供有哪几种像素格式,并对代码进行一些解释:
  PIXELFORMATDESCRIPTOR包括了定义像素格式的全部信息。
  DWFlags定义了与像素格式兼容的设备和接口。

  通常的OpenGL发行版本并不包括所有的标志(flag)。wFlags能接收以下标志:

  PFD_DRAW_TO_WINDOW 使之能在窗口或者其他设备窗口画图;
  PFD_DRAW_TO_BITMAP 使之能在内存中的位图画图;
  PFD_SUPPORT_GDI 使之能调用GDI函数(注:如果指定了PFD_DOUBLEBUFFER,这个选项将无效);
  PFD_SUPPORT_OpenGL 使之能调用OpenGL函数;
  PFD_GENERIC_FORMAT 假如这种象素格式由Windows GDI函数库或由第三方硬件设备驱动程序支持,则需指定这一项;
PFD_NEED_PALETTE 告诉缓冲区是否需要调色板,本程序假设颜色是使用24或 32位色,并且不会覆盖调色板;
  PFD_NEED_SYSTEM_PALETTE 这个标志指明缓冲区是否把系统调色板当作它自身调色板的一部分;
  PFD_DOUBLEBUFFER 指明使用了双缓冲区(注:GDI不能在使用了双缓冲区的窗口中画图);
  PFD_STEREO 指明左、右缓冲区是否按立体图像来组织。

  PixelType定义显示颜色的方法。PFD_TYPE_RGBA意味着每一位(bit)组代表着红、绿、蓝各分量的值。PFD_TYPE_COLORINDEX 意味着每一位组代表着在彩色查找表中的索引值。本例都是采用了PFD_TYPE_RGBA方式。
  ● cColorBits定义了指定一个颜色的位数。对RGBA来说,位数是在颜色中红、绿、蓝各分量所占的位数。对颜色的索引值来说,指的是表中的颜色数。
  ● cRedBits、cGreenBits、cBlue-Bits、cAlphaBits用来表明各相应分量所使用的位数。
  ● cRedShift、cGreenShift、cBlue-Shift、cAlphaShift用来表明各分量从颜色开始的偏移量所占的位数。

  一旦初始化完我们的结构,我们就想知道与要求最相近的系统象素格式。我们可以这样做:

  m_hGLPixelIndex = ChoosePixelFormat(hDC, &pixelDesc);
  ChoosePixelFormat接受两个参数:一个是hDc,另一个是一个指向PIXELFORMATDESCRIPTOR结构的指针& pixelDesc;该函数返回此像素格式的索引值。如果返回0则表示失败。假如函数失败,我们只是把索引值设为1并用 DescribePixelFormat得到像素格式描述。假如你申请一个没得到支持的像素格式,则Choose-PixelFormat将会返回与你要求的像素格式最接近的一个值。一旦我们得到一个像素格式的索引值和相应的描述,我们就可以调用SetPixelFormat设置像素格式,并且只需设置一次。
3.产生渲染描述表(RC)
  现在像素格式已经设定,我们下一步工作是产生渲染描述表(RC)并使之成为当前渲染描述表。在COpenGLDemoView中加入一个保护型的成员函数BOOL CreateViewGLContext(HDC hDC),并加入一个保护型的成员变量HGLRC m_hGLContext;HGLRC是一个指向rendering context的句柄。
BOOL COpenGLDemoView::CreateViewGLContext(HDC hDC)
{
    this->m_hGLContext = wglCreateContext(hDC);
    if(this->m_hGLContext==NULL)
    {//创建失败
        return FALSE;
    }


    if(wglMakeCurrent(hDC,this->m_hGLContext)==FALSE)
    {//选为当前RC失败
        return FALSE;
    }
    return TRUE;
} 
4.在OnCreate函数中调用此函数:
int COpenGLDemoView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;
    
    // TODO: Add your specialized creation code here
    HWND hWnd = this->GetSafeHwnd();    
    HDC hDC = ::GetDC(hWnd);
    if(this->SetWindowPixelFormat(hDC)==FALSE)
    {
        return 0;
    }
    if(this->CreateViewGLContext(hDC)==FALSE)
    {
        return 0;
    }
    return 0;
} 
  5.添加WM_DESTROY的消息处理函数Ondestroy( ),使之如下所示:
void COpenGLDemoView::OnDestroy() 
{
    CView::OnDestroy();
    
    // TODO: Add your message handler code here
    if(wglGetCurrentContext()!=NULL)
    {
        wglMakeCurrent(NULL,NULL);
    }
    if(this->m_hGLContext!=NULL)
    {
        wglDeleteContext(this->m_hGLContext);
        this->m_hGLContext = NULL;
    }
}
  6.编辑COpenGLDemoView的构造函数,使之如下所示:
COpenGLDemoView::COpenGLDemoView()
{
    // TODO: add construction code here
    this->m_GLPixelIndex = 0;
    this->m_hGLContext = NULL;
}
至此,我们已经构造好了框架,使程序可以利用OpenGL进行画图了。你可能已经注意到了,我们在程序开头产生了一个RC,自始自终都使用它。这与大多数GDI程序不同。在GDI程序中,DC在需要时才产生,并且是画完立刻释放掉。实际上,RC也可以这样做;但要记住,产生一个RC需要很多处理器时间。因此,要想获得高性能流畅的图像和图形,最好只产生RC一次,并始终用它,直到程序结束。

  CreateViewGLContex产生RC并使之成为当前RC。WglCreateContext返回一个RC的句柄。在你调用 CreateViewGLContex之前,你必须用SetWindowPixelFormat(hDC)将与设备相关的像素格式设置好。 wglMakeCurrent将RC设置成当前RC。传入此函数的DC不一定就是你产生RC的那个DC,但二者的设备句柄(Device Context)和像素格式必须一致。假如你在调用wglMakeforCurrent之前已经有另外一个RC存在,wglMakeforCurrent 就会把旧的RC冲掉,并将新RC设置为当前RC。另外你可以用wglMakeCurrent(NULL, NULL)来消除当前RC。要在OnDestroy中把渲染描述表删除掉。但在删除RC之前,必须确定它不是当前句柄。我们是通过wglGetCurrentContext来了解是否存在一个当前渲染描述表的。假如存在,那么用wglMakeCurrent(NULL, NULL)来把它去掉。然后就可以通过wglDelete-Context来删除RC了。这时允许视类删除DC才是安全的。注:一般来说,使用的都是单线程的程序,产生的RC就是线程当前的RC,不需要关注上述这一点。但如果使用的是多线程的程序,那我们就特别需要注意这一点了,否则会出现意想不到的后果。


三、给个实例(我就懒点吧,直接从网上把资料搬过来,就不编译执行了)
  下面给出一个简单的二维图形的例子(这个例子都是以上述框架为基础的)。
  用Classwizard为COpenGLDemoView添加WMSIZE的消息处理函数OnSize,代码如下:
void COpenGLDemoView::OnSize(UINT nType, int cx, int cy) 
{
    CView::OnSize(nType, cx, cy);
    
    // TODO: Add your message handler code here
    GLsizei width,height;
    GLdouble aspect;
    width = cx;
    height = cy;
    if(cy==0)
    {
        aspect = (GLdouble)width;
    }
    else
    {
        aspect = (GLdouble)width/(GLdouble)height;
    }
    glViewport(0,0,width,height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0.0,500.0*aspect,0.0,500.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}
用Classwizard为COpenGLDemoView添加WM_PAINT的消息处理函数OnPaint,代码如下:
void COpenGLDemoView::OnPaint() 
{
    CPaintDC dc(this); // device context for painting
    
    // TODO: Add your message handler code here
    
    // Do not call CView::OnPaint() for painting messages


    glLoadIdentity();
    glClear(GL_COLOR_BUFFER_BIT);
    glBegin(GL_POLYGON);
        glColor4f(1.0f,0.0f,0.0f,1.0f);
        glVertex2f(100.0f,50.0f);
        glColor4f(0.0f,1.0f,0.0f,1.0f);
        glVertex2f(450.0f,400.0f);
        glColor4f(0.0f,0.0f,1.0f,1.0f);
        glVertex2f(450.0f,50.0f);
    glEnd();
    glFlush();
}

这个程序的运行结果是黑色背景下的一个绚丽多彩的三角形。



这里你可以看到用OpenGL绘制图形非常容易,只需要几条简单的语句就能实现强大的功能。如果你缩放窗口,三角形也会跟着缩放。这是因为OnSize通过glViewport(0, 0, width, height)定义了视口和视口坐标。glViewport的第一、二个参数是视口左下角的像素坐标,第三、四个参数是视口的宽度和高度。
  OnSize中的glMatrixMode是用来设置矩阵模式的,它有三个选项:GL_MODELVIEW、GL_PROJECTION、 GL_TEXTURE。GL_MODELVIEW表示从实体坐标系转到人眼坐标系。GL_PROJECTION表示从人眼坐标系转到剪裁坐标系。 GL_TEXTURE表示从定义纹理的坐标系到粘贴纹理的坐标系的变换。
  
四、小结
  1、如果要响应WM_SIZE消息,则一定要设置视口和矩阵模式。
  2、尽量把你全部的画图工作在响应WM_PAINT消息时完成。
  3、产生一个渲染描述表要耗费大量的CPU时间,所以最好在程序中只产生一次,直到程序结束。
  4、尽量把你的画图命令封装在文档类中,这样你就可以在不同的视类中使用相同的文档,节省你编程的工作量。
  5、glBegin和glEnd一定要成对出现,这之间是对图元的绘制语句。
  glPushMatrix()和glPopMatrix()也一定要成对出现。glPushMatrix()把当前的矩阵拷贝到栈中。当我们调用 glPopMatrix时,最后压入栈的矩阵恢复为当前矩阵。使用glPushMatrix()可以精确地把当前矩阵保存下来,并用 glPopMatrix把它恢复出来。(以后可能会总结一下glPushMatrix()和glPopMatrix()的重要作用~~)
  6、解决屏幕的闪烁问题。我们知道,在窗口中拖动一个图形的时候,由于边画边显示,会出现闪烁的现象。在GDI中解决这个问题较为复杂,通过在内存中生成一个内存DC,绘画时让画笔在内存DC中画,画完后一次用Bitblt将内存DC“贴”到显示器上,就可解决闪烁的问题。在OpenGL中,我们是通过双缓存来解决这个问题的。一般来说,双缓存在图形工作软件中是很普遍的。双缓存是两个缓存,一个前台缓存、一个后台缓存。绘图先在后台缓存中画,画完后,交换到前台缓存,这样就不会有闪烁现象了。通过以下步骤可以很容易地解决这个问题:

(1) 

要注意,GDI命令是没有设计双缓存的。我们首先把使用InvalidateRect(null)的地方改成InvalidateRect(NULL,FALSE)。这样做是使GDI的重画命令失效,由OpenGL的命令进行重画;

(2)

将像素格式定义成支持双缓存的(注:PFD_DOUBLEBUFFER和PFD_SUPPORT_GDI只能取一个,两者相互冲突)。 
   pixelDesc.dwFlags = 
   PFD_DRAW_TO_WINDOW | 
   PFD_SUPPORT_OPENGL | 
   PFD_DOUBLEBUFFER | 
   PFD_STEREO_DONTCARE; 

(3)

 我们得告诉OpenGL在后台缓存中画图,在视类的OnSize()的最后一行加入:glDrawBuffer (GL_BACK);

(4)

 最后我们得把后台缓存的内容换到前台缓存中,在视类的OnPaint()的最后一行加入:SwapBuffers(dc.m_ ps.hdc)。

  7、Z缓冲区的问题:要使三维物体显得更流畅,前后各面的空间关系正确,一定得使用Z缓冲技术;否则,前后各面的位置就会相互重叠,不能正确显示。Z缓冲区存储物体每一个点的值,这个值表明此点离人眼的距离。Z缓冲需要占用大量的内存和CPU时间。启用Z缓冲只需在OnSize()的最后加上glEnable (GL_DEPTH_TEST);要记住:在每次重绘之前,应使用glClear(GL_DEPTH_BUFFER_BIT)语句清空Z缓冲区。
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lyh03601/article/details/38731319

VS 2012环境下使用MFC进行OpenGL编程

VS 2012下使用MFC+OpenGL基本步骤 引子:以前一直惧怕MFC感觉它是一个非常复杂的东西,更不用说去用它来和OpenGL结合了,以前是一直羡慕别人用MFC创建一个窗口进行OpenGL绘图...
  • bbppbb
  • bbppbb
  • 2014年04月22日 21:11
  • 7093

MFC 中配置OpenGL环境

MFC为编写Windows应用程序提供了极大的方便,程序员在编写Windows应用程序时,只需要几步就可以把一个程序的框架建立起来.该类极大的提高了编程者的开发效率,是个具有极大实用价值的工具. ...
  • xiaoge132
  • xiaoge132
  • 2016年03月09日 09:45
  • 2903

直接在MFC的对话框上利用OpenGL绘制图形

借鉴前人的经验,如链接http://www.cnblogs.com/phinecos/archive/2007/07/28/834916.html介绍了如何利用MFC的单文档来使用OpenGL(本文也...
  • hurricanecqj
  • hurricanecqj
  • 2015年05月01日 15:20
  • 1910

MFC 使用Opengl贴图

用opengl库在mfc 对话框程序中创建简单的opengl程序。 步骤: 1.用vs2010创建mfc dialog对话框程序 直接点击完成,创建工程结束。 2.删掉对话框上面...
  • sz76211822
  • sz76211822
  • 2017年02月22日 19:40
  • 752

OpenGL入门14——MFC窗口中使用glut函数

调用glut函数前,要初始化glut,即调用glutInit(); 例如:         int argc=1; char* argv[]={"good"}; glutInit(&argc, arg...
  • xiaoxiaoyusheng2012
  • xiaoxiaoyusheng2012
  • 2015年04月23日 20:13
  • 3283

在Visual Studio 2013下利用MFC框架配置OpenGL

PS:这是弱鸡第一次写这样的总结,请多指教!需要做好的工作: 把.h文件放入D:\Program Files\Microsoft Visual Studio 12.0(前面的是Visual Studi...
  • chenzhenyu123456
  • chenzhenyu123456
  • 2017年03月12日 13:19
  • 941

OpenGL在MFC中的实现

转自http://www.cnblogs.com/carfield/archive/2011/11/20/2255780.html 1、在写代码之前,首先要做好OpenGL的环境配置。 ...
  • sircarfield
  • sircarfield
  • 2011年11月20日 10:06
  • 10530

MFC框架下OpenGL绘图基础例程 自带glut dll

  • 2014年09月29日 19:22
  • 405KB
  • 下载

OpenGL在MFC中的使用总结(二)——派生的OpenGL类

有些时候用到OpenGL需要每次进行配置,有点麻烦,可以直接基于CWND派生一个OpenGL类,在需要的时候直接用就可以了。下面附赠上这样一个类,其中删掉了我项目具体绘制的一些东西,如有错误不能用请联...
  • lyh03601
  • lyh03601
  • 2014年08月21日 16:27
  • 1429

制作一个基于MFC对话框的OpenGL类

今天写了个基于MFC对话框的OpenGL类:COpenGL,可以在对话框程序中使用OpenGL了,并且提供了全屏与非全屏转换的两个函数,很容易使用,速度快。  使用方法:在对话框上加一个Static控...
  • fxpopboy
  • fxpopboy
  • 2006年12月29日 19:21
  • 567
收藏助手
不良信息举报
您举报文章:OpenGL在MFC中的使用总结(一)——基本框架
举报原因:
原因补充:

(最多只允许输入30个字)