OpenGLで描画した内容をMFCのコントロール上に描画する
最近、MFCを使うようになったので、ちょっとGLとMFCを組み合わせてみたサンプルの備忘録。
MFCで、3D表示を行うソフトを作ろうとする場合、
GLの初期化など、毎度毎度面倒な処理が多々あるので、サブクラス化をしてまとめました。
MFCでGLを使いたいだけであれば、記事の最後の「再利用方法」の手順を行えばOKです。
結果画面
MFCのピクチャーコントロールを貼り付けて、そこにOpenGLで描画を行っています。
サブクラス化について
サブクラス化という言葉はちょっと紛らわしいので、以下にまとめておきます。
- C++などの言語使用上での意味
- あるクラスを継承して派生クラスを作成すること。
- Win32APIでのサブクラス化
- ウィンドウや、エディットコントロールなど各種コントロールのインスタンスが持つウィンドウプロシージャを、独自のコールバック関数に入れ替えて動作をカスタマイズすること。
今回のテーマで用いる「サブクラス化」という言葉は、後者のコントロールの特殊化を指します。
ただし、MFCではコントロールのサブクラス化を行う際に、CEditなどMFCのクラスを継承して独自のクラス定義を行うため、前者の意味でのサブクラス化も同時に行うことになります。
MFCでのコントロールのサブクラス化の方法
方法は何種類かあるみたいですが、ココでは比較的簡単な以下の手順でサブクラス化を行います。
GL+PictureControl
ということで、PictureControlをサブクラス化して、OpenGLの設定をする手順を以下に書いておきます。
今回のサンプルは、ダイアログベースアプリで説明をします。
(MDI, SDiの場合も、PictureContorolを同様にサブクラス化すればできます。)
DDX変数の型変更
先ほど作成したDDX変数の型を、新たに作成したクラスに変更します。
変更前 | CStatic m_xcGLPicture; |
---|---|
変更後 | CGLPictureCtrl m_xcGLPicture; |
ここまでの手順で、PictureControlをサブクラス化した雛形が作成できました。
次に、このサブクラス内に、OpenGLで描画を行うための処理を追記します。
新規クラスのソースに以下のコードを追加
GLPictureCtrl.cppの先頭に以下のコードを追記します。
OpenGLを使うためのヘッダのインクルードとライブラリの設定
// GLPictureCtrl.cpp : 実装ファイル // #include "stdafx.h" #include "MFCGL.h" #include "GLPictureCtrl.h" // OpenGLのヘッダをインクルード #include <GL/gl.h> #include <GL/glu.h> #pragma comment(lib, "glu32.lib") #pragma comment(lib, "opengl32.lib")
ヘッダに以下のコードを追加
protected:
HGLRC m_hRC;
CDC* m_pDC;
BOOL SetupPixelFormat();
BOOL InitGLContext();
OpenGLの初期化・破棄の関数作成
まず、OpenGLで描画するためのレンダリングコンテキストの作成・破棄をする関数を用意します。
以下の関数を追加。
BOOL CGLPictureCtrl::SetupPixelFormat() { PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0 }; int pixelformat; if (0 == (pixelformat = ::ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd))) { return FALSE; } if (FALSE == ::SetPixelFormat(m_pDC->GetSafeHdc(), pixelformat, &pfd)) { return FALSE; } return TRUE; } BOOL CGLPictureCtrl::InitGLContext() { m_pDC = new CClientDC(this); if (NULL == m_pDC) { return FALSE; } if (!SetupPixelFormat()) return FALSE; if (0 == (m_hRC = ::wglCreateContext(m_pDC->GetSafeHdc()))) { return FALSE; } if (FALSE == ::wglMakeCurrent(m_pDC->GetSafeHdc(), m_hRC)) { return FALSE; } return TRUE; }
オーバーライド、イベントの追加
・PreSubclassWindow
今回のサブクラス化の方法では、CGLPictureCtrlのインスタンスはアプリ作成時にはすでに作成されていて、サブクラス化した後ではOnCreateなどのイベントは呼ばれません。
そこで、サブクラス化をしたときに呼び出されるPreSubclassWindowというメソッドで、初期化を行います。
以下の手順で、PreSubclassWindowをオーバーライド。
PreSubclassWindowに以下のコードを追加します。
void CGLPictureCtrl::PreSubclassWindow() { // TODO: ここに特定なコードを追加するか、もしくは基本クラスを呼び出してください。 LONG style = GetWindowLong(this->m_hWnd, GWL_STYLE ); style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN ; SetWindowLong( this->m_hWnd, GWL_STYLE, style ); InitGLContext(); CStatic::PreSubclassWindow(); }
・OnDestroy
レンダリングコンテキストの破棄は、OnDestroyで行います。
OnDestroyに以下のコードを記述。
void CGLPictureCtrl::OnDestroy() { CStatic::OnDestroy(); // TODO: ここにメッセージ ハンドラ コードを追加します。 if(FALSE == ::wglMakeCurrent(NULL, NULL)) { // 必要に応じてエラーハンドリング } if(FALSE == ::wglDeleteContext(m_hRC)) { // 必要に応じてエラーハンドリング } if(m_pDC) delete m_pDC; }
・OnPaint
OpenGLでの描画は、OnPaintに記述します。ここでは、サンプルとして四角形の描画だけを行っています。
void CGLPictureCtrl::OnPaint() { CPaintDC dc(this); // device context for painting // TODO: ここにメッセージ ハンドラ コードを追加します。 // 描画メッセージで CStatic::OnPaint() を呼び出さないでください。 ::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); // とりあえず、四角形を描画してみる。 ::glPushMatrix(); ::glColor3f( 0.0f, 1.0f, 0.0f ); glColor3d(1.0, 0.0, 0.0); glBegin(GL_POLYGON); glVertex2d(-0.9, -0.9); glVertex2d(0.9, -0.9); glVertex2d(0.9, 0.9); glVertex2d(-0.9, 0.9); glEnd(); ::glPopMatrix(); ::glFinish(); if( FALSE == ::SwapBuffers( m_pDC->GetSafeHdc())){} }
・OnMove
画面をすばやく動かしたときに再描画が上手くされない場合があったので、以下の処理を追加。
(SDIのフォームビューでこのクラスを動作させた際に、フォームをスクロールさせた場合に再描画が行われないことがあったので・・・)
void CGLPictureCtrl::OnMove(int x, int y) { CStatic::OnMove(x, y); // TODO: ここにメッセージ ハンドラ コードを追加します。 Invalidate(TRUE); UpdateWindow(); }
再利用方法
ここで作成した、CGLPictureCtrlを再利用する際は、以下のような手順でOKです。
- ダイアログorフォームにPictureControlを作成。
- PictureControlのIDを任意の名前に変更
- PictureControlにDDX変数(コントロール変数)を作成
- DDX変数が追加されたヘッダーファイルに、CGLPictureCtrlクラスのヘッダーをインクルードする。
- 作成したDDX変数の型をCGLPictureCtrlに変更する。
- CGLPictureCtrlでインクルードしている、○○.h(MFCで自動生成される、アプリケーションクラスが定義されているヘッダーファイル)を、現在のプロジェクトのアプリケーションクラスのヘッダファイルに変更する。
- (必要に応じて)PictureControlのNotifyプロパティをTrueに変更する。
これで、今回作成したものと同様の、OpenGLで描画する機能を持ったピクチャーコントロールが簡単に作成できます。
以下に、サブクラス化したファイルの全体のコードを張っておきます。
GLPictureCtrl.h
#pragma once // CGLPictureCtrl class CGLPictureCtrl : public CStatic { DECLARE_DYNAMIC(CGLPictureCtrl) public: CGLPictureCtrl(); virtual ~CGLPictureCtrl(); // ★★追加 protected: HGLRC m_hRC; CDC* m_pDC; BOOL SetupPixelFormat(); BOOL InitGLContext(); protected: DECLARE_MESSAGE_MAP() virtual void PreSubclassWindow(); public: afx_msg void OnDestroy(); afx_msg void OnPaint(); afx_msg void OnMove(int x, int y); };
GLPictureCtrl.cpp
// GLPictureCtrl.cpp : 実装ファイル // #include "stdafx.h" #include "MFCGL.h" #include "GLPictureCtrl.h" #include <GL/gl.h> // ★★追加 #include <GL/glu.h> #pragma comment(lib, "glu32.lib") #pragma comment(lib, "opengl32.lib") // CGLPictureCtrl IMPLEMENT_DYNAMIC(CGLPictureCtrl, CStatic) CGLPictureCtrl::CGLPictureCtrl() { } CGLPictureCtrl::~CGLPictureCtrl() { } BEGIN_MESSAGE_MAP(CGLPictureCtrl, CStatic) ON_WM_DESTROY() ON_WM_PAINT() ON_WM_MOVE() END_MESSAGE_MAP() BOOL CGLPictureCtrl::SetupPixelFormat() { PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0 }; int pixelformat; if (0 == (pixelformat = ::ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd))) { return FALSE; } if (FALSE == ::SetPixelFormat(m_pDC->GetSafeHdc(), pixelformat, &pfd)) { return FALSE; } return TRUE; } BOOL CGLPictureCtrl::InitGLContext() { m_pDC = new CClientDC(this); if (NULL == m_pDC) { return FALSE; } if (!SetupPixelFormat()) return FALSE; if (0 == (m_hRC = ::wglCreateContext(m_pDC->GetSafeHdc()))) { return FALSE; } if (FALSE == ::wglMakeCurrent(m_pDC->GetSafeHdc(), m_hRC)) { return FALSE; } return TRUE; } // CGLPictureCtrl メッセージ ハンドラ void CGLPictureCtrl::PreSubclassWindow() { // TODO: ここに特定なコードを追加するか、もしくは基本クラスを呼び出してください。 LONG style = GetWindowLong(this->m_hWnd, GWL_STYLE ); style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN ; SetWindowLong( this->m_hWnd, GWL_STYLE, style ); InitGLContext(); CStatic::PreSubclassWindow(); } void CGLPictureCtrl::OnDestroy() { CStatic::OnDestroy(); // TODO: ここにメッセージ ハンドラ コードを追加します。 if(FALSE == ::wglMakeCurrent(NULL, NULL)) { // 必要に応じてエラーハンドリング } if(FALSE == ::wglDeleteContext(m_hRC)) { // 必要に応じてエラーハンドリング } if(m_pDC) delete m_pDC; } void CGLPictureCtrl::OnPaint() { CPaintDC dc(this); // device context for painting // TODO: ここにメッセージ ハンドラ コードを追加します。 // 描画メッセージで CStatic::OnPaint() を呼び出さないでください。 ::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); // とりあえず、四角形を描画してみる。 ::glPushMatrix(); ::glColor3f( 0.0f, 1.0f, 0.0f ); glColor3d(1.0, 0.0, 0.0); glBegin(GL_POLYGON); glVertex2d(-0.9, -0.9); glVertex2d(0.9, -0.9); glVertex2d(0.9, 0.9); glVertex2d(-0.9, 0.9); glEnd(); ::glPopMatrix(); ::glFinish(); if( FALSE == ::SwapBuffers( m_pDC->GetSafeHdc())){} } void CGLPictureCtrl::OnMove(int x, int y) { CStatic::OnMove(x, y); // TODO: ここにメッセージ ハンドラ コードを追加します。 Invalidate(TRUE); UpdateWindow(); }