OpenGLでオフスクリーンレンダリング: framebuffer object

※OpenGLに関連する項目は 別ページ に整理しました。ここにあるものは内容が古い場合があります。

../fbotest.jpg

(サンプルは一番最後にあります)

目標

OpenGLにはこれまで、プラットフォームに依存しないテクスチャへ直接レンダリングを行う手法は 存在していませんでしたが、framebuffer object が規定され、ウィンドウ以外への オフスクリーンレンダリングが可能になったことで、テクスチャへのレンダリングも プラットフォームに依存せず行えるようになりました。 ここでは framebuffer object の概要について記述します。

framebuffer object とは

framebuffer object は、OpenGLでプラットフォームに依存せず オフスクリーンレンダリングを実現する機構です。 OpenGL 2.0 の正式仕様には取り込まれていません。 OpenGL拡張 EXT_framebuffer_object として規定されています。

従来のフレームバッファは、実際の表示ウィンドウと密接に関連付けられて おり、OSが提供していました。 フレームバッファは、

  • カラーバッファ (一般的には複数枚存在します: ステレオ用・ダブルバッファ用・補助バッファ(AUX))
  • デプスバッファ
  • ステンシルバッファ
  • アキュームレーションバッファ

の組から成っており、これらは論理バッファ(logical buffer)と呼ばれます。

framebuffer object は、従来のフレームバッファと同等の論理バッファの組を、 表示ウィンドウと別個に保持し、それに対してレンダリングすることを可能とします。 複数のカラーバッファ、デプスバッファ、ステンシルバッファを扱うことが できます。アキュームレーションバッファは現時点では使用できないようです。

それぞれのバッファは、単純な2次元のピクセル列であり、texture もしくは framebuffer と共に新たに導入される renderbuffer と関連付けて 使用します。 すなわち、テクスチャへのレンダリング、もしくは、renderbuffer へのレンダリングが可能になる、ということです。 texture および renderbuffer は framebuffer-attachable と呼ばれます。 texture および renderbuffer に含まれる2次元ピクセルの配列を framebuffer-attachable image と呼びます。

注意すべき点として、framebuffer と renderbuffer は明確に区別しておく必要が あると思います。名前から受ける印象が似ているため私は混乱しました。 renderbuffer の実体は2次元ピクセル配列であり texture buffer と同じ 階層で取り扱われるバッファです。framebuffer は複数の論理バッファを統合する より抽象的なデータ構造であると考えればよいと思います。登場するバッファを 整理すると図のようになります。

../fbo.jpg

framebuffer object を使う手順

下準備編

  1. framebuffer object 内の各バッファに割り当てる texture もしくは renderbuffer を準備します。
  2. framebuffer object を作成します。
  3. framebuffer object の各論理バッファに texture もしくは renderbuffer を 対応付けます(attach)。

レンダリング編

  1. レンダリング先を framebuffer object に変更します。
  2. 視点(modelview matrix), 投影(projection matrix), スクリーン(viewport) の設定をします。 デフォルトのフレームバッファと framebuffer object の間で 行列スタックや光源などは共通です。 デフォルトのフレームバッファの設定はどこかに退避しておくのが よいと思います。
  3. 描画します。 論理バッファに対応付けた texture もしくは renderbuffer に 書き込まれます。 描画関数を呼び終えたら glFlush() を実行しておきます(フレームバッファを 切り替えると自動的に描画が実行されるので必須ではないようです)。
  4. レンダリング先をデフォルトのフレームバッファに戻します。

利用編

  1. texture の利用法は特に変わりありません。
  2. renderbuffer の利用法はいまひとつ不明です。

framebuffer object 実践編

次のようなサンプルを構築します。

  1. 回転するティーポットを texture にレンダリングする
  2. その texture を立方体の各面に貼り付けてレンダリングする。

マウスの左クリックで立方体の回転を、右クリックでティーポットの回転を それぞれOn/Offします。


サンプルプログラム解説

本サンプルでは、カラーバッファに texture を、デプスバッファに renderbuffer を割り当てます。

まず、各バッファの識別子を格納するためのGLuint型の変数を宣言します:

GLuint  texture_name;
GLuint  renderbuffer_name;
GLuint  framebuffer_name;

texture の初期化は以下の通りです。通常と異なる部分は、 texture に登録する画像が必要なく、バッファの確保のみを行えば よい点です。glTexImage2D の最後の引数に 0 を渡すことでバッファの確保のみを 行えるようです。OpenGL仕様内には記述を見つけることが できませんでしたがウェブ上のサンプルプログラムには散見されるのでよしとします:

void
InitTexture( void )
{
    glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
    glGenTextures( 1, &texture_name );
    glBindTexture( GL_TEXTURE_2D, texture_name );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
    glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, TEXTURE_WIDTH, TEXTURE_HEIGHT,
                  0, GL_RGBA, GL_UNSIGNED_BYTE, 0 );
}

renderbuffer の扱いは texture とほとんど同じです。 renderbuffer の確保は glGenRenderbuffersEXT で行います。 使い方は glGenTextures と同じです。 生成した renderbuffer に対して設定を行いますが、 以後の設定の対象となる renderbuffer を glBindRenderbufferEXT で 指定します。これも texture の場合と同じです。 glRenderbufferStorageEXT でバッファの型とサイズを指定します。 グラフィクスRAMの上に2次元配列をmallocすることに相当します。 texture の場合にはパラメータの設定をいくつか行いますが、 物体に貼られることのない renderbuffer には必要ありません:

