向MFC嵌入OpenGL

这篇教程详细介绍了如何在MFC对话框应用程序中创建一个OpenGL渲染上下文,包括设置OpenGL窗口、创建控制模块、添加OpenGL类、链接库、定义类变量、添加消息处理函数,以及设置摄像机和窗口大小调整处理。教程适合初学者,旨在帮助开发者在Visual Studio环境下集成OpenGL到MFC项目。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


      本人上计算机图形学课时要做的大作业是基于MFC框架用OpenGL实现一些功能。但是我一开始并不知道怎么在MFC中加入OpenGL模块。于是我在网上找了半天终于找到一篇文章,阅读之后受益匪浅。今天我准备把这篇文章翻译出来。

      原文地址:Setting Up OpenGL in an MFC Control

      翻译如下:

      我在网上没有看到许多文章关于集成功能的,哪怕仅仅是在一个MFC控制结构中设置基本的OpenGL窗口的文章也没有。本教程的目的是一步一步地教大家如何在MFC控制结构(例如图片控制)中初始化一个OpenGL渲染上下文(rendering context),还有基于定时器、改变大小事件、基本相机功能等的基本绘制功能。在本教程中我将在Microsoft Visual Studio .NET 2003 的环境下操作(注:译者用的是VS2015),而且我将尽量让各个水平的人都能理解。我将为初学者增加一些特定的步骤,非初学者也可以跳过这些步骤去找自己所需要的部分。无论如何,欢迎大家的评论(无论好坏),我将近我全力去更新此文直到每个人都满意。谢谢大家。那么让我们开始吧!

  第一部分:创建最初的OpenGL窗口

 第1步:创建工程

 首先我们先要创建一个MFC对话框应用程序项目。文件->新建->项目 选择MFC应用程序并将其命名为"oglMFCDialog"。把工程保存在哪里取决与你,你只要能记住你把它存到哪儿就行了。当MFC应用程序向导出现的时候,在应用程序类型中选择基于对话框然后点击完成按钮。其他附加的设置选项在用户界面功能中。比如说我就选择了最小化框。

 第2步 创建控制模块

 在资源浏览器标签中找到资源视图,展开Dialog文件夹,然后双击自动生成的IDD_OGLMFCDIALOG_DIALOG对话框。你需要添加一个控制组件用于OpenGL内容的渲染。一个简单的图片控制组件就可以,那么就在工具箱中选择Picture Control吧。

    注意:如果工具箱没有出现,在菜单栏中找视图->工具箱把它调出来。

 你可以直接将组建拽到你的对话窗口,也可以随时调整大小。

 你将需要对这个Picture Control设置一些属性值。当这个Picture Control被选中时,它的属性在右下角,设定以下属性:

 Visible:False

 ID:IDC_OPENGL

 你也许觉得很奇怪为什么将Visible属性设为False。当你在任何一个MFC中加载OpenGL渲染内容时,你能用到的只是这个控件的方形位置来辅助绘制OpenGL内容。由于一些奇怪的原因,如果Visible设为True,这个控件就会覆盖掉你的OpenGL内容。

 第3步 添加OpenGL类

 接下来,为了设置OpenGL,我选择为其添加一个单独的类。把工程里的主要部分分成单独的类,这是一个好习惯。因此我将把OpenGL和MFC分离。

 要添加一个类,点击解决方案资源管理器标签,在树中右击oglMFCDialog工程,选择添加->添加类,再从模板中选择C++类然后点击添加。当一般C++类向导出现时,设置以下属性:

 类名:COpenGLControl

 基类:CWnd

 访问(Access):public

 勾选虚析构函数

 然后点击完成,就创建成功了。

 第4步 添加项目库(Adding Project Libraries)

 由于你要用到OpenGL的渲染功能,你还需要向工程中链接一些库。在树中再次右击oglMFCDialog项目,选择属性,在属性页中选择链接器->输入,设置以下变量:

 附加依赖项:opengl32.lib;glu32.lib

 注意:这两个*.lib文件中间不要有任何空格

 第5步 设置类的变量

 在本工程的范围内,一些变量需要被添加到OpenGLControl.h中,公有的和私有的都需要,还需要添加#include语句,代码如下:

 

