«3D テクスチャ 最新 シャドウマッピングで FBO を使ってみる»

床井研究室

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

■ 2006年07月15日 [OpenGL][テクスチャ] キューブマッピングで FBO を使ってみる

2008年12月05日 08:54更新

旅費が出た!

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

Framebuffer Object (FBO) を使ってみる

その国際会議も目前に迫ってきているんですが,それがストレスになっているのか,精神的にはあまり安定していません.そういう時はプログラミングに逃避するというものひとつの手なので,以前から試してみなくちゃと思いつつほったらかしにしていた Framebuffer Object (FBO) を試してみました.FBO はフレームバッファを構成する要素の集合体であり,テクスチャやレンダーバッファをカラーバッファや Z バッファとして用いて,それらを組み合わせて構成します.

以前に書いたレンダリング画像をテクスチャに使う方法では,ダブルバッファのバックバッファにレンダリングした画像を glTexCopySubImage() を使ってテクスチャにコピーする手法を使っていました.今回はこれを雛形にして,FBO を使って直接テクスチャにレンダリングするように変更してみます.

まず,これに FBO に使う関数の宣言を追加します.ただし,上の雛形の Windows 版に含まれている glext.h はバージョンが古く,FBO に関する宣言が含まれていません.最新の glext.hOpenGL® 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(void)
{
  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);
  
  for (i = 0; i < 6; ++i) {
    /* テクスチャの割り当て */
    glTexImage2D(target[i].name, 0, GL_RGBA, TEXWIDTH, TEXHEIGHT, 0,
      GL_RGBA, GL_UNSIGNED_BYTE, 0);
  }
  
  ・・・

キューブマッピングのテクスチャの設定が済んだところで,デフォルトのテクスチャオブジェクトに戻しておきます.

  ・・・
  /* キューブマッピング用のテクスチャ座標を生成する */
  glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
  glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
  glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
  
  /* テクスチャオブジェクトの結合を解除する */
  glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

フレームバッファオブジェクトとレンダーバッファ

次に,フレームバッファオブジェクトとレンダーバッファの生成を行います.これらの生成はテクスチャオブジェクトと同じような感じです.テクスチャを(後で glFramebufferTexture2DEXT() を使って)フレームバッファオブジェクトに結びつけることによって,直接テクスチャにレンダリングできるようになります.一方,レンダーバッファはテクスチャとしては使えませんが,隠面消去を行うなら Z バッファが必要になるので,glRenderbufferStorageEXT() を使ってデプスバッファ用のレンダーバッファを確保して,glFramebufferRenderbufferEXT() でフレームバッファオブジェクトに結び付けておきます.

  /* フレームバッファオブジェクトを生成して結合する */
  glGenFramebuffersEXT(1, &fb);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
  
  /* フレームバッファオブジェクトにテクスチャオブジェクトを結合する */
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
    target[0].name, tex, 0);
  
  /* フレームバッファオブジェクトの結合を解除する */
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
  
  /* レンダーバッファを生成して結合する */
  glGenRenderbuffersEXT(1, &rb);
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rb);
  glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT,
    TEXWIDTH, TEXHEIGHT);
  
  /* フレームバッファオブジェクトにレンダーバッファを結合する */
  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
    GL_RENDERBUFFER_EXT, rb);
  
  /* レンダーバッファの結合を解除する */
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
  
  /* 初期設定 */
  glClearColor(0.3, 0.3, 1.0, 1.0);
  glEnable(GL_DEPTH_TEST);
  glDisable(GL_CULL_FACE);
  
  ・・・

キューブマッピングする図形を描画する際には,キューブマッピングのテクスチャオブジェクトを結合しておきます.

・・・
 
/*
** シーンの描画
*/
static void scene(void)
{
  static const GLfloat color[] = { 1.0, 1.0, 1.0, 1.0 };  /* 材質 (色) */
  
  /* 星の描画 */
  glCallList(stars);
  
  /* 材質の設定 */
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
  
  /* キューブマッピングのテクスチャオブジェクトを結合する */
  glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
  
  /* テクスチャマッピング開始 */
  glEnable(GL_TEXTURE_CUBE_MAP);
  
  /* テクスチャ座標の自動生成を有効にする */
  glEnable(GL_TEXTURE_GEN_S);
  glEnable(GL_TEXTURE_GEN_T);
  glEnable(GL_TEXTURE_GEN_R);
  
#if 1
  /* ティーポットを描く */
  glutSolidTeapot(1.8);
#else
  /* 球を描く */
  glutSolidSphere(1.5, 32, 16);
#endif
  
  /* テクスチャ座標の自動生成を無効にする */
  glDisable(GL_TEXTURE_GEN_S);
  glDisable(GL_TEXTURE_GEN_T);
  glDisable(GL_TEXTURE_GEN_R);
  
  /* テクスチャマッピング終了 */
  glDisable(GL_TEXTURE_CUBE_MAP);
  
  /* キューブマッピングのテクスチャオブジェクトの結合を解除する */
  glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
}
 
