前回はC++とWin32SKDによる「ウィンドウ周り」プログラミングでした。今回はユーザーウィンドウで直接GLUTを使う際の中核となるOpenGL.hとDrawFunctions.hについて解説してみようと思います。
先ずはOpenGLを専用の出力ウィンドウを使うコンソールベースではなく、ユーザーウィンドウで直接使う際のコアとなる部分をまとめたOpenGL.hです。
くどいようですが、
これはテスト、学習の為に調べながら書いたものであり、「OpenGLを簡単に使い、基本図形を関数で持っている」というGLUTの利点を生かすためには余りお勧めできません。
しかし、
OpenGLとGLUTの仕組みに興味があるのであれば、知っておいて損はないです。なお、OpenGLやGLUTに関連する部分は赤字にしています。今は詳しく説明しませんが、後でまた解説するので眺めておいてください。
【OpenGL.h】
/************************
OpenGL Includesファイル
************************/
#include <GL\glut.h> //glutライブラリをWindowsで使う場合必要
#include <math.h> //三角関数を使う為
//定数
#define PAI 3.141592653 //三角関数を使う為
//外部変数("g_")
bool g_GLon = FALSE; //OpenGLを使用中か否か
HWND g_hWnd; //OpenGLでグラフィックを表示するウィンドウのハンドル
HDC g_hDC; //対象ウィンドウのデバイスコンテキストハンドル
HGLRC g_hRC; //OpenGL用のレンダリング・コンテキスト
PIXELFORMATDESCRIPTOR g_pdf; //ピクセルフォーマット構造体
int g_pixelformat; //ChoosePixelFormat関数の戻り値:指定されたピクセル形式記述子に最も近いピクセル形式インデックス
int g_DrawMenuNo = IDM_2D01; //描画関数を選択する描画番号(初期値は2DのSample01-解説:これはアプリ用です。)
bool g_Stop = FALSE; //一時停止フラグ(解説:これもアプリ用です。)
///////////////////////////////////////
//GetRC-レンダリングコンテキストの取得
//WM_CREATE時に実行する
//解説:GLUTを「普通に」使用する際にはこの関数は不要です。
///////////////////////////////////////
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 //レイヤーマスク不使用
};
//出力ウィンドウのデバイスコンテキストを取得
g_hDC = GetDC(g_hWnd = hWnd); //対象ウィンドウのハンドルを記録し、デバイスコンテキストを取得
if(!g_hDC) {
MessageBox(g_hWnd, "ウィンドウのデバイスコンテキストハンドルの取得に失敗しました。", "エラー", MB_OK | MB_ICONERROR);
return NULL;
}
//同ウィンドウの最適ピクセルフォーマットインデックスを取得
if((!(g_pixelformat = ChoosePixelFormat(g_hDC, &pfd)))) { //0が返った場合
MessageBox(g_hWnd, "ChoosePixelFormat関数が失敗しました。", "エラー", MB_OK | MB_ICONERROR);
return NULL;
}
//最適ピクセルフォーマットの設定
if(!SetPixelFormat(g_hDC, g_pixelformat, &pfd)) { //FALSEの場合
MessageBox(g_hWnd, "SetPixelFormat関数が失敗しました。", "エラー", MB_OK | MB_ICONERROR);
return NULL;
}
//OpenGLレンダリングコンテキストの作成
if(!(g_hRC = wglCreateContext(g_hDC))) {
MessageBox(g_hWnd, "OpenGLレンダリングコンテキスト(HGLRC)の生成に失敗しました。", "エラー", MB_OK | MB_ICONERROR);
return NULL;
}
//g_hRCをレンダリングコンテキストに指定し、対象ウィンドウのDCに描画(最後にhRCをNULLにして再度wglMakeCurrentを呼び出し、指定を解除することが必要)
wglMakeCurrent(g_hDC, g_hRC);
//OpenGLを使用中にする
g_GLon = TRUE;
return g_hRC;
}
///////////////////////////////////////
//OpenGLを使用中か否かのフラグチェック
///////////////////////////////////////
bool IsGL() {
return g_GLon;
}
//解説:これはアプリ用です。(余り役には立たないようですが...笑)
////////////////////////////////////////////
//SizeChanged-WM_SIZE時のビューポートの取得
//解説:GLUTを「普通に」使用する際にも同じことをします。
////////////////////////////////////////////
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で囲まれる立方体の範囲を平行投影
gluLookAt( 0, 0, 1, //目のポイント (GLdouble) eyex, eyey, eyez,
0, 0, 0, //シーンの中心を示す参照点 (GLdouble) centerx, centery, centerz,
0, 1, 0); //アップベクター (GLdouble) upx, upy, upz,
glMatrixMode(GL_MODELVIEW); //視野・モデリング変換モードへ
*/
//透視投影
glViewport(0, 0, LOWORD(lParam), HIWORD(lParam));
// 透視変換行列の設定
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(90.0, (double)LOWORD(lParam) / (double)HIWORD(lParam), 10.0, 100.0);
gluLookAt( 0, 0, 10, //目のポイント (GLdouble) eyex, eyey, eyez,
0, 0, 0, //シーンの中心を示す参照点 (GLdouble) centerx, centery, centerz,
0, 1, 0); //アップベクター (GLdouble) upx, upy, upz,
// モデルビュー変換行列の設定
glMatrixMode(GL_MODELVIEW);
}
///////////////////////////////////////////
//ReleaseRC-レンダリングコンテキストの解放
//解説:「作ったものは後片付けする」がアンマネージドプログラミングの基本です。
//解説:GLUTを「普通に」使用する際にはGLUTがするのでユーザーはしません。
//WM_DESTROY時に実行する
///////////////////////////////////////////
void ReleaseRC() {
//カレントレンダリングコンテキストを解放
wglMakeCurrent(NULL, NULL);
//レンダリングコンテキストの解放
wglDeleteContext(g_hRC);
//対象ウィンドウのデバイスコンテキストを開放
ReleaseDC(g_hWnd, g_hDC);
//OpenGLの使用を終了
g_GLon = FALSE;
}
///////////////////
//描画メニュー処理
//解説:アプリ用です。
///////////////////
bool DrawMenu(int no) { //引数は新しい描画メニュー番号
ReleaseRC(); //一旦レンダリングコンテキストを開放する。
CheckMenuItem(GetSubMenu(GetMenu(g_hWnd), 1), g_DrawMenuNo, MF_BYCOMMAND | MF_UNCHECKED);
g_DrawMenuNo = no;
CheckMenuItem(GetSubMenu(GetMenu(g_hWnd), 1), g_DrawMenuNo, MF_BYCOMMAND | MF_CHECKED);
GetRC(g_hWnd); //新たにレンダリングコンテキストを取得する。(解説:RCを取り直す意味があったのかどうかわかりませんが...笑)
return TRUE;
}
さあ、これでOpenGLが使えるはずです。それでは肝心の描画部分を見てみましょう。
ここでは、OpenGLの基本的な描画作法を赤字で示します。(全部ではなく、最初に登場した時だけ、ですが。汗;-また、今から見ると画面消去命令が重複していたり、不要なマトリックスモード変換を行っていたり、と赤面ものですが、ドキュメンタリーとしてそのまま載せています。動くには動くので...)
大雑把なポイントを書いておきますので、それを踏まえてみてください。
(1)描画の際に画面を一旦全消去しています。(GL_COLOR_BUFFER_BIT)
(2)画面の消去のみならず、Z軸(深度)の描画用バッファー(例:GL_DEPTH_BUFFER_BIT)も消去しています。
(3)何やらデータ保護のためにスタックを使っています。(glPushMatrix、glPopMatrix。"Push"と"Pop"はFILOのスタックの代名詞)
(4)描画の前に座標処理(回転glRotatefや移動glTranslated)を行っているようです。
(5)描画ははじめ(glBegin)と終わり(glEnd)があり、何を書くか(GL_LINES、GL_TRIANGLES、GL_POLYGON等)も指定されます。
(6)特定の機能をオン(glEnable)、オフ(glDisable)しているようです。
(7)最後に(これがとても大事ですが)、他のプログラムと違い、整数(integer)がほとんど出てこないで、f(float、GLfloat-単精度実数)やd(double、GLdouble-倍精度実数)が頻出します。
私もサンプルを色々と弄って実行させながら、そんなことを考え、考えしていました。
【DrawFunctions.h】
////////////////////////////////////
// OpenGLによる描画関数
//注:「表示」の「2D/3D」メニューに
// 対応するDrawNoを設定し、それに
// 対応するGLLoop関数のスイッチ文
// から呼び出します。
////////////////////////////////////
///////////////////
//描画関数2D
//解説:二次元グラフィックスです。
///////////////////
void Draw2D01() {
static float theta = 0.0f; //シータ値(解説:回転させる為の値です。)
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); //解説:色を指定してウィンドウを塗りつぶす(↓と重複しています。)
glClear(GL_COLOR_BUFFER_BIT); //ウィンドウを塗りつぶす(解説:色のみ)
glPushMatrix();
glRotatef(theta, 0.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.7f, 0.0); //0.8 x (1.73 / 2)
glColor3f(0.0f, 1.0f, 0.0f); glVertex3f(0.7f, -0.4f, 0.0);
glColor3f(0.0f, 0.0f, 1.0f); glVertex3f(-0.7f, -0.4f, 0.0);
glEnd();
glPopMatrix();
//ダブルバッファリング
SwapBuffers(g_hDC); //解説:GLUTには専用関数があります。
theta += 1.0f;
Sleep(1);
}
void Draw2D02() {
static float theta = 0.0f; //シータ値
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT); //ウィンドウを塗りつぶす
glRotatef(theta, 0.0f, 0.0f, 1.0f);
glPushMatrix();
glBegin(GL_POLYGON);
glColor3d(1.0, 0.0, 0.0); glVertex3d(-0.5, -0.5, 0.0);
glColor3d(0.0, 1.0, 0.0); glVertex3d(0.5, -0.5, 0.0);
glColor3d(0.0, 0.0, 1.0); glVertex3d(0.5, 0.5, 0.0);
glColor3d(1.0, 1.0, 0.0); glVertex3d(-0.5, 0.5, 0.0);
glEnd();
glPopMatrix();
//ダブルバッファリング
SwapBuffers(g_hDC);
theta += 0.1f;
Sleep(1);
}
void Draw2D03() {
static float theta = 0.0f; //シータ値
double offset = 0.5;
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT); //ウィンドウを塗りつぶす
glRotatef(theta, 0.0f, 0.0f, 1.0f);
double radius = 0.5;
glPushMatrix(); //左側に線描画の円
glTranslated(offset, 0, 0);
glColor3d(1, 1, 0);
for(double rad = -PAI; rad <= PAI; rad += 0.1) {
glBegin(GL_LINES);
glVertex3d(radius * cos(rad), radius * sin(rad), 0.0);
glVertex3d(radius * cos(rad + 0.1), radius * sin(rad + 0.1), 0.0);
glEnd();
}
glPopMatrix(); //右側に塗潰し描画の円
glPushMatrix();
glTranslated(-offset, 0, 0);
for(radius = 0.5; radius > 0; radius -= 0.001) { //半径
glColor3d(1 - radius, radius, radius / 2);
for(double rad = -PAI; rad <= PAI; rad += 0.1) {
glBegin(GL_LINES);
glVertex3d(radius * cos(rad), radius * sin(rad), 0.0);
glVertex3d(radius * cos(rad + 0.1), radius * sin(rad + 0.1), 0.0);
glEnd();
}
}
glPopMatrix();
theta += 0.1f;
//ダブルバッファリング
SwapBuffers(g_hDC);
}
void Draw2D04() { //解説:メニューアイテムが"Grayed"になっている未実装のメニューです。
}
void Draw2D05() {
}
///////////////////
//描画関数3D
//解説:三次元グラフィックスです。
///////////////////
void Draw3D01() {
//中心を(0, 0, 0)とする一辺1.0の直方体の頂点
GLdouble vertex[][3] = {
{-0.4, -0.4, -0.4}, /* A */
{0.4, -0.4, -0.4}, /* B */
{0.4, 0.4, -0.4}, /* C */
{-0.4, 0.4, -0.4}, /* D */
{-0.4, -0.4, 0.4}, /* E */
{0.4, -0.4, 0.4}, /* F */
{0.4, 0.4, 0.4}, /* G */
{-0.4, 0.4, 0.4} /* H */
};
//上記頂点の連結線
int edge[][2] = {
{0, 1}, /* ア (A-B) */
{1, 2}, /* イ (B-C) */
{2, 3}, /* ウ (C-D) */
{3, 0}, /* エ (D-A) */
{4, 5}, /* オ (E-F) */
{5, 6}, /* カ (F-G) */
{6, 7}, /* キ (G-H) */
{7, 4}, /* ク (H-E) */
{0, 4}, /* ケ (A-E) */
{1, 5}, /* コ (B-F) */
{2, 6}, /* サ (C-G) */
{3, 7} /* シ (D-H) */
};
static float theta = 0.0f; //シータ値
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
/* 図形の回転 */
glRotated(theta, 0.0, 1.0, 1.0);
theta += 0.01;
/* 図形の描画 */
glColor3d(1.0, 1.0, 0.0);
glBegin(GL_LINES);
for (int i = 0; i < 12; i++) {
glVertex3dv(vertex[edge[i][0]]);
glVertex3dv(vertex[edge[i][1]]);
}
glEnd();
glFlush();
//ダブルバッファリング
SwapBuffers(g_hDC);
}
void Draw3D02() {
glEnable(GL_DEPTH_TEST); //陰面消去を行う為の深度処理有効化
//画面クリア
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //解説:陰面消去用の「深度」バッファーをクリアしています。
static float theta = 0.0f; //シータ値
//ポリゴン描画
glPushMatrix();
glRotated(theta, 0.0, 1.0, 0.0);
theta += 0.05;
glBegin(GL_TRIANGLES);
//奥の面
glColor3f(1.0f, 0.0f, 0.0f);
glVertex3f(-0.500f, -0.272f, -0.289f); glVertex3f(0.500f, -0.272f, -0.289f); glVertex3f(0.000f, 0.544f, 0.000f);
//右手前面
glColor3f(0.0f, 1.0f, 0.0f);
glVertex3f(0.500f, -0.272f, -0.289f); glVertex3f(0.000f, -0.272f, 0.577f); glVertex3f(0.000f, 0.544f, 0.000f);
//左手前面
glColor3f(0.0f, 0.0f, 1.0f);
glVertex3f(0.000f, -0.272f, 0.577f); glVertex3f(-0.500f, -0.272f, -0.289f); glVertex3f(0.000f, 0.544f, 0.000f);
//底面
glColor3f(1.0f, 1.0f, 1.0f);
glVertex3f(0.500f, -0.272f, -0.289f); glVertex3f(0.500f, -0.272f, -0.289f); glVertex3f(0.000f, -0.272f, 0.577f); //解説:2024年6月4日修正
glEnd();
glPopMatrix();
//描画処理を実行
glFlush();
//ダブルバッファリング
SwapBuffers(g_hDC);
glDisable(GL_DEPTH_TEST); //陰面消去を行う為の深度処理有効化
}
void Draw3D03() {
glEnable(GL_DEPTH_TEST); //陰面消去を行う為の深度処理有効化
static float theta = 0.0f; //シータ値
//画面クリア
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//モデルビュー変換行列の初期化
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glPushMatrix();
glRotated(theta, 0.0, 1.0, 0.0);
glRotated(theta, 1.0, 0.0, 0.0);
//描画処理
/* 図形の回転 */
glBegin(GL_QUADS);
//手前正面
glColor3d(0.0, 0.0, 1.0);
glVertex3d(-0.5, -0.5, 0.5);
glVertex3d(0.5, -0.5, 0.5);
glVertex3d(0.5, 0.5, 0.5);
glVertex3d(-0.5, 0.5, 0.5);
//右側面
glColor3d(0.0, 1.0, 1.0);
glVertex3d(0.5, -0.5, -0.5);
glVertex3d(0.5,- 0.5, 0.5);
glVertex3d(0.5, 0.5, 0.5);
glVertex3d(0.5, 0.5, -0.5);
//奥正面
glColor3d(1.0, 0.0, 0.0);
glVertex3d(-0.5, -0.5, -0.5);
glVertex3d(0.5, -0.5, -0.5);
glVertex3d(0.5, 0.5, -0.5);
glVertex3d(-0.5, 0.5, -0.5);
//左側面
glColor3d(0.0, 1.0, 0.0);
glVertex3d(-0.5, -0.5, -0.5);
glVertex3d(-0.5, 0.5, -0.5);
glVertex3d(-0.5, 0.5, 0.5);
glVertex3d(-0.5, -0.5, 0.5);
//天面
glColor3d(1.0, 0.0, 1.0);
glVertex3d(-0.5, 0.5, -0.5);
glVertex3d(-0.5, 0.5, 0.5);
glVertex3d(0.5, 0.5, 0.5);
glVertex3d(0.5, 0.5, -0.5);
//底面
glColor3d(1.0, 1.0, 0.0);
glVertex3d(-0.5, -0.5, -0.5);
glVertex3d(-0.5, -0.5, 0.5);
glVertex3d(0.5, -0.5, 0.5);
glVertex3d(0.5, -0.5, -0.5);
glEnd();
theta += 0.1;
glPopMatrix();
//命令処理
glFlush();
//ダブルバッファリング
SwapBuffers(g_hDC);
glDisable(GL_DEPTH_TEST); //陰面消去を行う為の深度処理有効化
}
void Draw3D04() {
}
void Draw3D05() {
}
/////////////////////////
//描画関数を選択する変数
//解説:アプリ用です。
/////////////////////////
void DrawObjects(int num) {
switch(num) {
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;
}
}
これでGLSamplerのお話はいったん終了します。次回からは(細部は膨大なので)3Dグラフィックと関連事項の概念について私流にかみ砕いて解説したいと思います。
ps. 次回以降と関連するクイズです。GLSamplerは起動時に2Dの回転する小さな三角形が現れます。メニューでそれ(または他の図形-3D図形を含む)を選ぶと大きく表示されます。では、これらが表示されている時にウインドウ枠を弄ってサイズを変えてみましょう。どうなるでしょう?またどうしてでしょうか?