#include <gl/gl.h>
#include <gl/glu.h>
 
class COpenGLControl : public CWnd
{
   public:
      /******************/
      /* PUBLIC MEMBERS */
      /******************/
      // Timer
      UINT_PTR m_unpTimer;
 
   private:
      /*******************/
      /* PRIVATE MEMBERS */
      /*******************/
      // Window information
      CWnd    *hWnd;
      HDC     hdc;
      HGLRC   hrc;
      int     m_nPixelFormat;
      CRect   m_rect;
      CRect   m_oldWindow;
      CRect   m_originalRect;
  特别注意:我把函数按照特定顺序排列,这是非常重要的。因为如果有些函数在别的函数之前被实例化,就会有一个编译错误,这个错误是protected成员引起的。所以,我把这些函数按照以下顺序排列:

 1.构造和析构函数。

 2.手动添加的函数。

 3.自动添加的"afx_msg"函数。

 4.DECLARE_MESSAGE_MAP()调用。

 第6步 添加oglCreate函数

 一个新的函数将添加到OpenGLControl.hOpenGLControl.cpp中。该函数将负责搭建一些基本的窗口变量和对MFC来说很重要的函数调用。我把这个函数命名为oglCreate。添加的代码如下:


 OpenGLControl.h

void oglCreate(CRect rect, CWnd *parent);
 

 OpenGLControl.cpp

void COpenGLControl::oglCreate(CRect rect, CWnd *parent)
{
   CString className = AfxRegisterWndClass(CS_HREDRAW |
      CS_VREDRAW | CS_OWNDC, NULL,
      (HBRUSH)GetStockObject(BLACK_BRUSH), NULL);
 
   CreateEx(0, className, "OpenGL", WS_CHILD | WS_VISIBLE |
            WS_CLIPSIBLINGS | WS_CLIPCHILDREN, rect, parent, 0);
 
   // Set initial variables' values
   m_oldWindow    = rect;
   m_originalRect = rect;
 
   hWnd = parent;
}

  第7步 添加OnPaint函数

 接下来需要添加一个消息类函数。该函数与其他函数的区别也很好辨认。当程序中有事件发生时,MFC会调用特定的消息(以WM_为前缀),例如OnPaint,OnSize,OnCreate等。要通过Visual Studio添加消息,你可以在属性窗口中找消息按钮(闪电图标右边的那个)。如果你的光标在你选定的文件中,这就是你添加事件的地方。

 说完了这些,首先你需要创建OnPaint事件,通过属性->消息窗口下找到WM_PAINT事件。然后再下拉列表中选择<Add>OnPaint。你注意到消息函数同时加到OpenGLControl.hOpenGLControl.cpp中。但是,与用户添加的函数不同的是,这个函数有"afx_msg"的前缀,还会在cpp文件的顶部加一个调用。我建议大家不要更改Visual Studio自动增加的东西,除非你知道它们是干什么的。

 在OnPaint函数内部,只有一小部分自动生成的代码需要更改。你自己渲染(或者说是绘制)OpenGL窗口的方式与MFC不同,不同在你是通过定时器来绘制的。以防万一接下来需要固定一个特定的帧率。由于要通过定时器来渲染窗口,OnPaint函数就不能按照原有的方式调用。因此就需要加上简单的一行来代替原有的调用。以下代码需要加到OpenGLControl.cpp文件中的OnPaint函数。

 OpenGLControl.cpp

void COpenGLControl::OnPaint()
{
   //CPaintDC dc(this);    // device context for painting
   ValidateRect(NULL);
}


 第8步 添加OnCreate函数