・・・

バックバッファへのレンダリングは行わないので,最初の画面クリアは無効にしておきます.#if 0 〜 #endif の間の行は削除して構いません.その代わり,テクスチャのレンダリングを行う際にフレームバッファオブジェクトを結合しておきます.そして,glFramebufferTexture2DEXT() を使ってレンダリング先のテクスチャ(キューブマッピングの場合は6枚のうちの1枚)を指定し,ビューポートの設定や画面クリアを行った後,テクスチャのレンダリングを行います.

また FBO ではテクスチャに直接レンダリングするので,レンダリング結果のテクスチャメモリへのコピーは不要になります.これも #if 0 〜 #endif ではさんで無効にするか,削除してしまいます.

最後に,FBO へのレンダリングを解除して,通常のフレームバッファへのレンダリングに戻します.

・・・
 
static void display(void)
{
#if 0
  /* 画面クリア */
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
#endif
  
  /* 透視変換行列の設定 */
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(90.0, (double)TEXWIDTH / (double)TEXHEIGHT, 1.0, 10.0);
  
  /* モデルビュー変換行列の設定 */
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  
  /* フレームバッファオブジェクトへのレンダリング開始 */
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
  
  /* テクスチャの作成 */
  for (int i = 0; i < 6; ++i) {
#if 0
    /* ビューポートをテクスチャのサイズに設定する */
    glViewport(target[i].x, target[i].y, TEXWIDTH, TEXHEIGHT);
#endif
    
    /* レンダリング先のテクスチャを指定する */
    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);
    
    /* 視線の方向を設定して,その向きに見えるものをレンダリング */
    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();
    
#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;
}

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

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

コメント(3) [コメントを投稿する]
Seagul-X 2006年07月19日 13:03

こんにちは。<br>OpenGL召Extension Registry の件、以下のような動きに伴うもののようです。<br><br>http://www.tgdaily.com/2006/07/18/analysis_khronos_and_open_gl_merge/<br><br>SGI のチャプター11申請などもあり、OpenGL を心中させるわけにはいかないと切り離しにかかっているようですね。

とこ 2006年07月20日 19:00

 Seagul-X さま,こんにちは.情報ありがとうございます.実は話題にすることを意識的に避けてはいたのですが,Windows Vista における OpenGL の位置づけといい,3DLabs の撤退といい,そして SGI の チャプター11申請といい,OpenGL に吹く逆風の強さには不安を感じずにはいられませんでした.特に SGI のこの分野へのこれまでの貢献や知的財産が雲散霧消してしまうことは,何としてでも避けてほしいと思っていました.<br> Khronos と OpenGL ARB が合流するんですね.まあ,メンバーが重なっているとは言え,OpenGL|ES が OpenGL ARB と別のところで策定されていたことに私は少し違和感があったので,これはこれで自然な流れだと思います.これからどう展開していくのかは予断を許しませんが,これまでの資産をしっかり保全して頂いて,将来につなげてほしいですね.

Fadis 2007年03月03日 17:44

はじめまして。<br>OpenGLを勉強中ですがここの情報がとても役に立っています。<br>それはさておき...<br>> テクスチャのコピーを行わない分,パフォーマンスは上がるはずですが<br>この部分なんですが、少し前にちょっとした実験をしてみました。<br>1. テクスチャを作って適当な画像を書き込む<br>2. 適当なものをレンダリングしてテクスチャの一部にglCopyTexSubImage2Dする<br>3. glGetTexImageでテクスチャを読み出す<br>するとglCopyTexSubImage2Dした部分には他のプログラムのレンダリング結果などのゴミが表示された状態のテクスチャが読み出されました。<br>おそらくglCopyTexSubImage2Dした時点でテクスチャの一部が他のメモリ領域で置き換えられ、実際にはレンダリング結果自体のコピーは行われていないのだと思います。<br>ハードウェアによって違うことは十分予想されますが、モダンなアクセラレータを使用している場合FBOによってテクスチャのコピーが不要になった分パフォーマンスが上がるか、という点はあまり期待できないと思います。


編集 «3D テクスチャ 最新 シャドウマッピングで FBO を使ってみる»