■ 2008年12月07日 [OpenGL][GLSL] FBO を使ってデプスバッファを表示する
氷河期
昨日は急に寒くなりました.温暖な和歌山市でも雪が舞ってました.学生さんの就職の方にも,また氷河期が来ると言われています.というか,実際来ています.去年と今年でこんなにも差があると,今年就職活動をしている学生さんが可哀想に思えます.なんだかんだ言っても,いろいろ忙しいこの学科でみんなこれまで頑張ってきましたし,ちゃんと自分で考えることができ,デザインもできてプログラムも書けるという有能(便利?)な人材に育ってると思うんですが,やっぱりとても不安なようです.どうか自信を持って,自分を安売りせずに,自分と相性の合った就職先を見つけて欲しいと思います.もっと就職先を開拓しなくっちゃ.
うちはこういう研究室なので,学生さんも割とプログラミングが好きで,ゲームソフトなんかを作りたいと思っている人が集まってきます(と思ってるのは私だけかも…ここんとこ指導教員のヘタレぶりが伝わっているのか人気ないし).でも,全員がそっち方面に就職するわけではなくて,卒業生の話を聞くと Web 系の仕事も多いようです.デザインもできるということもあってか,うちの学科の学生さんはそういうところでも結構重宝されてるみたいですけど,うちの研究室としても Web 関連のテーマも用意した方がいいかなぁ.
それと就職先の方も,学生さんはゲームソフト関連に目を奪われる傾向にありますけど,うちの研究室でやってるような CG の技術を活かせる会社は,他にも電機系や機械系,建設系,医療系などいろいろあるはずなんですよね.最近の携帯やハンドヘルドデバイスを見てると,組み込み系ってところも面白そうです.学生さんにもっといろんな会社や仕事を紹介して,視野を広げてもらわないといけないなぁ.
Frame Buffer Object (FBO) を使う
前回紹介したデプスバッファの内容を表示するプログラムでは,デプスバッファの内容を glReadPixel() により一旦 CPU 側のメモリに読み込んだ後,glDrawPixels() を使って画面に表示していました.この方法は手軽ですが,かなり遅くなってしまいます.そこで今回は,FBO を使ってデプステクスチャに直接奥行きを書き込み,それをポリゴンにマッピングして表示する方法について説明します.
準備
この方法ではテクスチャオブジェクト,フレームバッファオブジェクト,それにシェーダオブジェクトを使うので,それらの識別子を保持する変数を用意します.
/* ** テクスチャの大きさ */ #define TEXWIDTH 512 /* テクスチャの幅 */ #define TEXHEIGHT 512 /* テクスチャの高さ */ /* ** テクスチャオブジェクト・フレームバッファオブジェクト */ static GLuint tex, fb; /* ** シェーダオブジェクト */ static GLuint vertShader; static GLuint fragShader; static GLuint gl2Program;
最初にテクスチャオブジェクトを生成し,デプステクスチャを割り当てます.
/* ** 初期化 */ static void init(void) { GLint compiled, linked; /* シェーダプログラムのコンパイル/リンク結果を得る変数 */ ・・・ /* テクスチャオブジェクトの生成 */ 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); /* テクスチャオブジェクトの結合解除 */ 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);
最後に,シェーダオブジェクトを作成します.
/* GLSL の初期化 */ if (glslInit()) exit(1); /* シェーダオブジェクトの作成 */ vertShader = glCreateShader(GL_VERTEX_SHADER); fragShader = glCreateShader(GL_FRAGMENT_SHADER); /* シェーダのソースプログラムの読み込み */ if (readShaderSource(vertShader, "showdepth.vert")) exit(1); if (readShaderSource(fragShader, "showdepth.frag")) exit(1); /* バーテックスシェーダのソースプログラムのコンパイル */ glCompileShader(vertShader); glGetShaderiv(vertShader, GL_COMPILE_STATUS, &compiled); printShaderInfoLog(vertShader); if (compiled == GL_FALSE) { fprintf(stderr, "Compile error in vertex shader.\n"); exit(1); } /* フラグメントシェーダのソースプログラムのコンパイル */ glCompileShader(fragShader); glGetShaderiv(fragShader, GL_COMPILE_STATUS, &compiled); printShaderInfoLog(fragShader); if (compiled == GL_FALSE) { fprintf(stderr, "Compile error in fragment shader.\n"); exit(1); } /* プログラムオブジェクトの作成 */ gl2Program = glCreateProgram(); /* シェーダオブジェクトのシェーダプログラムへの登録 */ glAttachShader(gl2Program, vertShader); glAttachShader(gl2Program, fragShader); /* シェーダオブジェクトの削除 */ glDeleteShader(vertShader); glDeleteShader(fragShader); /* シェーダプログラムのリンク */ glLinkProgram(gl2Program); glGetProgramiv(gl2Program, GL_LINK_STATUS, &linked); printProgramInfoLog(gl2Program); if (linked == GL_FALSE) { fprintf(stderr, "Link error.\n"); exit(1); } /* テクスチャユニット0を指定する */ glUniform1i(glGetUniformLocation(gl2Program, "depth"), 0); }
デプステクスチャにレンダリング
描画はデプステクスチャを割り当てたフレームバッファオブジェクトに対して行います.そのため現在のビューポートを保存しておき,ビューポートをテクスチャのサイズに設定します.
static void display(void) { GLint viewport[4]; /* ビューポートの保存用 */ /* 現在のビューポートを保存しておく */ glGetIntegerv(GL_VIEWPORT, viewport); /* ビューポートをテクスチャのサイズに設定する */ glViewport(0, 0, TEXWIDTH, TEXHEIGHT);
そして通常の手順でモデルビュー変換行列を設定します(透視変換行列はウィンドウのリサイズ時に設定します).
/* モデルビュー変換行列の設定 */ glLoadIdentity(); /* 視点の位置を設定する(物体の方を奥に移動する)*/ glTranslated(0.0, 0.0, -10.0); /* トラックボール式の回転を与える */ glMultMatrixd(trackballRotation());
図形の描画はフレームバッファオブジェクトに対して行います.
/* フレームバッファオブジェクトへのレンダリング開始 */ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb); /* デプステストを有効にする */ glEnable(GL_DEPTH_TEST); /* デプスバッファをクリアする */ glClear(GL_DEPTH_BUFFER_BIT); /* シーンを描画する */ scene(); /* フレームバッファオブジェクトへのレンダリング終了 */ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
四角形の描画
フレームバッファオブジェクトへの描画が完了したら,得られたテクスチャをマッピングして,ウィンドウいっぱいに1枚の四角形を描画します.この四角形の大きさは,テクスチャ空間の大きさに一致させておきます.また,四角形を1枚描くだけなので,デプステストはオフにします.なお,今回はバーテックスシェーダで行う座標変換に透視変換行列やモデルビュー変換行列を用いないので,ここでそれらを設定する必要はありません.
/* ビューポートを元に戻す */ glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); /* デプステストを無効にする */ glDisable(GL_DEPTH_TEST); /* テクスチャオブジェクトを結合する */ glBindTexture(GL_TEXTURE_2D, tex); /* シェーダプログラムを適用する */ glUseProgram(gl2Program); /* 全画面に四角形を描く */ glBegin(GL_QUADS); glVertex2d(0.0, 0.0); glVertex2d(1.0, 0.0); glVertex2d(1.0, 1.0); glVertex2d(0.0, 1.0); glEnd(); /* シェーダプログラムを解除する */ glUseProgram(0); /* テクスチャオブジェクトの結合を解除する */ glBindTexture(GL_TEXTURE_2D, 0); /* ダブルバッファリング */ glutSwapBuffers(); }
バーテックスシェーダ
バーテックスシェーダでは,頂点位置を座標変換するのに加えて,頂点のローカル座標値をそのままテクスチャ座標に使います.頂点位置は,四角形が正規化デバイス座標系(クリッピング座標系,(-1,-1) と (1,1) を対角の頂点とする正方形)に一致するように座標変換します.
// showdepth.vert void main(void) { gl_TexCoord[0] = gl_Vertex; gl_Position = vec4(gl_Vertex.xy * 2.0 - 1.0, 0.0, 1.0); }
フラグメントシェーダ
フラグメントシェーダでは,サンプリングしたテクスチャの色をそのまま出力します.
// showdepth.frag uniform sampler2D depth; void main(void) { gl_FragColor = texture2D(depth, gl_TexCoord[0].xy); }
デプステクスチャをストレートに拾ってきてますが,結果はちゃんと RGBA 揃ったカラーデータになります.
(Mac OS X 版は,Tiger だとシェーダのコンパイルまでしてくれるのでプログラムは実行できるのですが,うちの Mac mini は GPU が RADEON 9200 なので,実行結果は確認できてません)
床井さんのページにはいつも助かっています。<br>質問なんですが、ある視点からのデプス表示を他の視点から見れるようにするためにはどうしたら良いでしょうか?
サムさま,コメントありがとうございます.<br>デプステクスチャと同じ頂点数をもつメッシュ(ポリゴンを格子状に並べたもの)を準備しておいて,それを描画する際に,バーテックスシェーダでデプステクスチャをサンプリングし (Vertex Texture Fetch),デプス値に合わせて頂点の位置を動かすなどの方法があると思います.