このシリーズ物の【無駄話】は、お気楽にOpenGLの簡単なクラス化を夢見てこう始まって、ここまで来ました。より具体的に書くと、
(1)Dev-C++のサンプルを解析して、WindowsでサポートするOpenGL 1.1(ヘッダーファイルはBCC102の "...BCC102\include\windows\sdkGL\” にあるgl.hとglu.h)を使った2Dのグラフィックサンプルを使えるように、(Win32 APIのGetDC、ReleaseDCのように)自作GetRC(Rendering Context)とReleaseRC関数、SizeChanged関数とGLLoop(OpenGL用のループ)関数等を作り、2Dグラフィックスの描画は難なく対応できました。(末尾【ご参考】参照)
(2)CGやるならやりたいのは3Dグラフィックスでしょうが、これはOpenGL(gl.hとglu.h)だけでは大変なので、既に奇特な方が作成されたglutを使おうと思いました。しかし、オリジナルのglutは旧いし、20世紀末からバージョンアップされていないので、その正統後継と言われるfreeglutを導入することになりました。(その間のすったもんだについてはこれ参照)
(3)glut(及びfreeglut)というのは、「クラス化によるオブジェクト指向言語となるC++」以前の「手続志向言語としてのC」で書かれたようなライブラリーであり、実際"int main(int argc, char** argv)"をエントリーポイントとし、その後初期化、表示用ウィンドウ作成、コールバック関数の設定(注)を行い、表示用ウィンドウのメインループ(メッセージ処理をしない時のglutIdleFunc関数を含め)に入ります。このように、「(free)glutので作るウィンドウを使用することが前提のライブラリーであり、ユーザーウィンドウにグラフィック表示を行うライブラリーではない」所に特徴があり、このお作法から外れた処理の仕方をする場合、相当に腰を据えてかからないとうまく動作しないことを思い知らされました。
注:(free)glutではmain関数内で以下のようなシーケンスで表示用ウィンドウを作り、描画準備を行います。
// 初期化設定
glutInit(&argc, argv); // glutを初期化する
glutInitWindowSize(800, 600); // 画面サイズを指定する
glutInitWindowPosition(100, 100); // 画面の初期位置を指定する
glutInitDisplayMode(GLUT_RGBA); // 表示モード設定
glutCreateWindow("OpenGL Window"); // ウィンドウの名前
// コールバック関数の設定
glutDisplayFunc(display); // 描画処理が必要なときに呼ばれる
glutReshapeFunc(reshape); // reshapeが必要なときに呼ばれる
glutIdleFunc(idle); // 処理メッセエージが無いときに呼ばれる
glutMainLoop(); // 毎フレームのLoop
(4)具体的には、OpenGLで使える関数のうち"gl~"関数(gl.h)、"glu~"関数(glu.h)は(換言すれば2D処理は)問題なく使えるのですが、"glut~"関数(glut.hまたはfreeglut_std.h-注)は失敗(プログラムの異常終了-「落ちる」)します。これに対して、try~catchを使ってglGetError()関数で問題特定しようとしたりしましたが、結局原因は特定できませんでした。(OpenGL 4.0から利用できるようになったエラーメッセージコールバック関数<void GLAPIENTRY MessageCallback>は未定義で使えませんでした。)
注:特に以下の組み込み3DCG描画関数
glutSolidTeapot(GLdouble size); //glutWireTeapot(GLdouble size); 以下相対のワイアーフレーム処理も同じ
glutSolidCube(GLdouble size);
glutSolidSphere(GLdouble radius,GLint slices,GLint stacks);
glutSolidTorus(GLdouble innerradius, GLdouble outerradius, GLint nsides, GLint rings);
glutSolidOctahedron(void);
glutSolidTetrahedron(void);
glutSolidDodecahedron(void);
glutSolidCone(GLdouble radius, GLdouble height, GLint slices, GLint stacks);
(5)この為、唯一ユーザーウィンドウと末尾の関数で処理できる3D CGは、ユーザーが定義する図形を、
glBegin(GLenum mode);
glVertex3dv(GLdouble x, GLdouble y, GLdouble z);
等を使って描画することに限定されます。
まぁ、3D CGドシロートの私ではここまでが限界かもしれません。「3D CGが好きなので」という方はこんな面倒なことをしないで、freeglutをそのまま使って、もっと簡単に楽しめますよ。
根性があれば、「クソッ、このティーポットの頂点情報を入力してやるっ!」となるのですが、何せヘタレの老人なので、そんなパワーは全くありません。実際、ここまで来るだけでヘトヘトで、そんなにCGが好きなわけでもないので、テーマを変えたくなって仕方がありませんでした。
そろそろ、ここいら辺でOpenGLから開放してもらおうかな?
【ご参考-C++でWindows SKDベースで書いています】
/************************
OpenGL Includesファイル
************************/
#include <GL\gl.h> //OpenGLをWindowsで使う場合必須
#include <GL\glu.h> //OpenGLユーティリティ ライブラリをWindowsで使う場合必要
/*
glutを入手し、本プログラムでコンパイルしたら、glut.hとstdlib.hのexit関数が二重定義になっているというエラーが出た。
解決法はstdlib.h(windows.hに入っている)を先に取り込め、とあったが解決にはならなかった。
結局、glutのサイトからフルバージョンのファイルをダウンロードし、その中のglut.h(エラーが起こったものとは異なる内容)を使って解決した。
*/
//クラス化も考えて"m_"にしていますが、外部変数です。
bool m_GLon = FALSE; //OpenGLを使用中か否か
HWND m_hWnd; //OpenGLでグラフィックを表示するウィンドウのハンドル
HDC m_hDC; //対象ウィンドウのデバイスコンテキストハンドル
HGLRC m_hRC; //OpenGL用のレン. ダリング・コンテキスト
PIXELFORMATDESCRIPTOR m_pdf; //ピクセルフォーマット構造体
int m_pixelformat; //ChoosePixelFormat関数の戻り値:指定されたピクセル形式記述子に最も近いピクセル形式インデックス
int m_DrawMenuNo = IDM_2D01; //描画関数を選択する描画番号(初期値は2DのSample01)
#include "DrawFunctions.h" //OpenGLによる描画関数を記述したファイル
///////////////////////////////////////
//GetRC-レンダリングコンテキストの取得
//WM_CREATE時に実行する
///////////////////////////////////////
HGLRC GetRC(HWND hWnd) { //これはコンストラクターに入れる内容かも
static PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), //この構造体のサイズ
1, //OpenGLバージョン(Windowsが利用できるのは1.1のみ)
PFD_DRAW_TO_WINDOW | //ウィンドウ描画
PFD_SUPPORT_OPENGL | //OpenGL使用
PFD_DOUBLEBUFFER, //ダブルバッファ使用
PFD_TYPE_RGBA, //RGBAを使用
24, //24bit色を使用
0, 0, 0, 0, 0, 0, //カラー(RGB)ビット無視
0, 0, //アルファ(A)バッファ無し
0, //アキュムレーションバッファ不使用
0, 0, 0, 0, //アキュムレーションbit無視
32, //Zバッファー
0, //ステンシルバッファ無し
0, //補助バッファ不使用
PFD_MAIN_PLANE, //メインレイヤー(レイヤー種類: PFD_MAIN_PLANE、 PFD_OVERLAY_PLANE、 PFD_UNDERLAY_PLANE)
0, //予約
0, 0, 0 //レイヤーマスク不使用
};
//出力ウィンドウのデバイスコンテキストを取得
m_hDC = GetDC(m_hWnd = hWnd); //対象ウィンドウのハンドルを記録し、デバイスコンテキストを取得
if(!m_hDC) {
MessageBox(m_hWnd, "ウィンドウのデバイスコンテキストハンドルの取得に失敗しました。", "エラー", MB_OK | MB_ICONERROR);
return NULL;
}
//同ウィンドウの最適ピクセルフォーマットインデックスを取得
if((!(m_pixelformat = ChoosePixelFormat(m_hDC, &pfd)))) { //0が返った場合
MessageBox(m_hWnd, "ChoosePixelFormat関数が失敗しました。", "エラー", MB_OK | MB_ICONERROR);
return NULL;
}
//最適ピクセルフォーマットの設定
if(!SetPixelFormat(m_hDC, m_pixelformat, &pfd)) { //FALSEの場合
MessageBox(m_hWnd, "SetPixelFormat関数が失敗しました。", "エラー", MB_OK | MB_ICONERROR);
return NULL;
}
//OpenGLレンダリングコンテキストの作成
if(!(m_hRC = wglCreateContext(m_hDC))) {
MessageBox(m_hWnd, "OpenGLレンダリングコンテキスト(HGLRC)の生成に失敗しました。", "エラー", MB_OK | MB_ICONERROR);
return NULL;
}
//m_hRCをレンダリングコンテキストに指定し、対象ウィンドウのDCに描画(最後にhRCをNULLにして再度wglMakeCurrentを呼び出し、指定を解除することが必要)
wglMakeCurrent(m_hDC, m_hRC);
//OpenGLを使用中にする
m_GLon = TRUE;
return m_hRC;
}
///////////////////////////////////////
//OpenGLを使用中か否かのフラグチェック
///////////////////////////////////////
bool IsGL() {
return m_GLon;
}
////////////////////////////////////////////
//SizeChanged-WM_SIZE時のビューポートの取得
////////////////////////////////////////////
void SizeChanged(LPARAM lParam) {
glViewport(0, 0, LOWORD(lParam), HIWORD(lParam)); //ウインドウ全体に表示
glMatrixMode(GL_PROJECTION); //投影変換モードへ
glLoadIdentity(); //投影変換の変換行列を単位行列で初期化
glOrtho(-1.0, 1.0, -1.0, 1.0, 1.0, -1.0); //各軸-1.0~1.0で囲まれる立方体の範囲を並行投影
glMatrixMode(GL_MODELVIEW); //視野変換・モデリング変換モードへ
glLoadIdentity(); //視野変換・モデリング変換の変換行列を単位行列で初期化
}
//////////////////////////////
//対象ウィンドウGL描画用ループ
//////////////////////////////
void GLLoop(MSG msg) {
//アイドル時に描画させるメッセージループ
while(msg.message != WM_QUIT) {
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
//WM_QUITを明示的に処理する
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//処理メッセージが無ければ描画する
else {
switch(m_DrawMenuNo) { //描画関数を選択する変数
case IDM_2D01:
Draw2D01(); //2D-Sample01
break;
case IDM_2D02:
Draw2D02(); //2D-Sample02
break;
case IDM_2D03:
Draw2D03(); //2D-Sample03
break;
case IDM_2D04:
Draw2D04(); //2D-Sample04
break;
case IDM_2D05:
Draw2D05(); //2D-Sample05
break;
case IDM_3D01:
Draw3D01(); //3D-Sample01
break;
case IDM_3D02:
Draw3D02(); //3D-Sample02
break;
case IDM_3D03:
Draw3D03(); //3D-Sample03
break;
case IDM_3D04:
Draw3D04(); //3D-Sample04
break;
case IDM_3D05:
Draw3D05(); //3D-Sample05
break;
default:
break;
}
}
}
}
///////////////////////////////////////////
//ReleaseRC-レンダリングコンテキストの解放
//WM_DESTROY時に実行する
///////////////////////////////////////////
void ReleaseRC() { //これはデストラクターに入れる内容
//カレントレンダリングコンテキストを解放
wglMakeCurrent(NULL, NULL);
//レンダリングコンテキストの解放
wglDeleteContext(m_hRC);
//対象ウィンドウのデバイスコンテキストを開放
ReleaseDC(m_hWnd, m_hDC);
//OpenGLの使用を終了
m_GLon = FALSE;
}
///////////////////
//描画メニュー処理
///////////////////
bool DrawMenu(int no) { //引数は新しい描画メニュー番号
ReleaseRC(); //一旦レンダリングコンテキストを開放する。
CheckMenuItem(GetSubMenu(GetMenu(m_hWnd), 1), m_DrawMenuNo, MF_BYCOMMAND | MF_UNCHECKED);
m_DrawMenuNo = no;
CheckMenuItem(GetSubMenu(GetMenu(m_hWnd), 1), m_DrawMenuNo, MF_BYCOMMAND | MF_CHECKED);
GetRC(m_hWnd); //新たにレンダリングコンテキストを取得する。
return TRUE;
}