床井研究室

がんばれなんて言えない

気分が落ち込んでいるときは人と関わることが特に億劫になっているので、研究室の学生さん達との関係も疎遠になったりしてしまいます(代わりにこのブログなどで情報提供しているわけですが)。それでも彼らは自分達で何か進めているようです。けなげです。だから、彼らに多少進捗状況が遅いからといって「がんばれ」なんて無責任なことは言えません。だいたい、自分自身が「がんばれ」と言われることで煮詰まってしまうし。

シャドウマッピングと Frame Buffer Object (FBO)

シャドウマッピングでは、デプスバッファをテクスチャとして参照して影の領域の判定を行いますから、デプスバッファの内容をテクスチャメモリに転送する必要があります。FBO を使えば直接テクスチャメモリにレンダリングできますから、この転送を省くことができます。 それでは以前に書いたGLSL によるシャドウマッピングのプログラムを雛形にして、これを FBO を使うように変更してみたいと思います。

まず前回と同様、これに FBO に使う関数の宣言を追加します。この雛形の Windows 版に含まれている glext.h もバージョンが古く、FBO に関する宣言が含まれていませんので、最新の glext.h を入手して差し替えてください。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#if defined(_WIN32)
//#  pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#  include "glut.h"
#  include "glext.h"
PFNGLMULTTRANSPOSEMATRIXDPROC glMultTransposeMatrixd;
PFNGLGENFRAMEBUFFERSEXTPROC glGenFramebuffersEXT;
PFNGLBINDFRAMEBUFFEREXTPROC glBindFramebufferEXT;
PFNGLFRAMEBUFFERTEXTURE2DEXTPROC glFramebufferTexture2DEXT;
PFNGLGENRENDERBUFFERSEXTPROC glGenRenderbuffersEXT;
PFNGLBINDRENDERBUFFEREXTPROC glBindRenderbufferEXT;
PFNGLRENDERBUFFERSTORAGEEXTPROC glRenderbufferStorageEXT;
PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC glFramebufferRenderbufferEXT;
PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC glCheckFramebufferStatusEXT;
#elif defined(__APPLE__) || defined(MACOSX)
#  include <GLUT/glut.h>
#else
#  define GL_GLEXT_PROTOTYPES
#  include <GL/glut.h>
#endif
#include "glsl.h"

テクスチャオブジェクトとフレームバッファオブジェクトの識別子に使う変数を用意しておきます。

...

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

/*
** テクスチャオブジェクト・フレームバッファオブジェクト
*/
static GLuint tex, fb;

Windows では、FBO に使う API のエントリポイントを確保しておきます。今回も FBO が使えるという前提でプログラムを書いているので、もし FBO が使えない環境で実行した場合にはエラーとなってしまいますので注意してください。

/*
** 初期化
*/
static void init()
{
  /* シェーダプログラムのコンパイル/リンク結果を得る変数 */
  GLint compiled, linked;

#if defined(_WIN32)
  glGenFramebuffersEXT =
    (PFNGLGENFRAMEBUFFERSEXTPROC)wglGetProcAddress("glGenFramebuffersEXT");
  glBindFramebufferEXT =
    (PFNGLBINDFRAMEBUFFEREXTPROC)wglGetProcAddress("glBindFramebufferEXT");
  glFramebufferTexture2DEXT =
    (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)wglGetProcAddress("glFramebufferTexture2DEXT");
  glGenRenderbuffersEXT =
    (PFNGLGENRENDERBUFFERSEXTPROC)wglGetProcAddress("glGenRenderbuffersEXT");
  glBindRenderbufferEXT =
    (PFNGLBINDRENDERBUFFEREXTPROC)wglGetProcAddress("glBindRenderbufferEXT");
  glRenderbufferStorageEXT =
    (PFNGLRENDERBUFFERSTORAGEEXTPROC)wglGetProcAddress("glRenderbufferStorageEXT");
  glFramebufferRenderbufferEXT =
    (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)wglGetProcAddress("glFramebufferRenderbufferEXT");
  glCheckFramebufferStatusEXT =
    (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)wglGetProcAddress("glCheckFramebufferStatusEXT");
#endif

テクスチャオブジェクトを生成し、それを現在のテクスチャに結合しておきます。

  /* テクスチャオブジェクトを生成して結合する */
  glGenTextures(1, &tex);
  glBindTexture(GL_TEXTURE_2D, tex);

  /* デプステクスチャの領域を確保する */
  glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, TEXWIDTH, TEXHEIGHT, 0,
               GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);

  /* テクスチャを拡大・縮小する方法の指定 */
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

  /* テクスチャの繰り返し方法の指定 */
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

  /* 書き込むポリゴンのテクスチャ座標値のRとテクスチャとの比較を行うようにする */
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);

  /* もしRの値がテクスチャの値以下なら真(つまり日向) */
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);

  /* 比較の結果を輝度値として得る */
  glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE);

  /* テクスチャの結合を解除する */
  glBindTexture(GL_TEXTURE_2D, 0);

