glutCreateWindow で作ったウィンドウに OpenGL 描画していると視点操作や描画するデータをいくつか切り替えて使いたくなってくると思います。ダイアログアプリケーション作成すると視点座標をエディットボックスで入力したり、描画するデータをコンボボックスで切り替えたりが簡単に行えるようになります。
ここではダイアログアプリケーション上で OpenGL 描画をする方法を簡単に説明します。
1. メニューの[ファイル(F)]-[新規作成(N)]-[プロジェクト(P)…]を選択し、『新しいプロジェクト』ダイアログを開きます。
2. フィルタを "C++", "Windows", "デスクトップ" に変更します。
3. テンプレートリストから "MFCアプリ" を選択します。
4. 「次へ(N)」ボタンを押下すると、『新しいプロジェクトを構成します』ダイアログが開きます。
5. 「プロジェクト名(J)」に"MFCApplication1"を入力します。
6. 「場所(L)」にプロジェクトを保存するフォルダを入力します。
7. 「ソリューションとプロジェクトを同じディレクトリに配置する(D)」のチェックボックスをオンにします。
8. 「作成(C)」ボタンを押下します。
ここではアプリケーションの種類を選択します。アプリケーションの種類の特徴は下記のようになります。
シングルドキュメント
単一のドキュメントしか持たないアプリケーション
例) メモ帳など
マルチドキュメント
複数のドキュメントを持つことができるアプリケーション
例) Visual Studio, SOLIDWORKS, Excessなど
ダイアログベース
ダイアログ画面のみのアプリケーション
例) 電卓など
9. 「アプリケーションの種類(T)」から"ダイアログベース"を選択します。
10. 「完了」ボタンを押下します。
各ページで様々な設定がありますが、とりあえず変更する必要はないでしょう。
4. 『ツールボックス』を開きます。
5. "ダイアログエディター"を展開し、"Picture Control" をドラッグします。
6. 5を"IDD_MFCAPPLICATION1_DIALOG"にドロップするとダイアログにピクチャーコントロールが追加されます。
7. 6で追加したピクチャーコントロールの位置とサイズを変更します。
8. ピクチャコントロールを選択後、右クリックでポップアップメニューを開き、[プロパティ(R)]を実行します。
※ピクチャコントロールの選択は黒い枠線を選択して下さい。
9. ピクチャコントロールの 「ID」コンボボックスに"IDC_PICT"と入力します。
10.ピクチャコントロールの 「サイズ調整の種類」コンボボックスを"両方"に変更します。
11. 「OK」ボタンと「キャンセル」ボタンを選択後、右クリックでポップアップメニューを開き、[プロパティ(R)]を実行します。
12. ボタンの 「移動の種類」コンボボックスを"両方"に変更します。
13. メニューの[ファイル(F)]-[すべて保存(L)]を選択します。
正しく実行できていれば【resource.h】,【MFCApplication1.rc】のように変更されています。
本来なら機能を1つずつ追加していくのですが、クラスウィザードとエディタを何度も行き来して説明が大変なので、ここではクラスウィザードでの設定を先に一気に行います。
1. ”IDD_MFCAPPLICATION1_DIALOG"のダイアログを開きます。
2. ダイアログを選択後、右クリックでポップアップメニューを開き、[クラスウィザード(Z)…]を実行します。
3. 「メンバー変数」タブを選択します。
4. 「カスタムの追加(U)…」ボタンを押下します。
5. 『メンバー変数の追加』ダイアログが開きます。
6. 「変数の型(T)」コンボボックスに"HGLRC"を入力します。
※ HGLRC は OpenGL レンダリングコンテキストのハンドルです。
7. 「変数名(N)」エディットボックスに"m_hGLRC"を入力します。
8. 「アクセス」のラジオボタンから"プライベート(V)"を選択します。
9. 「OK」ボタンを押下します。
10. 「メンバー変数(V)」リストボックスから"IDC_PICT"を選択します。
11. 「変数の追加(A)…」ボタンを押下します。
12. 『メンバー変数の追加ウィザード』ダイアログが開きます。
13. 「変数名(N)」に"m_ctrlPict"を入力します。
14. 「アクセス(A)」コンボボックスから"private"を選択します。
15. 「完了」ボタンを押下します。
続けてクラスウィザードを使い、メソッドを追加します。
1. 「メソッド」タブを選択します。
2. 「メソッドの追加(A)…」ボタンを押下します。
3. 『関数の追加』ダイアログが開きます。
4. 「関数名(U)」エディットボックスに"InitGL"を入力します。
5. 「戻り値の型(Y)」コンボボックスから"bool"を選択します。
6. 「アクセス(A)」コンボボックスから"private"を選択します。
7.「OK」ボタンを押下します。
8.「メソッドの追加(A)…」ボタンを押下し、『関数の追加』ダイアログを開きます。
9. 「関数名(U)」エディットボックスに"DrawGL"を入力します。
10. 「戻り値の型(Y)」コンボボックスから"void"を選択します。
11. 「アクセス(A)」コンボボックスから"private"を選択します。
12.「OK」ボタンを押下します。
13.「メソッドの追加(A)…」ボタンを押下し、『メンバー関数追加ウィザード』ダイアログを開きます。
14. 「関数名(U)」エディットボックスに"SetDCPixelFormat"を入力します。
15. 「戻り値の型(Y)」コンボボックスから"bool"を選択します。
16. 「アクセス(A)」コンボボックスから"private"を選択します。
17. 「+」ボタンを押下します。
18.「パラメータ(P)」リストボックスに"HDC hdc"を入力します。
19.「OK」ボタンを押下します。
20.「メソッドの追加(A)…」ボタンを押下し、『メンバー関数追加ウィザード』ダイアログを開きます。
21. 「関数名(U)」エディットボックスに"SetDrawingArea"を入力します。
22. 「戻り値の型(Y)」コンボボックスから"void"を選択します。
23. 「アクセス(A)」コンボボックスから"private"を選択します。
24.「OK」ボタンを押下します。
正しく実行できていれば【MFCApplication1Dlg.h】,【MFCApplication1Dlg.cpp】のように変更されています。
gl*, glu*, glut* などの OpenGL 関連の API を呼び出すためのヘッダーファイルを追加しておきます。
// MFCApplication1Dlg.cpp : 実装ファイル
//
#include "pch.h"
#include "framework.h"
#include "MFCApplication1.h"
#include "MFCApplication1Dlg.h"
#include "afxdialogex.h"
#include <GL/glut.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
追加したメンバー変数 m_hGLRC の初期化処理を追加します。
CMFCApplication1Dlg::CMFCApplication1Dlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_MFCAPPLICATION1_DIALOG, pParent)
, m_hGLRC(NULL)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
クラスウィザードで追加したメソッドとハンドラーの関数を記述します。
ダイアログが初期化されるときの処理をここに記述します。この関数が呼び出されるより先に最初の OnSize が呼び出されてしまうので、OpenGL の表示領域に関する処理をここで行っています。
BOOL CMFCApplication1Dlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 長いので途中省略
// TODO: 初期化をここに追加します。
// OpenGL初期化
InitGL();
// OpenGLの描画領域を設定する
SetDrawingArea();
return TRUE; // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
}
[解説]
BOOL CDialog::OnInitDialog()
ダイアログが初期化されるときに呼び出される関数です。CDialogEx::OnInitDialog(); でコントロールが作成されるのでこの関数の後で各コントロールに初期化設定を行います。
ダイアログの描画が必要になったときに呼び出される関数です。glutDisplayFunc で設定した関数と同じ役割を持ちます。
void CMFCApplication1Dlg::OnPaint()
{
if (IsIconic())
{
// 変更なしなので省略
}
else
{
CDialogEx::OnPaint();
// OpenGL描画
DrawGL();
}
}
[解説]
CWnd::OnPaint()
フレームワークは、Windows またはアプリケーションがアプリケーションのウィンドウの一部の再描画要求を行うときにこのメンバー関数を呼び出します。
この関数で OpenGL 関連の初期化を行います。
bool CMFCApplication1Dlg::InitGL()
{
// TODO: ここに実装コードを追加します.
// ピクチャコントロールが生成済みかどうか
if (!m_ctrlPict.GetSafeHwnd()) {
return false;
}
// デバイスコンテキスト取得
CClientDC dc(&m_ctrlPict);
// ピクセルフォーマット設定
if (!SetDCPixelFormat(dc.m_hDC)) {
return false;
}
// レンダリングコンテキスト生成
m_hGLRC = wglCreateContext(dc.m_hDC);
if (!m_hGLRC) {
return false;
}
// カレントコンテキスト設定
if (!wglMakeCurrent(dc.m_hDC, m_hGLRC)) {
return false;
}
glClearColor(1.0, 1.0, 1.0, 1.0);
return true;
}
[解説]
HWND CWnd::GetSafeHwnd() const
ウィンドウハンドルを取得します。ウィンドウがアタッチされていないときは NULL を返します。
CClientDC::CClientDC(CWnd* pWnd)
クライアント領域に関連付けられているデバイスコンテキストを取得します。
HGLRC wglCreateContext(HDC hdc)
参照デバイス上に描画するのに適する新しい OpenGL レンダリングコンテキストを作成します。
BOOL wglMakeCurrent(HDC hdc, HGLRC hGLRC)
作成したレンダリングコンテキストをカレントコンテキストに設定します。
この関数で OpenGL 描画を行います。とりあえず X 軸, Y 軸, Z 軸方向に長さ 200 の直線を赤色, 緑色, 青色で描画しておきます。
void CMFCApplication1Dlg::DrawGL()
{
// TODO: ここに実装コードを追加します.
glClear(GL_COLOR_BUFFER_BIT);
// X, Y, Z軸線描画
glBegin(GL_LINES);
glColor3d(1.0, 0.0, 0.0);
glVertex3d(-100.0, 0.0, 0.0);
glVertex3d(100.0, 0.0, 0.0);
glEnd();
glBegin(GL_LINES);
glColor3d(0.0, 1.0, 0.0);
glVertex3d(0.0, -100.0, 0.0);
glVertex3d(0.0, 100.0, 0.0);
glEnd();
glBegin(GL_LINES);
glColor3d(0.0, 0.0, 1.0);
glVertex3d(0.0, 0.0, -100.0);
glVertex3d(0.0, 0.0, 100.0);
glEnd();
glFlush();
if (m_ctrlPict.GetSafeHwnd()) {
CClientDC dc(&m_ctrlPict);
SwapBuffers(dc.m_hDC);
}
}
[解説]
BOOL SwapBuffers(HDC hDC)
フロントバッファとバックバッファを交換します。このタイミングでピクチャコントロールが再描画されます。
この関数では OpenGL 描画を行うデバイス(ここではピクチャコントロール)のピクセルフォーマットを設定します。
bool CMFCApplication1Dlg::SetDCPixelFormat(HDC hdc)
{
// TODO: ここに実装コードを追加します.
// デバイスコンテキストに適したピクセルフォーマットを設定する
static PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // Size of this structure
1, // Version number
PFD_DRAW_TO_WINDOW | // Flags
PFD_SUPPORT_OPENGL |
PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA, // RGBA pixel values
24, // 24-bit color
0, 0, 0, 0, 0, 0, // Don't care about these
0, 0, // No alpha buffer
0, 0, 0, 0, 0, // No accumulation buffer
32, // 32-bit depth buffer
0, // No stencil buffer
0, // No auxiliary buffers
PFD_MAIN_PLANE, // Layer type
0, // Reserved (must be 0
0, 0, 0 // No layer masks
};
const int nPixelFormat = ChoosePixelFormat(hdc, &pfd);
if (!SetPixelFormat(hdc, nPixelFormat, &pfd)) {
return false;
}
return (DescribePixelFormat(hdc, nPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd) != 0);
}
[解説]
int ChoosePixelFormat(HDC hdc, CONST PIXELFORMATDESCRIPTOR *ppfd)
デバイスコンテキストでサポートされる指定したピクセルフォーマットに最も近くなる設定を取得します。
BOOL SetPixelFormat(HDC hdc, int format, CONST PIXELFORMATDESCRIPTOR * ppfd)
デバイスコンテキストにピクセルフォーマットを設定します。
int DescribePixelFormat(HDC hdc,
int iPixelFormat,
UINT nBytes,
LPPIXELFORMATDESCRIPTOR ppfd)
使用するピクセルフォーマットの情報を取得します。
この関数ではピクチャコントロールの表示領域を取得して正射投影行列に設定することで描画領域を設定します。
void CMFCApplication1Dlg::SetDrawingArea()
{
// TODO: ここに実装コードを追加します.
if (!m_ctrlPict.GetSafeHwnd()) {
return;
}
// ピクチャコントロールの表示領域を取得する
CRect rect;
m_ctrlPict.GetClientRect(&rect);
const int w = rect.Width();
const int h = rect.Height();
// ピクチャコントロールの表示領域に一致させる
glViewport(0, 0, w, h);
glLoadIdentity();
glOrtho(-w / 2.0, w / 2.0, -h / 2.0, h / 2.0, -1.0, 1.0);
}
[解説]
CRect クラス
長方形領域を表す左端/上端/右端/下端の座標をメンバー変数にもつ、領域を操作を行うためのクラスです。
void CWnd::GetClientRect(LPRECT lpRect) const
クライアント領域を取得する関数です。
この関数でダイアログを破棄するのに必要な処理を行います。
void CMFCApplication1Dlg::OnDestroy()
{
CDialogEx::OnDestroy();
// TODO: ここにメッセージ ハンドラー コードを追加します。
wglMakeCurrent(NULL, NULL); // free current context
wglDeleteContext(m_hGLRC); // Delete rendering context
}
[解説]
CWnd::OnDestroy()
ウィンドウ(ここではダイアログ)が破棄されるときに呼び指されます。
BOOL wglMakeCurrent(HDC hdc, HGLRC hGLRC)
引数に NULL を渡した場合はレンダリングコンテキストに指定しているデバイスコンテキストを解放します。
BOOL wglDeleteContext(HGLRC hGLRC)
指定したレンダリングコンテキストを削除します。
ダイアログのサイズが変更されたときに呼び出される関数です。glutReshapeFunc で設定した関数と同じ役割を持ちます。ダイアログが初期化(OnInitDialog)される前に呼び出されるので、ピクチャコントロールが作成前にはピクチャコントロールの表示領域処理を行わないようにしています。
void CMFCApplication1Dlg::OnSize(UINT nType, int cx, int cy)
{
CDialogEx::OnSize(nType, cx, cy);
// TODO: ここにメッセージ ハンドラー コードを追加します。
SetDrawingArea();
}
[解説]
void CWnd::OnSize(UINT nType, int cx, int cy)
ウィンドウ(ここではダイアログ)のサイズが変更されたときに呼び出されます。引数の nType にはサイズ変更の種類、cx, cy にはクライアント領域の新しい幅と高さが渡されます。
このプログラムでは(かなり手を抜いて) X, Y, Z 軸方向に長さ200の直線を描画していますが、視点や注視点を設定していないので2次元図形を描画しているように見えてます。当然3次元図形を描画することもできますので「3次元図形を描く」を参考にして色々試してみてください。また、ダイアログにテキストボックスやスライダコントロールを追加して視点座標, 注視点座標, 視野角などの情報を設定できるようにすると、様々な視点で図形形状を確認することができるようになります。