 接下来,按照之前的方法在添加一个消息类,这次选择WM_CREATE消息,选择<Add>OnCreate。和之前一样,新的类也会加到.h和.cpp文件中。当程序调用到它时,你只需要添加一行代码调用之后会增加的函数,oglInitialize函数。

 注意:当然,你还没有添加这个函数,运行时会有编译错误。

 OpenGLControl.cpp

 

int COpenGLControl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
   if (CWnd::OnCreate(lpCreateStruct) == -1)
      return -1;
 
   oglInitialize();
 
   return 0;
}

  第9步 添加oglInitialize函数

 正如前文所说的那样,接下来我们将手动的添加这个函数,即oglInitialize函数,该函数在你的OpenGL类被创建的时候调用(通过OnCreate消息)。oglInitialize函数将负责设定所有OpenGL渲染所需要的信息。这些信息包括像素格式、渲染上下文以及清除颜色以确保程序正常交换缓存。

 注意:下面的*.cpp代码片段的最后一行电泳OnDraw函数,该函数还没有创建,此时运行将有编译错误。

OpenGLControl.h

void oglInitialize(void);
OpenGLControl.cpp

void COpenGLControl::oglInitialize(void)
{
   // Initial Setup:
   //
   static PIXELFORMATDESCRIPTOR pfd =
   {
      sizeof(PIXELFORMATDESCRIPTOR),
      1,
      PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
      PFD_TYPE_RGBA,
      32,    // bit depth
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      16,    // z-buffer depth
      0, 0, 0, 0, 0, 0, 0,
   };
 
   // Get device context only once.
   hdc = GetDC()->m_hDC;
 
   // Pixel format.
   m_nPixelFormat = ChoosePixelFormat(hdc, &pfd);
   SetPixelFormat(hdc, m_nPixelFormat, &pfd);
 
   // Create the OpenGL Rendering Context.
   hrc = wglCreateContext(hdc);
   wglMakeCurrent(hdc, hrc);
 
   // Basic Setup:
   //
   // Set color to use when clearing the background.
   glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
   glClearDepth(1.0f);
 
   // Turn on backface culling
   glFrontFace(GL_CCW);
   glCullFace(GL_BACK);
 
   // Turn on depth testing
   glEnable(GL_DEPTH_TEST);
   glDepthFunc(GL_LEQUAL);
 
   // Send draw request
   OnDraw(NULL);
}

   第10步 添加OnDraw函数

 接下来,我们将添加之前在oglInitialize函数中提到的OnDraw函数。这个函数将会扮演消息函数,但是需要手动添加。正如你归纳出来的那样,在该函数的声明语句中需要在前面添加"afx_msg"前缀。你将注意到在cpp代码片段中你实际上什么也不用做,只有一个TODO注释。在本教程后面部分会用它实现一些功能,例如摄像机控制等。

 注意:如果你现在运行该程序,他并不会报错,但不幸的是在控件中什么也没有,这取决于你在OnPaint函数中做出的更改。

 OpenGLControl.h

afx_msg void OnDraw(CDC *pDC);

  OpenGLControl.cpp

void COpenGLControl::OnDraw(CDC *pDC)
{
   // TODO: Camera controls.
}

  第11步 添加OnTimer函数

 现在是时候用定时器来添加之前OnPaint函数的功能了。让我们添加一个新的消息类,选择WM_TIMER消息,然后在下拉列表中选择<Add>OnTimer来创建函数。

 OnTimer函数的代码十分的直白。这个类的定时器每次调用的时程序都会通过该函数执行。因此,函数自动传入的值是一个无符号整数,对应着你手动创建的定时器。使用select声明(译者认为应该是switch),我为定时器创建了相应的case。基本的"绘制并交换缓存"过程将在这个定时器里。在本教程的前半部分中你不用绘制任何图形,只需要正确的交换缓存即可。

 OpenGLControl.cpp

