2022年にやることではないとは思いますが、仕事の勉強がてらMFCを使ったグラフィカルなアプリケーションを作りたくなりました. そこでOpenGLを使用したMFCアプリケーションを作ります.
なお、MFCは書籍等も中古でしか見当たらないほど過去のものであり、特別な事情がない限り使用する理由はないです(逆に言えば、今回は仕事で使いそうだという理由からMFCを利用しています).
この記事はその1として、環境構築から矩形を表示するところまでをカバーしています.
2022/8/16 ライブラリに関する記述を一部修正
2022/10/6 内容を修正
環境
項目 | バージョン |
---|---|
OS | Windows11 |
Visual Studio | 2017 Community Edition (ツールセットv141) |
Windows SDK | 10.0.17763.0 |
Glew_static | 2.1.0.3 |
glfw | 3.38 |
プロジェクトの作成
まずは新しいMFCプロジェクトを作成します.ファイル->新規作成->プロジェクトからVisual C++->MFC/ATL->MFCアプリを選択し、好きなプロジェクト名を作成します. 今回はOpenGLという名前にしました. MFCアプリ作成用ウィザードが表示されるので、次の画像のようにプロジェクトを作成します.
上記の画像のようにウィザードを進めたら完了を押してプロジェクトの作成は完了です.作成されたばかりのプロジェクトをビルドすると、次のようなフレームが表示されます.
とりあえずコントロールにOpenGLで描画
さて、ここから実際にMFCのコントロールにOpenGLを用いて描画していきます. 次の記事を参考にさせていただきました.
ざっくりとした流れは次になります
- ピクチャーコントロールをダイアログに追加する
- CStaticクラスを継承したCGLScreenクラスを作る
- ダイアログのソースにCGLScreen型のDDX変数を追加
- CGLScreenのデバイスコンテキストを取得し、OpenGLで矩形を描画する
ピクチャーコントロールの追加
リソースビュー ->OpenGL->OpenGL.rc ->Dialog->IDD_OPENGL_DIALOGを選択し、リソースエディタを開きます. リソースエディタを表示中にツールボックスをクリックすると、様々なコントロールが表示されています. 検索窓でPicture Controlと検索し、Picture Controlをドラッグ&ドロップしてダイアログに追加しましょう. 邪魔なので、中心にあるラベルは削除しておきましょう.
Picture Control追加直後は、コントロールのIDがID_STATICになっているかと思います. 今回はIDC_GLという名前に変更します.
CGLScreenクラスの定義
次にPicture Control用の独自クラスを実装するため、ソリューションエクスプローラー->右クリック->追加->クラス からクラスのソースファイルとヘッダーファイルを同時に生成します.
CGLScreen.h
生成されたCGLScreen.hを以下のようにします.
// CGLScreen.h #pragma once #include <afxwin.h> class CGLScreen : public CStatic { protected: HGLRC m_hRC; CDC* m_pDC; BOOL SetupPixelFormat(); BOOL InitGLContext(); public: virtual void PreSubclassWindow(); DECLARE_MESSAGE_MAP() virtual afx_msg void OnPaint(); virtual afx_msg void OnDestroy(); };
CGLScreen.cpp
生成されたCGLScreen.cppに下のようなコードを追記します.
// CGLScreen.cpp #include "CGLScreen.h" #include <GL/GL.h> #include <GL/GLU.h> BOOL CGLScreen::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 CGLScreen::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; } void CGLScreen::PreSubclassWindow() { LONG style = GetWindowLong(this->m_hWnd, GWL_STYLE); style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN; SetWindowLong(this->m_hWnd, GWL_STYLE, style); InitGLContext(); CStatic::PreSubclassWindow(); } BEGIN_MESSAGE_MAP(CGLScreen, CStatic) ON_WM_PAINT() ON_WM_DESTROY() END_MESSAGE_MAP() void CGLScreen::OnPaint(){ CPaintDC dc(this); // device context for painting ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //とりあえず、四角形を描画してみる。 ::glPushMatrix(); ::glColor3f(1.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 CGLScreen::OnDestroy() { CStatic::OnDestroy(); // TODO: ここにメッセージ ハンドラ コードを追加します。 if (FALSE == ::wglMakeCurrent(NULL, NULL)) { // 必要に応じてエラーハンドリング } if (FALSE == ::wglDeleteContext(m_hRC)) { // 必要に応じてエラーハンドリング } if (m_pDC) delete m_pDC; }
ここでOnPaint()とOnDestroy()の解説をば. MFCアプリケーションはメッセージによるイベント駆動を基本に動作します. つまり、イベントハンドラを書き足してアプリケーションを作るのですが、その際にどのメッセージを有効にするかということを明示しなければなりません. メッセージを有効にする場合、BEGIN_MESSAGE_MAP()~END_MESSAGE_MAP()で囲まれた部分に有効にしたいメッセージとそのメッセージに対するハンドラを登録します. JavaScriptでいうところのaddEventListenerみたいなものですね. 今回の場合はWM_PAINTとWM_DESTROYというメッセージに対するイベントハンドラを登録していて、これらのメッセージを受信したときにハンドラが自動的に実行されるようにしています.
PreSubclassWindow()は自動的に呼び出されるという点ではイベントハンドラに似ていますが、フレームワークによって呼び出される関数です. なので、対応するメッセージなどは存在せず、メッセージマップに登録する必要はありません.
DDX変数のダイアログへの追加
Picture Controlと先ほど定義したクラスを紐づけるため、DDX変数を追加します. より正確に言えば、IDC_GLとバインドさせたCGLScreen型の変数m_GLScreenを、OpenGLDlgクラスに追加します.
Picture Control を追加したときのようにリソースエディタを開き、DDX変数を追加したいコントロールを右クリック->変数の追加からウィザードを開きます.
コントロール変数の追加ウィザードのコントロールタブを次のようにします. なお、その他タブはデフォルトのままで大丈夫です.
ウィザードを完了した後にOpenGLDlg.hを見ると、CGLScreen型の変数m_GLScreenが追加されていることが分かります. ただし、CGLScreen.hをインクルードしていないので、赤い破線が表示されています.
なので、OpenGLDlg.h内でCGLScreen.hをインクルードしましょう.
OpenGLDlg.h
// OpenGLDlg.h // m_GLScreenが追加された #pragma once #include "CGLScreen.h" class COpenGLDlg : public CDialogEx { public: COpenGLDlg(CWnd* pParent = nullptr); // ダイアログ データ #ifdef AFX_DESIGN_TIME enum { IDD = IDD_OPENGL_DIALOG }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV サポート // 実装 protected: HICON m_hIcon; // 生成された、メッセージ割り当て関数 virtual BOOL OnInitDialog(); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: CGLScreen m_GLScreen; };
以上でファイルの編集は完了です.
いざビルド
以上のように無事MFCのコントロールにOpenGLを使って描画することが出来ました.
まとめ
Visual Studio 2017でもMFCとOpenGLを連携して使用することが出来ました. VC++でイベント駆動アプリケーションを作るのは抵抗がありましたが、やってみるとほかの言語とそこまで変わらなくて驚きました.
次回は頂点バッファオブジェクトと頂点配列オブジェクトを使った矩形の描画をしていきます.