«第25回 球にバンプマッピング 最新 第27回 シャドウマッピング»

床井研究室

※このブログは遅くとも 2027 年 3 月に管理者の定年退職により閉鎖します (移転先は管理者本人共々模索中)

■ 2005年09月23日 [OpenGL][テクスチャ] 第26回 レンダリング画像をテクスチャに使う

2012年06月13日 11:55更新

テクスチャ画像の生成

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

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

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

オフスクリーンバッファにはいくつかの種類があります.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 void init(void)
{
  int i;
  
  ・・・
  
  /* 星の生成 */
  stars = glGenLists(1);
  glNewList(stars, GL_COMPILE);
  
  /* 星として箱をいっぱい描く */
  for (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(void)
{
  /* 画面クリア */
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
  /* 透視変換行列の設定 */
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(90.0, 1.0, 1.0, 10.0);

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

  /* モデルビュー変換行列の設定 */
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  
  /* テクスチャの作成 */
  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 を指定しておきます.xoffset と yoffset はコピー先のテクスチャ上の位置を指定します.x と y はコピー元のフレームバッファ上の位置です.width と height はコピーする領域の幅と高さです.

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

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

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

このテクスチャを使って,最終的な画像をレンダリングします.上の画像はすでにテクスチャメモリにコピーしてありますから,ここで消してしまいます.そしてウィンドウ全体をビューポートにしてから,通常のシーンのレンダリングを行います.変数 width と height は,関数 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, 1.0, 100.0);
  
  /* モデルビュー変換行列の設定 */
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  
  /* 視点の移動(物体の方を奥に移動)*/
  glTranslated(0.0, 0.0, -7.0);
  
  /* 光源の位置を設定 */
  glLightfv(GL_LIGHT0, GL_POSITION, lightpos);
  
  /* トラックボール処理による回転 */
  glMultMatrixd(trackballRotation());
  
  /* シーンの描画 */
  scene();
  
  /* ダブルバッファリング */
  glutSwapBuffers();
}

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

キューブマッピングした結果の画像

コメントでムービーをいただきましたので,埋め込まさせていただきました.uglab さま,ありがとうございました.

コメント(4) [コメントを投稿する]
uglab 2011年11月29日 19:51

参考にさせて頂きました。<br>大変ありがたいです。<br><br>私はGLUTの細部を理解していない素人ですが<br>サンプルを少し修正して箱達を回転させてみました。<br>動画を作成したのでurlをはらせていただきます。<br><br>http://www.youtube.com/watch?v=PAqBxjb1IBw<br><br>不適切でしたらお手数ですがコメントの削除をお願いします。

とこ 2011年11月30日 00:46

コメントありがとうございます.お役に立てましたのなら非常に光栄です.<br>ムービー拝見しました.このページではムービーを用意していないので参考になります!<br>なぜかニヤニヤしてしまいました.

uglab 2011年11月30日 19:24

分かります。<br>私も動画を作りながらニヤニヤしていました。

とこ 2012年06月13日 11:54

埋め込まさせていただきました.ありがとうございます.


編集 «第25回 球にバンプマッピング 最新 第27回 シャドウマッピング»