void COpenGLControl::OnTimer(UINT nIDEvent)
{
   switch (nIDEvent)
   {
      case 1:
      {
         // Clear color and depth buffer bits
         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
         // Draw OpenGL scene
         // oglDrawScene();
 
         // Swap buffers
         SwapBuffers(hdc);
 
         break;
      }
 
      default:
         break;
   }
 
   CWnd::OnTimer(nIDEvent);
}

 第12步 添加OnSize函数

 你需要添加的最后一个消息函数是OnSize函数。和之前一样,选择WM_SIZE消息并在下拉列表中选择<Add>OnSize

 OnSize函数在窗口大小改变时被调用。为了防止窗口大小改变造成的渲染窗口的失常,透视及视口等需要调整。在本教程中你其实不需要随窗口大小调整,但是在必要时可以通过以下函数来实现:

 OpenGLControl.cpp

void COpenGLControl::OnSize(UINT nType, int cx, int cy)
{
   CWnd::OnSize(nType, cx, cy);
 
   if (0 >= cx || 0 >= cy || nType == SIZE_MINIMIZED) return;
 
   // Map the OpenGL coordinates.
   glViewport(0, 0, cx, cy);
 
   // Projection view
   glMatrixMode(GL_PROJECTION);
 
   glLoadIdentity();
 
   // Set our current view perspective
   gluPerspective(35.0f, (float)cx / (float)cy, 0.01f, 2000.0f);
 
   // Model view
   glMatrixMode(GL_MODELVIEW);
}

现在你已经完成了 COpenGLControl类(至少是初始化)。接下来就是将他集成到你的主MFC窗口,即 CoglMFCDialogDlg类。

 第13步 定制主MFC对话框类

 你的最后一步就是将你的OpenGL类集成到主MFC对话框中。这个过程非常简单。首先在oglMFCDialogDlg.h文件中将你的OpenGL类包含进去,然后添加一个COpenGLControl类作为本地变量。

 oglMFCDialogDlg.h

#include "OpenGLControl.h"
 
class CoglMFCDialogDlg : public CDialog
{
   private:
      COpenGLControl m_oglWindow;
   .
   .
   .
 接下来,在 oglMFCDialogDlg.cpp文件中已经存在的 OnInitDialog函数中return语句之前加上以下代码就可以在你创建的picture控件中启动定时器,渲染OpenGL内容。

 注意:在出事测试中,我将定时器的间隔设为1毫秒。

 oglMFCDialogDlg.cpp::OnInitDialog

.
.
.
CRect rect;
 
// Get size and position of the picture control
GetDlgItem(IDC_OPENGL)->GetWindowRect(rect);
 
// Convert screen coordinates to client coordinates
ScreenToClient(rect);
 
// Create OpenGL Control window
m_oglWindow.oglCreate(rect, this);
 
// Setup the OpenGL Window's timer to render
m_oglWindow.m_unpTimer = m_oglWindow.SetTimer(1, 1, 0);
.
.
.

  第14步 初始化 OpenGL 控件结果

 现在你完成了!现在你应该看到黑色的背景。现在你可以在OnTimer中添加绘制函数。


  第二部分:扩展OpenGL的功能(附加)

 第15步 绘制几何图形

 下一步就是在你的OpenGL空间中绘制一些测试图形。不幸的是,你需要设置摄像机系统,否则无法看到这些图形。如果没有摄像机直向特定点的话,你绘制的图形将被放置在摄像机原本的位置上,这样你就什么也看不见了。所以如果你想先看到你画的东西,请先看第16步:设置Maya风格的摄像机,在第16步中你将创建一个Alias|Wavefront Maya风格的摄像机(用鼠标进行旋转/缩放/平移)。但是我选择在下一步中做这些。

 无论如何,本教程绘制的图形是十分简单的,就画一个立方体就行了(6个方形面)。如果你再次调用OnTimer函数,将会调用oglDrawScene函数,你将创建这个函数。

