床井研究室

テクスチャ画像の生成

これまではファイルに保存しておいた画像を使ってテクスチャマッピングを行ってきました。したがって読み込んだ画像は、プログラムの実行中に変更することなく静止画として利用してきました。もし、このテクスチャをプログラム内で動的に生成できれば、更に凝った効果を色々実現できるようになります。ということで、サンプルプログラムを作ってみました。

オフスクリーンレンダリング

プログラムが自分自身でレンダリングした画像をテクスチャに利用することができれば、テクスチャを動的に変更することが可能になります。しかし、通常のレンダリングは画面表示を行うために用いるフレームバッファに対して行ないますから、テクスチャ用にレンダリングした(最終結果ではない)画像も、利用者に見えてしまいます。そこで、このような画像は画面表示が行われないところにレンダリングします。このようなレンダリング方法のことをオフスクリーンレンダリングといいます。また、画面表示が行われないフレームバッファのことをオフスクリーンバッファといいます。

オフスクリーンバッファにはいくつかの種類があります。pbuffer (pixel buffer) や framebuffer object は、オフスクリーンバッファを用意するための OpenGL の拡張機能です。ただし、pbuffer はプラットホーム依存(Windows や Mac OS X で使い方が異なる)なので、GLUT を使っているありがたみを失ってしまいます(私が使い方を知らないというだけですが)。framebuffer object は OpenGL の中で完結した機能ですが、新しい拡張機能のため使用できる環境がまだ限られています(これも私が使い方を知らないだけです、ごめんなさい)。

そこで、ここではオフスクリーンバッファとして、ダブルバッファリングの時のバックバッファを使用することにします。ダブルバッファリングを行っている際、このバッファは glutSwapBuffers() を呼び出すまで画面に表示されませんから、その間にテクスチャのレンダリングも済ましてしまいます。この方法はテクスチャのサイズが開いたウィンドウのサイズに制限されてしまうなどの問題がありますが、簡便なので pbuffer や framebuffer object が登場する前に一般的に使用されてきました。

キューブマッピング用のテクスチャをレンダリングする

それではサンプルプログラムの解説をします。このプログラムはキューブマッピング用のテクスチャをレンダリングし、それを使ってキューブマッピングを行います。キューブマッピングには6枚のテクスチャを用いますから、下図に示すウィンドウ内の領域のそれぞれにテクスチャをレンダリングすることにします。

テクスチャのレンダリング位置

別にテクスチャをこんな風に(十字形に)配置する必要はないのですが(ウィンドウを3×2に分割して配置したほうが小さくて済むし)、この方がテクスチャとしてレンダリングした画像を眺めるときに楽しいので、こういう具合にやってます。この配置を元に、テクスチャのターゲット名や領域の左下の位置、それにその領域に画像をレンダリングする際のカメラ(視線)の方向などを、配列変数に格納しておくことにします。

/*
** テクスチャ
*/
#define TEXWIDTH  128                               /* テクスチャの幅    */
#define TEXHEIGHT 128                               /* テクスチャの高さ   */

/*
** キューブマッピングのターゲットと、そこに向かう視線の向きと「上」の方向
*/
static const struct {
  GLenum name;                                  /* テクスチャのターゲット名 */
  GLint x, y;                                   /* ビューポートの位置    */
  GLdouble cx, cy, cz;                          /* 視線の向き        */
  GLdouble ux, uy, uz;                          /* 「上」の方向       */
} target[] = {
  { /* 左 */
    GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
    0, TEXHEIGHT,
    1.0, 0.0, 0.0,
    0.0, 1.0, 0.0,
  },
  { /* 前 */
    GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
    TEXWIDTH, TEXHEIGHT,
    0.0, 0.0, 1.0,
    0.0, 1.0, 0.0,
  },
  { /* 右 */
    GL_TEXTURE_CUBE_MAP_POSITIVE_X,
    TEXWIDTH * 2, TEXHEIGHT,
    -1.0, 0.0, 0.0,
    0.0, 1.0, 0.0,
  },
  { /* 後 */
    GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
    TEXWIDTH * 3, TEXHEIGHT,
    0.0, 0.0, -1.0,
    0.0, 1.0, 0.0,
  },
  { /* 下 */
    GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
    TEXWIDTH, TEXHEIGHT * 2,
    0.0, 1.0, 0.0,
    0.0, 0.0, -1.0,
  },
  { /* 上 */
    GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
    TEXWIDTH, 0,
    0.0, -1.0, 0.0,
    0.0, 0.0, 1.0,
  },
};

また、キューブマッピングのテクスチャに使う周囲のシーンを作るために、原点を中心に箱をいっぱい描きます。これはキューブマッピングのテクスチャをひとつ作るごとに使うので、ディスプレイリスト(変数 stars)に入れておきます。

/*
** 星
*/
#define MAXSTARS 200
static GLuint stars;

/*
** ウィンドウサイズ
*/
static GLsizei width, height;

