前回OpenGLの3Dグラフィックスについて簡単に説明しましたが、今回からはGLUTを「仕様通りの使い方」に沿って使い、GLSamplerでつかった描画オブジェクト(プリミティブ)のサンプルも使って実際に実験しながら確かめてゆきたいと思います。
先ずは毎回異なる仕様のコードを使うのではなく、今後3Dグラフィックスを作成するベースとなるGLUTの使い方を(私の得意技である)スケルトンで示し、3次元空間にx,y,zの各軸に沿った矢印を描いてみます(今回のお題)。なお、3Dグラフィックスに関わるコードの詳しい説明はおいおいしてゆきますので、今回は全体像の紹介を優先します。
【GLUTの3D描画用スケルトン】
#include <stdio.h> //C言語ベースなのでprintf等を使う場合必要
#include <GL\freeglut.h> //Embarcadero C++ コンパイラーの"BCC102\include\windows\sdk\GL"に入れる
//外部変数
int g_w, g_h; //クライアント領域幅、高さ(解説:GLUT出力ウィンドウの、です。)
GLdouble eyex = 0.0, eyey = 0.0, eyez = 0.9; //視点座標(解説:gluLookAt関数で使います。)
bool Ortho_Perspect = TRUE; //平行投影か否(透視投影)か(解説:glOrtho関数で使います。)
GLdouble fov = 60.0; //視野角(解説:gluPerspective関数で使います。nDist、fDisも同じ。)
GLdouble nDist = 1.0; //視点から視野空間手前までの距離
GLdouble fDist = 100.0; //視点から視野空間奥までの距離
//アイドル時処理関数(解説:↓のdisplay関数で行う描画の再描画処理です。メッセージがないアイドル中に行います。)
void idle(void) {
glutPostRedisplay(); //再描画関数
}
//投影(視野空間)設定(解説:所謂「モデルビュー変換」の「ビュー変換」の所です。)
void SetProjection(void) {
//変換行列の初期化(座標変換行列に単位行列を設定)
glMatrixMode(GL_PROJECTION);
glLoadIdentity(); //解説:また説明しますが、行列計算の初期化(変換行列を単位行列で初期化)です。
//透視投射範囲の設定
if(Ortho_Perspect) //解説:今は平行投影は遠近法が効かない、ということだけ覚えてください。
glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0); //平行投射範囲の設定
else { //解説:今は遠近法を使うなら透視投影を使う、と覚えてください。
if(!g_h) g_h = 1; //高さが0の場合は1に設定
gluPerspective(fov, (double)g_w /(double)g_h, nDist, fDist); //透視投射範囲の設定
}
//モデルビュー変換行列を指定
glMatrixMode(GL_MODELVIEW);
}
void DrawAxes(void) { //原点からのx, y, z軸(サンプル)-解説:矢印の矢羽根の面にも注意してください。
glPushMatrix();
glBegin(GL_LINES);
//X軸(青)
glColor3d(0.0, 0.0, 1.0);
glVertex3d(0.0, 0.0, 0.0);
glVertex3d(0.95, 0.0, 0.0);
glVertex3d(0.95, 0.0, 0.0);
glVertex3d(0.90, 0.03, 0.0);
glVertex3d(0.95, 0.0, 0.0);
glVertex3d(0.90, -0.03, 0.0);
//Y軸(緑)
glColor3d(0.0, 1.0, 0.0);
glVertex3d(0.0, 0.0, 0.0);
glVertex3d(0.0, 0.95, 0.0);
glVertex3d(0.0, 0.95, 0.0);
glVertex3d(0.03, 0.90, 0.0);
glVertex3d(0.0, 0.95, 0.0);
glVertex3d(-0.03, 0.90, 0.0);
//Z軸(赤)
glColor3d(1.0, 0.0, 0.0);
glVertex3d(0.0, 0.0, 0.0);
glVertex3d(0.0, 0.0, 0.95);
glVertex3d(0.0, 0.0, 0.95);
glVertex3d(0.0, 0.03, 0.90);
glVertex3d(0.0, 0.0, 0.95);
glVertex3d(0.0, -0.03, 0.90);
glEnd();
glPopMatrix();
}
//描画関数(解説:これがコールバック関数に登録された描画処理関数です。プリミティブの「モデル変換」は此処で行います。)
void display(void) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //色とZ(深度)バッファを初期化
glLoadIdentity(); //変換行列を初期化
gluLookAt( eyex, eyey, eyez, //eyex, eyey, eyez
0.0, 0.0, 0.0, //targetx, targety, targetz
0.0, 1.0, 0.0); //アップベクター(上方向)
DrawAxes(); //x, y, z軸を描画(サンプル)
glFlush(); //glutSwapBuffersで行われるので不要かも
glutSwapBuffers(); //glutInitDisplayMode(GLUT_DOUBLE)でダブルバッファリングを利用可
//解説:これを書いていて分かったのですが、この描画処理は「パラパラ漫画」と同じように、毎回「画面を消去し、座標データを初期化して、計算しなおし、改めて絵を書き直す」処理を繰り返すことでアニメーションを実現しています。
}
//ウィンドウサイズ変更時関数(解説:WM_SIZEの際の処理です-新しいサイズで描画用設定をやり直す感じでしょうか。)
static void resize(int width, int height)
{
//クライアントエリアサイズを記録
g_w = width; g_h = height;
//クライアントエリア全体に表示
glViewport(0, 0, g_w, g_h);
//投影設定
SetProjection();
gluLookAt( eyex, eyey, eyez, //eyex, eyey, eyez
0.0, 0.0, 0.0, //targetx, targety, targetz
0.0, 1.0, 0.0); //アップベクター(上方向)
}
//マウス入力処理関数
void mouse(int button, int state, int x, int y) { //解説:今は何もしません。どう使うのかのドンガラだけです。
switch(button) {
case GLUT_LEFT_BUTTON:
switch(state) {
case GLUT_UP:
break;
case GLUT_DOWN:
break;
default:
break;
}
break;
case GLUT_MIDDLE_BUTTON:
break;
case GLUT_RIGHT_BUTTON:
break;
default:
break;
}
}
//キーボード入力処理関数
void keyboard(unsigned char key, int x, int y) { //解説:これも終了処理以外何もしません。どう使うのかのドンガラだけです。
switch(key) {
//ESC、Qまたはqで終了
case '\033': //ESC
case 'Q':
case 'q':
exit(0);
break;
default:
break;
}
}
//初期設定関数
void init(void) { //解説:コメントアウトしているのは照光や陰面消去処理関係です。
//初期設定
//glColor(1.0, 1.0, 1.0, 1.0); //描画色指定(白)
//glEnable(GL_LIGHTING); //照光を有効化する
//glEnable(GL_LIGHT0); //光源0を有効化する
//glEnsable(GL_CULL_FACE); //前面または背面のファセットをカリングを有効にする
}
//メイン(エントリーポイント)関数
int main(int argc, char *argv[]) {
//解説:以下が典型的なGLUTの使い方です。GLUTの初期化、出力ウィンドウ初期設定と生成、コールバック関数の登録とメッセージループ処理になります。
glutInit(&argc, argv); //GLUTの初期化
glutInitDisplayMode(GLUT_RGBA | //ディスプレーの初期化
GLUT_DOUBLE | //ダブルバッファの場合
GLUT_DEPTH); //3Dグラフィックには必要
//GLUT_SINGLE); //シングルバッファーの場合
glutInitWindowPosition(100, 100); //ウィンドウ位置指定
glutInitWindowSize(640, 640); //ウィンドウサイズ指定
//ファイル名だけを表示する(解説:これは私の趣味ですね)
char* fn = argv[0];
for(char* pt = argv[0]; *pt; pt++) {
if(*pt == '\\')
fn = ++pt;
}
glutCreateWindow(fn); //タイトル付ウィンドウ生成
glShadeModel(GL_SMOOTH); //既定値の滑らかな網かけ
glEnable(GL_DEPTH_TEST); //深度テストを有効にする
glClearColor(0.0, 0.0, 0.0, 1.0); //画面消去色(黒)
glutDisplayFunc(display); //描画関数の指定
glutReshapeFunc(resize); //ウィンドウサイズ変更時関数の指定
glutIdleFunc(idle); //アイドル時処理関数の指定
glutMouseFunc(mouse); //マウス入力処理関数
glutKeyboardFunc(keyboard); //キーボード入力処理関数
init(); //ユーザーの初期設定関数
glutMainLoop(); //メインループ関数
return 0;
}
実際にこのプログラムはコンパイルすることが出来ます。そして完成したプログラムを起動するとこうなります。
「あれっ、y軸とx軸だけの2Dじゃないか?」
「こういう3Dではないの?」
と思ったあなた、そのお話はまた次回。