 OpenGLControl.h

void oglDrawScene(void);
 OpenGLControl.cpp

void COpenGLControl::oglDrawScene(void)
{
   // Wireframe Mode
   glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
 
   glBegin(GL_QUADS);
      // Top Side
      glVertex3f( 1.0f, 1.0f,  1.0f);
      glVertex3f( 1.0f, 1.0f, -1.0f);
      glVertex3f(-1.0f, 1.0f, -1.0f);
      glVertex3f(-1.0f, 1.0f,  1.0f);
 
      // Bottom Side
      glVertex3f(-1.0f, -1.0f, -1.0f);
      glVertex3f( 1.0f, -1.0f, -1.0f);
      glVertex3f( 1.0f, -1.0f,  1.0f);
      glVertex3f(-1.0f, -1.0f,  1.0f);
 
      // Front Side
      glVertex3f( 1.0f,  1.0f, 1.0f);
      glVertex3f(-1.0f,  1.0f, 1.0f);
      glVertex3f(-1.0f, -1.0f, 1.0f);
      glVertex3f( 1.0f, -1.0f, 1.0f);
 
      // Back Side
      glVertex3f(-1.0f, -1.0f, -1.0f);
      glVertex3f(-1.0f,  1.0f, -1.0f);
      glVertex3f( 1.0f,  1.0f, -1.0f);
      glVertex3f( 1.0f, -1.0f, -1.0f);
 
      // Left Side
      glVertex3f(-1.0f, -1.0f, -1.0f);
      glVertex3f(-1.0f, -1.0f,  1.0f);
      glVertex3f(-1.0f,  1.0f,  1.0f);
      glVertex3f(-1.0f,  1.0f, -1.0f);
 
      // Right Side
      glVertex3f( 1.0f,  1.0f,  1.0f);
      glVertex3f( 1.0f, -1.0f,  1.0f);
      glVertex3f( 1.0f, -1.0f, -1.0f);
      glVertex3f( 1.0f,  1.0f, -1.0f);
   glEnd();
}
 现在,你最后要做的是替换 OnTimer函数的注释部分,调用 oglDrawScene函数。

 OpenGLControl.cpp::OnTimer

.
.
.
// Clear color and depth buffer bits
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
// Draw OpenGL scene
oglDrawScene();
 
// Swap buffers
SwapBuffers(hdc);
.
.
.
 第16步 设置Maya风格的摄像机

 Maya风格的摄像机指的是在Alias|Wavefront Maya中使用的摄像机系统。鼠标左键旋转,右键缩放,中键平移。要实现这个功能,你需要创建一个OnMouseMove函数调用。

 选择WM_MOUSEMOVE消息,在下拉菜单中选择<Add>OnMouseMove,创建函数。在新建的函数里,你需要为每个按键添加相应的操作。如果你回想起之前的内容,你创建一些成员变量用于旋转、缩放和平移。你将为这些变量设置新的值。
 OpenGLControl.h

afx_msg void OnMouseMove(UINT nFlags, CPoint point);

  OpenGLControl.cpp

void COpenGLControl::OnMouseMove(UINT nFlags, CPoint point)
{
   int diffX = (int)(point.x - m_fLastX);
   int diffY = (int)(point.y - m_fLastY);
   m_fLastX  = (float)point.x;
   m_fLastY  = (float)point.y;
 
   // Left mouse button
   if (nFlags & MK_LBUTTON)
   {
      m_fRotX += (float)0.5f * diffY;
 
      if ((m_fRotX > 360.0f) || (m_fRotX < -360.0f))
      {
         m_fRotX = 0.0f;
      }
 
      m_fRotY += (float)0.5f * diffX;
 
      if ((m_fRotY > 360.0f) || (m_fRotY < -360.0f))
      {
         m_fRotY = 0.0f;
      }
   }
 
   // Right mouse button
   else if (nFlags & MK_RBUTTON)
   {
      m_fZoom -= (float)0.1f * diffY;
   }
 
   // Middle mouse button
   else if (nFlags & MK_MBUTTON)
   {
      m_fPosX += (float)0.05f * diffX;
      m_fPosY -= (float)0.05f * diffY;
   }
 
   OnDraw(NULL);
 
   CWnd::OnMouseMove(nFlags, point);
}

