前回お話しした通り、「論より証拠」、「百聞は一見に如かり」ですので、実際にやってみましょう。(そのプログラムは末尾の【TestGLUT001.cpp】を参照して下さい。)
先ずプログラムを起動します。
x-y軸の2D画面ですが、この時マウスを左クリックすると、
コンソールに"Viewpoint is set at (0.5, 0.5, 0.5)"と表示され、gluLookAt関数で視点を (0.5, 0.5, 0.5)、参照中心点を (0.0, 0.0, 0.0)とした表示になります。(ご注意いただきたいのは、まだこの段階では何ら投影(投射)設定を行っていないので、GLUTのデフォルトの状態であることです。)
次にマウス中ボタンを押すと、
コンソールに"Viewpoint is set at (-0.5, 0.5, 0.5)"と表示され、視点も(-0.5, 0.5, 0.5)に移動します。(青のx軸の先が切れていることに注意してください。視点が視野空間の中央にあるのか、x軸方向-0.5の位置からだと1.0まで見通せないようです。)
次にマウス右ボタンを押すと、
コンソールに"Viewpoint is set at (-0.5, -0.5, -0.5)"と表示され、最初の反対側から見る視点へ移動します。(今度は全部の軸がちょん切れました。矢張り見通せる範囲は視点から1.0に限られるようです。)
同様に'1', '2', '3'のキーを順に押してもらうと同じ表示(イメージは同じなので省略)になりますが、(↓のプログラムコードを見ていただくと分かりますが、プログラム冒頭のSetProjection関数を呼び出しており、投影方法を決定するOrtho_PerspectフラグがデフォルトでTRUEなので)今度は明示的にglOrtho関数で平行投影処理を行い、「xyz軸すべて-1.0~1.0までの平行投影」で表示しています。
従ってGLUTのデフォルトの表示設定だこの状態だと考えてよいと思われます。
この段階で'm | M'(または'l | L'キー)を押してください。すると、'1'キーでは、
続いて'2'キーでは、
最後に'3'キーでは、
の様に表示されます。これはデフォルトのモデル空間(ローカル空間)がxyz軸共に-1.0~1.0であるところ('s'キーを押すとこの状態に戻りますが)、'm'では-1.5~1.5、'l'では-2.0~2.0と大きくなるからです。また視点位置は変わっていないのに、なぜか3軸が離れてゆくように見えるのは、ローカル空間の投射範囲を大きくしたのですが、この空間がワールド空間に配置される際に座標が-1.0~1.0に正規化されてしまうのでしょう。このことから「平行投影範囲を変化させると、視点位置を変化させるのと見え方は変わらない」相対性があることが分かります。
では、今度は'f'キーを押してから、'1', '2', '3'のキーを順に押してみてください。
コンソールに"Viewing volume is set 0.8 away from the view point."とでますが、視点からo.8離れた所からその5倍の4.0までの視界空間(viewing volume-形は四角錐の頭を切ったような形状です)で同様の視点の移動を行った光景が見られます。更にこの視点をo.4にするために'n'を押してください。("Viewing volume is set 0.4 away from the view point."とコンソールに出ます。)視点からo.4離れた所からその5倍の2.0までの視界空間となります。
XYZ軸がはみ出した画像が出ます。又'2'、'3'通すと、
とまた異なった透視投影画像が確認できます。
このように、OpenGL(+GLUT)で、3次元グラフィックを描画するには、
(1)ローカル座標空間(モデルビュー空間)で、定義、描画したプリミティブの画像を(好みで縮小・拡大、回転、移動下後に)、
(2)ワールド座標空間へ投影(投射-デフォルトは平行投影)し、
(3)特定の視点から眺める(デフォルトはxyz軸共に-1.0~1.0)
プロセスを踏むことが必要であることが分かりました。
次回は座標だけではなく、実際に
GLSamplerで描画した正三角錐と直方体を描画し、視点を変えて眺めて
みようと思います。楽しみにしてくださいね。
【TestGLUT001.cpp】
#include <stdio.h> //C言語ベースなのでprintf等を使う場合必要
#include <GL\freeglut.h> //Embarcadero C++ コンパイラーの"BCC102\include\windows\sdk\GL"に入れる
//外部変数
int g_w, g_h; //クライアント領域幅、高さ
GLdouble eyex = 0.0, eyey = 0.0, eyez = 0.9; //視点座標
GLdouble vsize = 1.0; //平行投射範囲の1/2
bool Ortho_Perspect = TRUE; //平行投影か否(透視投影)か
GLdouble fov = 60.0; //視野角
GLdouble nDist = 1.0; //投射投影の視野垂台への距離
//投影(視野空間)設定
void SetProjection(void) {
//変換行列の初期化(投影(プロジェクション)座標変換行列に単位行列を設定-注1)
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//透視投射範囲の設定
if(Ortho_Perspect)
glOrtho(-vsize, vsize, -vsize, vsize, -vsize, vsize); //平行投射範囲の設定
else {
if(!g_h) g_h = 1; //高さが0の場合は1に設定(解説:Division by 0エラー回避策です。)
gluPerspective(fov, (double)g_w /(double)g_h, nDist, nDist * 5); //透視投射範囲の設定
}
//モデルビュー変換行列を指定-注2
glMatrixMode(GL_MODELVIEW);
}
/*
注1:投影(プロジェクション)座標変換行列とはモデルビュー座標をワールド座標に変換する為に乗じる行列です。「単位行列」とは「座標A行列 x 単位行列 = 座標A行列」となるような行列で、GLUTでは
1,0,0,0
0,1,0,0
0,0,1,0
0,0,0,1
の様になります。
注2:モデル座標変換行列とは、回転、縮小拡大、平行移動をするためにプリミティブのローカル座標に乗じる行列です。
*/
void DrawAxes(void) { //解説:RGBのxyz軸を描く関数です。
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 idle(void) {
glutPostRedisplay(); //再描画関数(解説:これを読むと、InvalidateRectの様に再描画要求を出すだけですね。)
}
//描画関数
void display(void) { //GLUTのコールバック関数です。描画命令を書き込みます。
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); //アップベクター(上方向)-解説:画面の上がxyz軸いずれかを選びます。
DrawAxes(); //x, y, z軸を描画(サンプル)
glFlush(); //glutSwapBuffersで行われるので不要かも
glutSwapBuffers(); //glutInitDisplayMode(GLUT_DOUBLE)でダブルバッファリングを利用可
}
//ウィンドウサイズ変更時関数
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: //解説:マウス左ボタンが押された時
eyex = eyey = eyez = 0.5;
//透視変換(視野角90度、ビューポートの縦横比で、視点から1~4迄の視野空間を設定)
printf("Viewpoint is set at (0.5, 0.5, 0.5)\r\n");
break;
case GLUT_MIDDLE_BUTTON: //解説:マウス中央ボタンが押された時
eyex = -0.5;
eyey = eyez = 0.5;
printf("Viewpoint is set at (-0.5, -0.5, 0.5)\r\n");
break;
case GLUT_RIGHT_BUTTON: //解説:マウス右ボタンが押された時
eyex = eyey = eyez = -0.5;
printf("Viewpoint is set at (-0.5, 0.5, 0.5)\r\n");
break;
default:
break;
}
display();
}
//キーボード入力処理関数-解説:キー入力関連のウィンドウメッセージ処理を簡単にしたものです。
void keyboard(unsigned char key, int x, int y) {
switch(key) {
case '\033': //ESC
case 'q': //ESC か q をタイプしたら終了
exit(0);
case '1': //解説:'1'キ-が押された時
eyex = eyey = eyez = 0.5;
printf("Viewpoint is set at (0.5, 0.5, 0.5)\r\n");
break;
case '2': //解説:'2'キ-が押された時
eyex = -0.5;
eyey = eyez = 0.5;
printf("Viewpoint is set at (-0.5, 0.5, 0.5)\r\n");
break;
case '3': //解説:'3'キ-が押された時
eyex = eyey = eyez = -0.5;
printf("Viewpoint is set at (-0.5, -0.5, -0.5)\r\n");
break;
case 'S': //解説:'S | s'キ-が押された時
case 's':
Ortho_Perspect = TRUE; //平行投影変換
vsize = 1.0;
printf("Viewable space is set in the demention of -1.0 to 1.0 for both X, Y and Z.\r\n");
break;
case 'M': //解説:'M | m'キ-が押された時
case 'm':
Ortho_Perspect = TRUE; //平行投影変換
vsize = 1.5;
printf("Viewable space is set in the demention of -1.5 to 1.5 for both X, Y and Z.\r\n");
break;
case 'L': //解説:'L | l'キ-が押された時
case 'l':
Ortho_Perspect = TRUE; //平行投影変換
vsize = 2.0;
printf("Viewable space is set in the demention of -2.0 to 2.0 for both X, Y and Z.\r\n");
break;
case 'F': //解説:'F | f'キ-が押された時
case 'f':
Ortho_Perspect = FALSE; //透視投影変換
nDist = 0.8;
printf("Viewing volume is set 0.8 away from the view point.\r\n");
break;
case 'N': //解説:'N | n'キ-が押された時
case 'n':
Ortho_Perspect = FALSE; //透視投影変換
nDist = 0.4;
printf("Viewing volume is set 0.4 away from the view point.\r\n");
break;
default:
break;
}
SetProjection();
display();
}
//メイン(エントリーポイント)関数
int main(int argc, char *argv[]) {
glutInit(&argc, argv); //GLUTの初期化
glutInitDisplayMode(GLUT_RGBA | //ディスプレーの初期化
GLUT_DOUBLE | //ダブルバッファの場合
GLUT_DEPTH); //3Dグラフィックには必要
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); //キーボード入力処理関数
glutMainLoop(); //メインループ関数
return 0;
}