void
InitRenderbuffer( void )
{
    glGenRenderbuffersEXT( 1, &renderbuffer_name );
    glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, renderbuffer_name );
    glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT,
                              RENDERBUFFER_WIDTH, RENDERBUFFER_HEIGHT );
}

renderbuffer の型として、ここでは、デプスバッファとして 用いるために GL_DEPTH_COMPONENT を指定しています。 カラーバッファとして用いるならば GL_RGBA などを指定します。 指定できる型は、specification には color-renderable, depth-renderable, stencil-renderable と 書かれています。glDrawPixels などで使用できるフォーマットと 思っておけばよいでしょう。

カラーバッファおよびデプスバッファに割り当てるバッファを 確保したら、framebuffer object を生成します。

glGenFramebuffersEXT は texture などと同じ一般的なOpenGLの作法で framebuffer の識別子を確保します。続いて glBindFramebufferEXT で 以下の設定を適用するバッファを指定するのも同じです。

framebuffer 内の各論理バッファに、どの texture や renderbuffer を割り当てるかを指定する関数が、 glFramebuffer*EXT です。* の部分に割り当て元のバッファの 種類が入ります。texture を割り当てるなら glFramebufferTexture2DEXT を、 renderbuffer を割り当てるなら glFramebufferRenderbufferEXT を使います。 3次元 texture の割り当ても行えます。その場合には割り当てる平面の z 座標を指定します。 割り当て先は、第2引数で指定します。色バッファなら GL_COLOR_ATTACHMENT?_EXT を、デプスバッファなら GL_DEPTH_ATTACHMENT を 指定します。色バッファは複数割り当てることができるのでGLSLと組み合わせると multi render target を実現できます。 割り当てが終わったら、無用のバグの発生を防止するために、 デフォルトのフレームバッファに戻しておくのがよいと思います。 glBindFramebufferEXT の引数に 0 を入れて呼び出します:

void
InitFramebuffer( void )
{
    glGenFramebuffersEXT( 1, &framebuffer_name );
    glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, framebuffer_name );

    glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                               GL_TEXTURE_2D, texture_name, 0 );
    glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
                                  GL_RENDERBUFFER_EXT, renderbuffer_name );

    glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
}

さて、実際の framebuffer object へのレンダリングを行うわけですが、 難しい点は何もありません:

glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, framebuffer_name );

で先程設定を行った framebuffer へと書き込み先を変更します。

その後の処理は通常のレンダリングと全く変わりありません。 glut で double buffering を用いている場合は glutSwapBuffers() を 最後に実行しますが、オフスクリーンレンダリングの場合は glFlush() を 実行します。ただし、glBindFramebufferEXT でレンダリング先を 切り替えると glFlush と同等の処理がなされるようですので趣味の範疇です。

OpenGL の様々なコンテキストはフレームバッファ間で共通です。 viewport や各行列の設定、更にはライティングなどの設定も共有されています。 デフォルトのフレームバッファの視野に関する設定を、 以下のような関数を作成して保存しておきます。デフォルトの フレームバッファも含めてクラス化するのが筋だと思います:

GLuint  viewport[ 4 ];

void
SaveFramebufferStatus( void )
{
    glGetIntegerv( GL_VIEWPORT, viewport );
    glMatrixMode( GL_PROJECTION );
    glPushMatrix();
    glMatrixMode( GL_MODELVIEW );
    glPushMatrix();
}

void
RestoreFramebufferStatus( void )
{
    glViewport( viewport[ 0 ], viewport[ 1 ], viewport[ 2 ], viewport[ 3 ] );
    glMatrixMode( GL_PROJECTION );
    glPopMatrix();
    glMatrixMode( GL_MODELVIEW );
    glPopMatrix();
}

void
RenderToTexture( void )
{
    /* switch to framebuffer object */

    SaveFramebufferStatus();
    glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, framebuffer_name );

    :

    /* execute drawing */

    glFlush();

    /* switch to default buffer */

    glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
    RestoreFramebufferStatus();
}

サンプルプログラムを以下に置いておきます。GLEWを導入しておく必要があります。

http://chihara.naist.jp/people/STAFF/imura/computer/OpenGL/fbotest.lzh


補足

framebuffer object が正しく設定されている(complete)ことを確認するには、 glCheckFramebufferStatusExt を使います:

if ( glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT )
     != GL_FRAMEBUFFER_COMPLETE_EXT ) {
  cerr << "framebuffer is not complete" << endl;
}

疑問点

framebuffer object に対して、ステンシルバッファの指定を行っていませんが、 特に問題は起こっていません。本サンプルではステンシルバッファを 利用していないからかもしれません。 特に指定しなければデフォルトのフレームバッファと共有するのでしょうか。 デフォルトのフレームバッファと framebuffer object とで サイズが異なる場合に、バッファのリサイズ(再確保)による 速度低下が起こるのではないかと懸念していますが確かめていません。


参考

Astle, D: More OpenGL Game Programming