 此外,我在 COpenGLControl类的构造函数中加一些初始化语句。

 OpenGLControl.cpp

COpenGLControl::COpenGLControl(void)
{
   m_fPosX = 0.0f;    // X position of model in camera view
   m_fPosY = 0.0f;    // Y position of model in camera view
   m_fZoom = 10.0f;   // Zoom on model in camera view
   m_fRotX = 0.0f;    // Rotation on model in camera view
   m_fRotY = 0.0f;    // Rotation on model in camera view
}
 现在最后要做的是改变 OnDraw函数,将TODO注释改为以下:

 OpenGLControl.cpp::OnDraw

.
.
.
glLoadIdentity();
glTranslatef(0.0f, 0.0f, -m_fZoom);
glTranslatef(m_fPosX, m_fPosY, 0.0f);
glRotatef(m_fRotX, 1.0f, 0.0f, 0.0f);
glRotatef(m_fRotY, 0.0f, 1.0f, 0.0f);
.
.
.

  第17步 正确地改变窗口的大小

改变MFC窗口的大小是一个十分繁琐的工作(或许和Visual Basic相比),不过也能轻松完成。而且一旦你做完一次,你就能一直重复使用这段代码。以下就是我摸索出来的,现在还在用的方法。这涉及到MFC和OpenGL的两个OnSize函数/功能。

 首先,进入oglMFCDialogDlg.cpp文件中按照之前的方法创建OnSize消息函数。这回为你的MFC部分生成OnSize函数,但是你要加一些代码,整个Onsize函数是这样的。

 oglMFCDialogDlg.cpp

void CoglMFCDialogDlg::OnSize(UINT nType, int cx, int cy)
{
   CDialog::OnSize(nType, cx, cy);
 
   switch (nType)
   {
      case SIZE_RESTORED:
      {
         if (m_oglWindow.m_bIsMaximized)
         {
            m_oglWindow.OnSize(nType, cx, cy);
            m_oglWindow.m_bIsMaximized = false;
         }
 
         break;
      }
 
      case SIZE_MAXIMIZED:
      {
         m_oglWindow.OnSize(nType, cx, cy);
         m_oglWindow.m_bIsMaximized = true;
 
         break;
      }
   }
}
 你注意到了你改变了一些没有创建的变量的值。所以你现在要找到OpenGL窗口代码,在已经存在的OnSize函数后面添加以下代码。

   OpenGLControl.cpp

.
.
.
switch (nType)
{
   // If window resize token is "maximize"
   case SIZE_MAXIMIZED:
   {
      // Get the current window rect
      GetWindowRect(m_rect);
 
      // Move the window accordingly
      MoveWindow(6, 6, cx - 14, cy - 14);
 
      // Get the new window rect
      GetWindowRect(m_rect);
 
      // Store our old window as the new rect
      m_oldWindow = m_rect;
 
      break;
   }
 
   // If window resize token is "restore"
   case SIZE_RESTORED:
   {
      // If the window is currently maximized
      if (m_bIsMaximized)
      {
         // Get the current window rect
         GetWindowRect(m_rect);
 
         // Move the window accordingly (to our stored old window)
         MoveWindow(m_oldWindow.left,
                    m_oldWindow.top - 18,
                    m_originalRect.Width() - 4,
                    m_originalRect.Height() - 4);
 
         // Get the new window rect
         GetWindowRect(m_rect);
 
         // Store our old window as the new rect
         m_oldWindow = m_rect;
      }
 
      break;
   }
}
.
.
.

以上就是这些。简单?不简单?请随意发表评论。感谢你的阅读,希望能帮到你。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值