A. ウォークスルーの実験

ウォークスルーは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;
}

この視点を、例えば車を運転するように、 マウスのドラッグに従って自由に移動できるようにしてください。 どういう移動の仕方をするかは自分で考えて下さい。 うまい方法が思い付かなければ、以下の方式を参考にして下さい。

例えば、マウスカーソルがウィンドウの中心にある時に静止するとして、 そこからマウスを前(マウスカーソルは上)に動かせば前進、 後ろ(マウスカーソルは下)に動かせば後退、 右に動かせば右旋回、左に動かせば左旋回するようにします。 中心から離れるにつれて速く動くようになるようにしましょう。

マウスによる制御 ウォークスルー
  1. 視点の現在位置は display() の中の ex および ez の二つの変数で設定しています。この値を変更することで、 視点の位置を変えることができます。 また、その位置での進行方向は r で設定しています。
  2. 現在位置を P = (ex, ez) とし、速度ベクトルを V とすれば、 t 秒後の位置 P' = (ex', ez') は次式で求められます。
    P' = V t + P
  3. またその位置における進行方向 r' は、 現在の進行方向を r、進行方向を変える速度(ハンドルを切る速さ)を a とすれば、次式で求められます。
    r' = a t + r
  4. したがって、ウィンドウの中心位置とマウスカーソルの関係から、 速度ベクトルの絶対値(進行方向に対する速度) |V| と角速度 d を求めます。 現在位置における速度ベクトル Vは次式で求められます。
    V = (|V|sin r, |V|cos r)
    ただし、glRotated()の第1引数 r は左回転が正なのに対してマウスの座標系は右方向が正であること、および r の単位はなのに対して数学関数 sin(), cos() の引数はラジアンで与える必要があることに注意してください。
  5. ウィンドウのサイズは resize() の引数で得られますから、 それらからウィンドウの中心位置を求めます。 得られた位置は、他の関数で利用できるように、 適当な外部変数に代入しておきます。
  6. ドラッグ中のマウスの位置を得るために、 このプログラムにマウスのドラッグ中に呼び出す関数を追加し、 それを glutMotionFunc() の引数に指定します。マウスの位置は追加した関数の引数で得ることができますから、 その位置のウィンドウの中心からの変位を元に、 進行方向とその方向に対する速度を決定します。 これらも適当な外部変数に代入します。
  7. この進行方向とその方向に対する速度をもとに、 視点の現在位置を変更します。なお、時刻 t は経過時間を計測して決定する必要がありますが、 面倒なら定数でも構いません。 t = 1 くらいから始めて適当なスピードになるよう調整してみて下さい。
  8. もちろん、視点の移動は滑らかに行われるようにしてください。 また画面のちらつきも抑制するようにしてください。
移動の仕方

上のプログラムでは、視点を移動するかわりに 物体の方を逆方向に動かして います。このため 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() のようなプリミティブをこれで拡大縮小すると法線ベクトルがずれて陰影付けがおかしくなってしまいます。

なお、これはあくまで「一例」に過ぎません。 自分の思う方法で歩き回り方を考えてくれれば結構です。 ただし、 ちゃんと物体の背後に「回り込む」ことができるようにして下さい。