床井研究室

旅費が出た!

以前、お金が無い!と嘆いていたのですが、関係各位?のご尽力により、国際会議の旅費に加えて参加費まで大学(への寄付金)から出してもらえることになりました!助かりました。今後は大学の乏しい資金に頼らず、自分で外部資金等を取得するよう努力したいと思います。本当にありがとうございました。

Framebuffer Object (FBO) を使ってみる

その国際会議も目前に迫ってきているんですが、それがストレスになっているのか、精神的にはあまり安定していません。そういう時はプログラミングに逃避するというものひとつの手なので、以前から試してみなくちゃと思いつつほったらかしにしていた Framebuffer Object (FBO) を試してみました。FBO はフレームバッファを構成する要素の集合体であり、テクスチャやレンダーバッファをカラーバッファや Z バッファとして用いて、それらを組み合わせて構成します。 以前に書いたレンダリング画像をテクスチャに使う方法では、ダブルバッファのバックバッファにレンダリングした画像を glTexCopySubImage() を使ってテクスチャにコピーする手法を使っていました。今回はこれを雛形にして、FBO を使って直接テクスチャにレンダリングするように変更してみます。

まず、これに FBO に使う関数の宣言を追加します。ただし、上の雛形の Windows 版に含まれている glext.h はバージョンが古く、FBO に関する宣言が含まれていません。最新の glext.h は OpenGL® Extension Registry から入手できますので、それと差し替えてください。なお、OpenGL® Extension Registry は最近 SGI から OpenGL.org に移管されたようです。

#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"
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

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

...

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

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

Windows では、FBO に使う API のエントリポイントを確保しておきます。例によって今回も FBO が使えるという前提でプログラムを書いているので、もし FBO が使えない環境で実行した場合にはエラーとなってしまいますので注意してください。このあたりのことをもっと楽にやりたければ、GLEW の導入を検討してください。

/*
** 初期化
*/
static void init()
{
  int i;

#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_CUBE_MAP, tex);

  /* 6面分のテクスチャ領域を確保する */
  for (i = 0; i < 6; ++i) {
    glTexImage2D(target[i].name, 0, GL_RGBA, TEXWIDTH, TEXHEIGHT, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, 0);
  }

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

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

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

次に、フレームバッファオブジェクトとレンダーバッファオブジェクトを生成します。レンダーバッファオブジェクトには、Z バッファの領域を確保しておきます。

  /* レンダーバッファオブジェクト(デプスバッファ)を生成する */
  glGenRenderbuffersEXT(1, &rb);
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rb);
  glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT,
                           TEXWIDTH, TEXHEIGHT);
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);

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

  /* フレームバッファオブジェクトにレンダーバッファオブジェクト(デプスバッファ)を結合する */
  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
                               GL_RENDERBUFFER_EXT, rb);

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

そして、FBO へのレンダリングを行います。

/*
** 描画
*/
static void display(void)
{
  int i;

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

  /* キューブマップの6面を描く */
  for (i = 0; i < 6; ++i) {
    /* カラーアタッチメント(描画先)にキューブマップの1面を割り当てる */
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                              target[i].name, tex, 0);

    /* ビューポートを設定する */
    glViewport(0, 0, TEXWIDTH, TEXHEIGHT);

    /* 画面クリア */
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

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

    /* モデルビュー変換行列の設定 */
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(0.0, 0.0, 0.0,
              target[i].center[0], target[i].center[1], target[i].center[2],
              target[i].up[0], target[i].up[1], target[i].up[2]);

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

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

#if 0
    /* レンダリングした結果をテクスチャメモリに移す */
    glCopyTexSubImage2D(target[i].name, 0, 0, 0,
                        target[i].x, target[i].y, TEXWIDTH, TEXHEIGHT);
#endif
  }

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

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

任意のサイズのテクスチャを使う

FBO ではテクスチャのサイズが開いたウィンドウのサイズに制限されないので、テクスチャの大きさはシステムの制限の範囲内で自由に設定できます。試しに、大きなテクスチャを使ってみます。

...

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

テクスチャのサイズに合わせてウィンドウのサイズを制限する必要もなくなりますので、その部分を無効にします。

...

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

  /* ウィンドウサイズの保存 */
  width = w;
  height = h;

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

ウィンドウサイズの初期値も設定しないようにしておきます。

...

/*
** メインプログラム
*/
int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
#if 0
  glutInitWindowSize(TEXWIDTH * 4, TEXWIDTH * 3);
#endif
  glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
  glutCreateWindow(argv[0]);
  glutDisplayFunc(display);
  glutReshapeFunc(resize);
  glutMouseFunc(mouse);
  glutMotionFunc(motion);
  glutKeyboardFunc(keyboard);
  init();
  glutMainLoop();
  return 0;
}

パフォーマンスは向上するか

テクスチャのコピーを行わない分、パフォーマンスは上がるはずですが、このデモではそういう比較はできないので、確かめられません。でも、少なくともテクスチャのサイズに制限がなくなるのはうれしいですね。前のものはかなりテクスチャが粗かったですから。そのうちシャドウマッピング でも試してみようと思います。