/*
** 初期化
*/
static void init()
{
  ...

  /* 星の生成 */
  stars = glGenLists(1);

  /* 星のディスプレイリストを作成する */
  glNewList(stars, GL_COMPILE);

  /* 星として箱をいっぱい描く */
  for (int i = 0; i < MAXSTARS; ++i) {
    float r = 2.5f * (float)rand() / (float)RAND_MAX + 2.5f;
    float t = 6.2831853f * (float)rand() / (float)RAND_MAX;
    float p = 3.1415926f * (float)rand() / (float)RAND_MAX;
    float c[] = {
      0.9f * (float)rand() / (float)RAND_MAX + 0.1f,
      0.9f * (float)rand() / (float)RAND_MAX + 0.1f,
      0.9f * (float)rand() / (float)RAND_MAX + 0.1f,
    };

    glPushMatrix();
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c);
    glTranslatef(r * sinf(p) * cosf(t), r * cosf(p), r * sinf(p) * sinf(t));
    glScalef(0.5f, 0.5f, 0.5f);
    glutSolidCube(0.8);
    glPopMatrix();
  }

  /* ディスプレイリストの作成終了 */
  glEndList();
}

これは、こういう図形になります。

環境のテクスチャに使う周囲のシーン

ここまで準備ができたら、テクスチャのレンダリングに取り掛かります。キューブマッピング用のテクスチャを作成するには、原点にカメラを置き、そこを中心とする立方体の六面にカメラを向けるようにして、周囲を撮影します。

キューブマッピングのテクスチャを作成する際のカメラの配置

立方体の各面(正方形)がきっちりとカメラに収まるよう、カメラの画角を 90°に設定し、アスペクト比を1に設定します。前方面と後方面の位置は、カメラの周囲にばら撒いた箱の範囲に合わせます。

static void display()
{
  /* 透視変換行列の設定 */
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(90.0, 1.0, 1.0, 10.0);

  /* モデルビュー変換行列の設定 */
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  /* テクスチャの6面分の画面クリア */
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

まず、テクスチャをレンダリングするウィンドウ内の領域にビューポートを設定します。そして、その方向にカメラを向けた後、光源を設定してからモデル変換(ここではトラックボール処理)を行って、カメラの周囲のシーンを描きます。

  /* テクスチャの作成 */
  for (int i = 0; i < 6; ++i) {

    /* ビューポートをテクスチャのサイズに設定する */
    glViewport(target[i].x, target[i].y, TEXWIDTH, TEXHEIGHT);

    /* 視線の方向を設定して、その向きに見えるものをレンダリングする */
    glPushMatrix();
    gluLookAt(0.0, 0.0, 0.0,
      target[i].cx, target[i].cy, target[i].cz,
      target[i].ux, target[i].uy, target[i].uz);
    glLightfv(GL_LIGHT0, GL_POSITION, lightpos);
    glMultMatrixd(trackballRotation());
    glCallList(stars);
    glPopMatrix();

以上の処理で、こういう画像がレンダリングされます。

レテクスチャとしてレンダリングした画像

こうしてレンダリングした画像を、テクスチャメモリにコピーします。

    /* レンダリングした結果をテクスチャメモリに移す */
    glCopyTexSubImage2D(target[i].name, 0, 0, 0,
      target[i].x, target[i].y, TEXWIDTH, TEXHEIGHT);
  }
void glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height)
描画対象になっているフレームバッファの内容の一部をテクスチャメモリにコピーします。target には GL_TEXTURE_2D を指定します。level はミップマップの解像度レベルで、0 がベースの(最も解像度の高い)レベルで、ミップマップを使用しない時は 0 を指定しておきます。xoffsetyoffset はコピー先のテクスチャ上の位置を指定します。xy はコピー元のフレームバッファ上の位置です。widthheight はコピーする領域の幅と高さです。

なお、こうして得られたテクスチャは、キューブマッピング用のテクスチャとして使うには裏返しにする必要があります。そこでテクスチャ変換行列にテクスチャ座標の x 座標値と y 座標値を反転する設定をして、テクスチャを裏側からサンプリングするようにします。

  /* テクスチャ変換行列の設定 */
  glMatrixMode(GL_TEXTURE);
  glLoadIdentity();
  glScaled(-1.0, -1.0, 1.0);

レンダリングしたテクスチャを使って最終的な画像をレンダリングする

このテクスチャを使って、最終的な画像をレンダリングします。上の画像はすでにテクスチャメモリにコピーしてありますから、ここで消してしまいます。そしてウィンドウ全体をビューポートにしてから、通常のシーンのレンダリングを行います。変数 widthheight は、関数 resize() が呼び出されたときのウィンドウのサイズを保存したものです。

  /* 表示用の画面クリア */
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  /* ウィンドウ全体をビューポートにする */
  glViewport(0, 0, width, height);

  /* 透視変換行列の指定 */
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(60.0, (double)width / (double)height, 0.1, 10.0);

  /* モデルビュー変換行列の設定 */
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  /* 視点の移動(物体の方を奥に移動)*/
  glTranslated(0.0, 0.0, -7.0);

  /* 光源の位置を設定 */
  glLightfv(GL_LIGHT0, GL_POSITION, lightpos);

  /* トラックボール処理による回転 */
  glMultMatrixd(trackballRotation());

  /* シーンの描画 */
  scene();

  /* ダブルバッファリング */
  glutSwapBuffers();
}

これでこういう画像が得られます。

レンダリング画像をテクスチャに使う