次に、フレームバッファオブジェクトを生成し、それに先ほど作成したテクスチャオブジェクトを結合します。

  /* フレームバッファオブジェクトを生成する */
  glGenFramebuffersEXT(1, &fb);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);

  /* フレームバッファオブジェクトにデプステクスチャを結合する */
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
                            GL_TEXTURE_2D, tex, 0);

  /* カラーバッファを読み書きしない */
  glDrawBuffer(GL_NONE);
  glReadBuffer(GL_NONE);

  /* フレームバッファオブジェクトの結合を解除する */
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

そして、光源から見たシーンの描画を行います。

/*
** 描画
*/
static void display(void)
{
  /* 光源から見たシーンの描画 */

  /* フレームバッファオブジェクトへのレンダリング開始 */
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);

  /* デプスバッファだけクリアする */
  glClear(GL_DEPTH_BUFFER_BIT);

  /* ビューポートをテクスチャのサイズに設定する */
  glViewport(0, 0, TEXWIDTH, TEXHEIGHT);

  /* 透視変換行列の設定 */
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(lightfov, 1.0, lightnear, lightfar);

  /* 光源の視点の設定 */
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(lightpos[0], lightpos[1], lightpos[2],
            lighttarget[0], lighttarget[1], lighttarget[2],
            0.0, 1.0, 0.0);

  /* 陰影付けを行わない */
  glDisable(GL_LIGHTING);

  /* 裏面をカリングする */
  glCullFace(GL_BACK);

  /* シーンを描画する */
  scene(t);

  /* フレームバッファオブジェクトへのレンダリング終了 */
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

#if 0
  /* デプスバッファの内容をテクスチャメモリに転送する */
  glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, TEXWIDTH, TEXHEIGHT);
#endif

  /* 通常の描画の設定に戻す */
  glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
  glMatrixMode(GL_PROJECTION);
  glLoadMatrixd(projection);
  glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
  glEnable(GL_LIGHTING);
  glCullFace(GL_BACK);

テクスチャマッピングを有効にする際に、テクスチャオブジェクトを結合しておきます。

  /* モデルビュー変換行列に戻す */
  glMatrixMode(GL_MODELVIEW);

  /* テクスチャオブジェクトを結合する */
  glBindTexture(GL_TEXTURE_2D, tex);

  /* テクスチャマッピングを有効にする */
  glEnable(GL_TEXTURE_2D);

視点から見たシーンの描画が済み、テクスチャマッピングを無効にしたら、テクスチャオブジェクトの結合を解除しておきます。

  glDisable(GL_TEXTURE_2D);

  /* テクスチャオブジェクトの結合を解除する */
  glBindTexture(GL_TEXTURE_2D, 0);

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

この場合もテクスチャのサイズでウインドウのサイズを制限する必要はなくなるので、その部分を無効にしておきます。

static void resize(int w, int h)
{
#if 0
  /* ウィンドウサイズの縮小を制限する */
  if (w < TEXWIDTH || h < TEXHEIGHT) {
    if (w < TEXWIDTH) w = TEXWIDTH;
    if (h < TEXHEIGHT) h = TEXHEIGHT;
    glutReshapeWindow(w, h);
  }
#endif

  /* トラックボールする範囲 */
  trackballRegion(w, h);

初期ウィンドウサイズもデフォルトに戻しておきます。

/*
** メインプログラム
*/
int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
#if 0
  glutInitWindowSize(TEXWIDTH, TEXHEIGHT);
#endif
  glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);

パフォーマンスについて

正確な測定は行っていませんが、このプログラムの場合は、パフォーマンスはだいぶ向上した気がします。

2008年12月4日追記

ATI のビデオカードのドライバを更新したら、影が逆回りするようになってしまいました(nVIDIA のビデオカードでは問題ありません)。どうも、デプステクスチャが上下反転して読み込まれているようです。原因がわからず、いろいろ試した結果、カラーバッファに描き込まないようにすればカラーバッファを用意する必要は無い(エラーにならない)と分かったので、そのように書き換えたら直りました。