ウォークスルーは3次元CGシーンの中を歩き回る効果のことを言います。 これは視点の移動によるアニメーションです。 マウスで視点の位置を動かすプログラムを作ってみましょう。 これまでに作ったプログラムをベースを改造するのが手っ取り早いと思いますが、 うまく行かなければ下の手順を参考にしてください。 ソースファイル名は prog3.c としてください。
下のプログラムは球を一つだけ静止画で表示します。 球の表示には glutSolidSphere() という関数を用いています。 ただし、このプログラムでは、これを display() ではなく、別の関数 scene() の中で実行しています。
glNewList()〜glEndList() の間に挟まれた OpenGL の命令は、 glNewList() の引数に GL_COMPLIE を指定しているときにはすぐには表示されず、 OpenGL のサーバ側に保存されます。 これを実際に表示するには glCallList() を使います。 これはディスプレイリストと呼ばれ、 同じ OpenGL のコマンドを繰り返し実行する場合は、 そのコマンドをサーバ側に転送する手間が省けるために表示速度が向上します。 なお、使用可能なディスプレイリスト番号を得るには glGenLists() を用います。
#include <stdlib.h> #include <GL/glut.h> /* ディスプレイリスト番号 */ GLuint objects; void display(void) { static GLfloat lightpos[] = { 3.0, 4.0, 5.0, 1.0 }; /* 光源の位置 */ static double ex = 0.0, ez = 0.0; /* 視点の位置 */ static double r = 0.0; /* 視点の向き */ /* 画面クリア */ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* モデルビュー変換行列の初期化 */ glLoadIdentity(); /* 視点の移動 */ glRotated(r, 0.0, 1.0, 0.0); glTranslated(ex, 0.0, ez); /* 光源の位置を設定 */ glLightfv(GL_LIGHT0, GL_POSITION, lightpos); /* シーンの描画 */ glCallList(objects); glFlush(); } void resize(int w, int h) { /* ウィンドウ全体をビューポートにする */ glViewport(0, 0, w, h); /* 透視変換行列の指定 */ glMatrixMode(GL_PROJECTION); /* 透視変換行列の初期化 */ glLoadIdentity(); gluPerspective(30.0, (double)w / (double)h, 1.0, 100.0); /* モデルビュー変換行列の指定 */ glMatrixMode(GL_MODELVIEW); } void keyboard(unsigned char key, int x, int y) { /* ESC か q をタイプしたら終了 */ if (key == '\033' || key == 'q') { exit(0); } } void scene(void) { /* 物体の色 */ static GLfloat red[] = { 0.8, 0.2, 0.2, 1.0 }; static GLfloat green[] = { 0.2, 0.8, 0.2, 1.0 }; static GLfloat blue[] = { 0.2, 0.2, 0.8, 1.0 }; static GLfloat yellow[] = { 0.8, 0.8, 0.2, 1.0 }; static GLfloat ground[][4] = { { 0.6, 0.6, 0.6, 1.0 }, { 0.3, 0.3, 0.3, 1.0 } }; int i, j; /* 図形をディスプレイリストに登録 */ objects = glGenLists(1); glNewList(objects, GL_COMPILE); /* 赤い箱 */ glPushMatrix(); glTranslated(0.0, 0.0, -3.0); glMaterialfv(GL_FRONT, GL_DIFFUSE, red); glutSolidCube(1.0); glPopMatrix(); /* 緑の箱 */ glPushMatrix(); glTranslated(0.0, 0.0, 3.0); glMaterialfv(GL_FRONT, GL_DIFFUSE, green); glutSolidCube(1.0); glPopMatrix(); /* 青い箱 */ glPushMatrix(); glTranslated(-3.0, 0.0, 0.0); glMaterialfv(GL_FRONT, GL_DIFFUSE, blue); glutSolidCube(1.0); glPopMatrix(); /* 黄色い箱 */ glPushMatrix(); glTranslated(3.0, 0.0, 0.0); glMaterialfv(GL_FRONT, GL_DIFFUSE, yellow); glutSolidCube(1.0); glPopMatrix(); /* 地面 */ glBegin(GL_QUADS); glNormal3d(0.0, 1.0, 0.0); for (j = -5; j < 5; j++) { for (i = -5; i < 5; i++) { glMaterialfv(GL_FRONT, GL_DIFFUSE, ground[(i + j) & 1]); glVertex3d((GLdouble)i, -0.5, (GLdouble)j); glVertex3d((GLdouble)i, -0.5, (GLdouble)(j + 1)); glVertex3d((GLdouble)(i + 1), -0.5, (GLdouble)(j + 1)); glVertex3d((GLdouble)(i + 1), -0.5, (GLdouble)j); } } glEnd(); glEndList(); } void init(void) { /* 初期設定 */ glClearColor(1.0, 1.0, 1.0, 0.0); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); } int main(int argc, char *argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH); glutCreateWindow(argv[0]); glutDisplayFunc(display); glutReshapeFunc(resize); glutKeyboardFunc(keyboard); scene(); init(); glutMainLoop(); return 0; }
この視点を、例えば車を運転するように、 マウスのドラッグに従って自由に移動できるようにしてください。 どういう移動の仕方をするかは自分で考えて下さい。 うまい方法が思い付かなければ、以下の方式を参考にして下さい。
例えば、マウスカーソルがウィンドウの中心にある時に静止するとして、 そこからマウスを前(マウスカーソルは上)に動かせば前進、 後ろ(マウスカーソルは下)に動かせば後退、 右に動かせば右旋回、左に動かせば左旋回するようにします。 中心から離れるにつれて速く動くようになるようにしましょう。
P' = V t + P
r' = a t + r
V = (|V|sin r, |V|cos r)ただし、glRotated()の第1引数 r は左回転が正なのに対してマウスの座標系は右方向が正であること、および r の単位は度なのに対して数学関数 sin(), cos() の引数はラジアンで与える必要があることに注意してください。
上のプログラムでは、視点を移動するかわりに 物体の方を逆方向に動かして います。このため ex, ez および r の値は、 進行方向とは逆に設定する必要がありますので、注意してください。
scene() の図形は自分なりに色々変えてみてください。 ただし、あんまり複雑なものを描くと表示が遅くなります。 部品には glutSolidSphere() のほか、glutSolidCube(), glutSolidCone(), glutSolidDodecahedron(), glutSolidOctahedron(), glutSolidIcosahedron(), glutSolidTetrahedron(), glutSolidTorus(), それに glutSolidTeapot() などが用意されています。詳しくは man コマンドや GLUT ガイド日本語版などの資料を参照してください。
glutSolidTetrahedron() などはサイズを指定することができませんから、 これらの大きさを変えたいときは glScaled() を使ってください(サイズが指定できる glSolidCube() などをこれで拡大縮小すると、 法線ベクトルがずれて陰影付けがおかしくなります)
- glScaled(GLdouble x, GLdouble y, GLdouble z)
- 変換行列に拡大縮小の行列を乗じます。 引数はいずれも GLdouble 型 (double と等価)で、 3つの引数 x, y, z には拡大係数を指定します。 引数が float 型なら glScalef() を使います。ただし、glutSolidCube() のようなプリミティブをこれで拡大縮小すると法線ベクトルがずれて陰影付けがおかしくなってしまいます。
なお、これはあくまで「一例」に過ぎません。 自分の思う方法で歩き回り方を考えてくれれば結構です。 ただし、 ちゃんと物体の背後に「回り込む」ことができるようにして下さい。