在MFC中使用OpenGL

mfcandopengl老外的教程果然还是非常赞的,强烈推荐原版,这篇博客简单介绍一下步骤:

        1.新建MFC工程

        2.添加Picture Control控件

        3.添加一个OpenGL控件类

        4.在MFC中使用OpenGL控件

        获取博主编译的测试工程(使用VS2013 X64平台,你还需要另外配置OpenGL相关环境),运行效果如左图所示,这个Demo还可以接受鼠标的控制,对模型进行操作。如果你需要多个OpenGL窗口,再实例化这个类,可以很轻易地实现多个窗口的绘制。这里就不翻译老外网站上的教程了,在上面给出了框架搭建的链接,这里就直接介绍该OpenGL类的使用方法吧。

        这里特别强调一下第二步,以防阅读不仔细的同学产生不必要的错误。修改Picture Control控件:visible 为 FALSE,控件ID位 IDC_OPENGL。

        你可能会有疑问,为什么visible要设置为FALSE?这里的控件只是提供了一个绘图区域的坐标信息,绘图过程是由Opengl根据坐标直接绘制的,与控件无关,如果控件存在,会遮挡住绘图区域,所以要设置visible位FALSE。

        添加OpenGL控件,这个控件类的搭建方法在这里。我在构建代码时使用的OpenGL库为glew和freeglut库,如果你刚刚接触OpenGL,本人强力推荐NeHe的OpenGL教程。这里给出这个控件类的源码:

#ifndef OPENGL_CONTROL_
#define OPENGL_CONTROL_
#include "afxwin.h"
#include <GL\glew.h>
#include <GL\freeglut.h>
#pragma comment(lib, "glew32s.lib")
#ifdef _DEBUG
#pragma comment(lib, "debug/freeglut_static.lib")
#else
#pragma comment(lib, "release/freeglut_static.lib")
#endif
class COpenGLControl :
	public CWnd
{
public:
	COpenGLControl();
	~COpenGLControl();

	void	oglCreate(CRect rect, CWnd *parent);
	void	oglInitialize(void);
	void	oglDrawScene(void);
	
public:
	UINT_PTR m_unpTimer;
	float	 m_fLastX;
	float	 m_fLastY;
	bool	 m_bIsMaximized;
	GLfloat  m_fPosX;    // X position of model in camera view
	GLfloat  m_fPosY;    // Y position of model in camera view
	GLfloat  m_fZoom;   // Zoom on model in camera view
	GLfloat  m_fRotX;    // Rotation on model in camera view
	GLfloat  m_fRotY;    // Rotation on model in camera view

public:
	CWnd*	hWnd;
	HDC		hdc;
	HGLRC	hrc;
	int		m_nPixelFormat;
	CRect	m_rect;
	CRect	m_oldWindow;
	CRect	m_originalRect;
	
	afx_msg void OnPaint();
	afx_msg void OnDraw(CDC *pDC);
	afx_msg void OnTimer(UINT_PTR nIDEvent);
	afx_msg void OnSize(UINT nType, int cx, int cy);
	afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);

	DECLARE_MESSAGE_MAP()
};

#endif

上面是类的头,下面是类的实现。

#include "stdafx.h"
#include "OpenGLControl.h"

COpenGLControl::COpenGLControl()
{
	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
	m_bIsMaximized = false;
}

COpenGLControl::~COpenGLControl()
{
}
BEGIN_MESSAGE_MAP(COpenGLControl, CWnd)
	ON_WM_PAINT()
	ON_WM_TIMER()
	ON_WM_SIZE()
	ON_WM_CREATE()
	ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()

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, L"OpenGL", WS_CHILD | WS_VISIBLE |
		WS_CLIPSIBLINGS | WS_CLIPCHILDREN, rect, parent, 0);

	// Set initial variables' values
	m_oldWindow = rect;
	m_originalRect = rect;

	hWnd = parent;
}

void COpenGLControl::OnPaint()
{
	//CPaintDC dc(this); // device context for painting
	// TODO: Add your message handler code here
	// Do not call CWnd::OnPaint() for painting messages
	ValidateRect(NULL);

	CPaintDC dc(this);
	OnDraw(&dc);
}

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,
	};
	hdc = GetDC()->m_hDC; // Get device context only once.	
	m_nPixelFormat = ChoosePixelFormat(hdc, &pfd); // Pixel format.
	SetPixelFormat(hdc, m_nPixelFormat, &pfd);
	hrc = wglCreateContext(hdc); // Create the OpenGL Rendering Context.
	wglMakeCurrent(hdc, hrc);
	
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);// Set color to use when clearing the background.
	glClearDepth(1.0f);
	glFrontFace(GL_CCW); // Turn on backface culling
	glCullFace(GL_BACK);
	glEnable(GL_DEPTH_TEST); // Turn on depth testing
	glDepthFunc(GL_LEQUAL);
	OnDraw(NULL); // Send draw request
}

void COpenGLControl::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: Add your message handler code here and/or call default
	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);
}

void COpenGLControl::OnSize(UINT nType, int cx, int cy)
{
	CWnd::OnSize(nType, cx, cy);
	// TODO: Add your message handler code here
	glViewport(0, 0, cx, cy); // Map the OpenGL coordinates.
	glMatrixMode(GL_PROJECTION); // Projection view
	glLoadIdentity();
	gluPerspective(35.0f, (float)cx / (float)cy, 0.01f, 2000.0f); // Set our current view perspective
	glMatrixMode(GL_MODELVIEW); // Model view

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

void COpenGLControl::OnDraw(CDC *pDC)
{
	// TODO: Camera controls.
	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);
}

int COpenGLControl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CWnd::OnCreate(lpCreateStruct) == -1)
		return -1;
	// TODO:  Add your specialized creation code here
	oglInitialize();
	return 0;
}

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();
}

void COpenGLControl::OnMouseMove(UINT nFlags, CPoint point)
{
	// TODO: Add your message handler code here and/or call default
	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);
}

下面进入正题,我们由教程中的方法构建了一个控件类,我们要在对话框类中使用该控件,需要在对话框类中修改如下代码:

在对话框类中包含该控件的头,并且添加控件变量

#include "OpenGLControl.h"
class ...Dlg{..
......
private:
	COpenGLControl m_oglWindow;
......
}

在对话框初始化时,添加如下代码:

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

在类向导中添加WM_SIZE消息的处理:

void CMFCwithOpenglDlg::OnSize(UINT nType, int cx, int cy)
{
	CDialogEx::OnSize(nType, cx, cy);
	// TODO: Add your message handler code here
	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;
	}
	}
}

        这样我们就可以实现在MFC中绘制OpenGL了,如果有更多疑惑,请参照文章开头提供的源码,或者查看原版教程或对比源码(博主编译的版本和原版教程提供的源码均可编译通过),祝好。如果要使用OpenGL抓取图像,可以参照我下一篇博客。

        OK, See You Next Chapter!

发表评论