2009年05月30日

影(Shadow)について

何となく床と影ぐらい表示したいなと思いボーンは置いておいて影表示をしてみることにしました。
陰影は両方「カゲ」ですが、陰(Shade)と影(Shadow)でちょっと違います。

光の当たり具合で明るかったり暗かったりする方が陰で、物体に光が遮られてできる黒い形の方が影です。
まぁどうでもいいですね。影を作る場合には、OpenGLではステンシルバッファを利用することが多いようです。たぶん。

ちょっと調べてみると、いつの間にかこのような本が出ていました。1年以上も気づかなかったとは困ったものです。

GLUTによるOpenGL入門2 テクスチャマッピング
http://www.kohgakusha.co.jp/books/detail/978-4-7775-1332-1

恐らく床井先生のブログを見れば、本の内容もある程度載っているのでしょうが、やはり本の方が読みやすそうでいいですね。これを機会にGLSLもちょっと試してみたいですね。

というわけで本は注文しましたが、まだ届いていないので下記ページを参考に試してみたいと思います。

http://artis.inrialpes.fr/Research/RealTimeShadows/pdf/stencil.pdf

実行結果



今日は疲れたので反射までにして、影は次回ということで。



ステンシルバッファの使い方
色々なページがあると思うのでそちらを参考にしてください。

ステンシルバッファとかの初期化

glClearStencil(0); // clear to zero
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
ステンシルバッファの有効化・無効化切替

glEnable(GL_STENCIL_TEST);
glDisable(GL_STENCIL_TEST);


デプステストやアルファテスト、ステンシルテストでは、8つの比較方法を設定することができます。
never, always, less than, less than or equal, greater than, greater than or equal, equal, そして not equal

ステンシルバッファの場合は次のように記述します。
glStencilFunc(GL_EQUAL, // comparison function
0x1, // reference value
0xff); // comparison mask
テストした結果を書き込む方法なども指定することができるようです。
glStencilOp(GL_KEEP, // stencil fail
GL_DECR, // stencil pass, depth fail
GL_INCR); // stencil pass, depth pass
glStencilMask(0xff);
よくわかりませんが。そしてOpenGLではステンシルバッファの読み込み・書き込み・コピーをサポートしています。glReadPixels, glDrawPixels, glCopyPixels関数で。

ステンシルを使わない平面反射
平面のミラーを作るのはOpenGLでは簡単です。


  1. モデルビュー行列を読み込みます。次のように。
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(eye[0], eye[1], eye[2], center[0], center[1], center[2], up[0], up[1], up[2]);

  2. モデルビュー行列をプッシュします。
    glPushMatrix();

  3. Z平面に反射させる行列を掛けます。やり方はZ軸のスケールをマイナスにするだけでいいです。
    glScalef(1.0, 1.0, -1.0);
    任意平面に反射させたい場合は、4x4の行列を作成します。作成の仕方は付録Aに書いてあります。

  4. もし背面消去をしているのであれば、前面消去に切り替えます。反射変換は変換したポリゴンの表/裏姿勢がひっくり返ります。
    glCullFace(GL_FRONT);

  5. シーンを描画しますが、ミラー平面の反射側のオブジェクトだけレンダリングすることに注意してください。ミラーの後ろにあるオブジェクトは、あたかもそれらがミラー平面の前に実際にはあるようにミラーがレンダリングされることによって正確に見えなくなるべきです。

  6. 背面消去に戻して、反射行列も戻します。
    glCullFace(GL_BACK);
    glPopMatrix();

  7. オプションとして、つやを出すために大理石の床や汚れた鏡のように、準反射させることもできます。これはStep 5.で描画したものと床をblend関数で混合することでできます。
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE); // additive blending
    renderMarbleTexturedMirrorSurfacePolygons();
    glDisable(GL_BLEND);
    たとえミラーの表面をブレンドしない場合でも、ミラー表面のポリゴンをデプスバッファにレンダリングすることは、シーンが次のステップでミラーによって塞がれたオブジェクトを再び描画する時に大事です。例:
    glColorMask(0,0,0,0); // disable color buffer updates
    renderMirrorSurfacePolygons(); // update depth buffer with the mirror surface
    glColorMask(1,1,1,1); // re-enable color buffer updates

  8. 最後に、私たちは反射していないシーンをレンダリングします。



恐らく描画関数はこんな感じです。
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glEnable(GL_DEPTH_TEST);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(eye[0], eye[1], eye[2],
center[0], center[1], center[2],
up[0], up[1], up[2]);

// 照明GL_LIGHT1の回転移動
glPushMatrix ();
glRotated ( (GLdouble) spin_light, 0.0, 1.0, 0.0 );
glLightfv ( GL_LIGHT1, GL_POSITION, light_position1 );

// 照明GL_LIGHT1の位置の表示
glDisable ( GL_LIGHTING );
glPointSize ( 5.0 );
glBegin ( GL_POINTS );
glColor3f ( 1.0, 1.0, 1.0 );
glVertex3f ( light_position1[0], light_position1[1], light_position1[2] );
glEnd ();
glPopMatrix ();

glEnable ( GL_LIGHTING );

glPushMatrix();

glScalef(1.0, -1.0, 1.0);
glCullFace(GL_FRONT);

drawVBO();

glCullFace(GL_BACK);
glPopMatrix();

glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE); // additive blending
shadow.drawFloor();
glDisable(GL_BLEND);

drawVBO();

glDisable(GL_LIGHTING);
glDisable(GL_BLEND);

gui->renderGUI();
glutSwapBuffers();
}
この辺りは下記ページのmirror.cが参考になると思います。
http://www.opengl.org/resources/faq/technical/source.htm

ステンシルを使用して平面反射を改良

ステンシルを使用しないと、視点を変えた場合等にただ反射した物体を描画しているだけということがばれてしまいます。ステンシルバッファを使えばこのような問題を解決できます。

先程のアルゴリズムと殆ど同じですが、いくつか修正が必要です。


  1. ステンシルバッファをシーンの最初にクリアする必要があります。

  2. シーン内の各ミラーにおいて、アプリケーションは重ならない部分や共通の平面のポリゴンなど、ミラーの表面を構成するデータ構造を、各ミラー毎に維持する必要があります。標準的な四角いミラーのためには、4つのワールド空間での頂点座標で十分です。下のコードで、thisMirror変数は、現在の鏡のポリゴンセットと平面の式を持っている構造体へのポインタです。

  3. カラー、デプス、ステンシルバッファをクリアします。そして、デプスバッファは使用するがステンシルテストは行わずに、ミラーの表面を除いてシーンを描画します。
    glClearStencil(0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFER_BIT);
    glEnable(GL_DEPTH_BUFFER_BIT);
    glDisable(GL_STENCIL_TEST);
    drawScene(); // draw everything except mirrors

  4. 各ミラーについて、次の処理を実行します。


    1. デプステストをパスしたステンシルバッファに1を書き込んでステンシルを設定します。同時にカラーバッファの書き込みを禁止します。
      glEnable(GL_STENCIL_TEST);
      glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
      glStencilFunc(GL_ALWAYS, 1, ~0);
      glColorMask(0,0,0,0);
      renderMirrorSurfacePolygons(thisMirror);
      このステップでは、すべてのミラーの可視ピクセルのステンシル値を1に「タグ」付けます。

    2. カラーバッファ書き込みはまだ不可にしたままで、すべての更新したピクセルのために最も遠い値をデプスレンジに設定し、デプステストをいつもパスするように設定します。また、ステンシルテストは、ステンシル値が1にタグ付けられたピクセルだけを更新するように設定します。その後、ミラーのポリゴンを描画します。
      glDepthRange(1,1); // always
      glDepthFunc(GL_ALWAYS); // write the farthest depth value
      glStencilFunc(GL_EQUAL, 1, ~0); // match mirror’s visible pixels
      glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // do not change stencil values
      renderMirrorSurfacePolygons(thisMirror);
      このステップでは、すべてのミラーの可視ピクセルのために、デプスバッファをそのクリアされた最大値にリセットします。

    3. デプステスト、カラーマスク、そしてデプスレンジを標準的な設定に戻します。
      glDepthFunc(GL_LESS);
      glColorMask(1,1,1,1);
      glDepthRange(0,1);
      私たちは、反射自身をレンダリングする準備ができました。反射に属するピクセルは、またステンシル値が1にタグ付けられたままです。そして、それらのピクセルは、デプス値が最も遠い値にクリアされています。ステンシルテストは、例えば可視ミラー上のピクセルのように、ステンシル値が1のピクセルだけを更新できるようにします。そして「less than」デプステストは、ミラーピクセルへのレンダリングが適切に可視表面を決定することを確実します。

    4. 反射のレンダリングには、鏡面を通したシーンを反射する必要がありますが、私たちはミラー側に反射したオブジェクトだけをレンダリングする必要があります。したがって、私たちは、鏡面の反射側上のオブジェクトだけをレンダリングするために、ユーザが定義したクリップ平面を確立します。反射自身は、モデルビュー行列に適切な反射行列を掛けることによって実行されるので、すべてが鏡面を通じて反射されます。反射はポリゴンの面の表裏がひっくり返るので、面のカリングの状態も反対になります。そしてシーンをレンダリングします。
      glPushMatrix();
      // returns world-space plane equation for mirror plane to use as clip plane
      computeMirrorClipPlane(thisMirror, &clipPlane[0]);
      // set clip plane equation
      glClipPlane(GL_CLIP_PLANE0, &clipPlane);
      // returns mirrorMatrix (see Appendix A) for given mirror
      computeReflectionMatrixForMirror(thisMirror, &matrix[0][0]);
      // concatenate reflection transform into modelview matrix
      glMultMatrixf(&matrix[0][0]);
      glCullFace(GL_FRONT);
      drawScene(); // draw everything except mirrors
      drawOtherMirrorsAsGraySurfaces(thisMirror); // draw other mirrors as
      // neutral “gray” surfaces
      glCullFace(GL_BACK);
      glDisable(GL_CLIP_PLANE0);
      glPopMatrix();
      これでミラーの反射は正しくレンダリングされました。この鏡の反射で他の可視のミラーはグレーにレンダリングされるので、それらが正しくオブジェクトを反射しなくても、それらの後ろで適切にオブジェクトを塞ぎます。それに続いて、私たちは反射の反射を扱う再帰的なアルゴリズムをスケッチします。

    5. 最後に、私たちは別のミラーの反射をレンダリングしている間に、別のミラーのピクセルが混乱しないようにすべてのミラーのピクセルのステンシル値を0にリセットします。同様に、デプスバッファを更新するので、このミラーが別のミラーを塞ぐかもしれません。このステップではカラーバッファの更新はしません。
      glColorMask(0,0,0,0);
      glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
      glDepthFunc(GL_ALWAYS);
      renderMirrorSurfacePolygons(thisMirror);
      glDepthFunc(GL_LESS);
      glColorMask(1,1,1,1);
      カラーバッファを更新しない代わりに、以前議論したように、大理石のような表面をシミュレーションするために準反射としてブレンドすることができます。




うーむ、ここの部分は合わせ鏡のようなものを作る方法のようですが、よくわかりませんね。

ちなみにここの部分はGLUTのソースコードバージョンにサンプルとしてソースコードが入っています。
この辺からソースコードバージョンをダウンロードしてみてください。

http://www.opengl.org/resources/libraries/glut/glut_downloads.php

progs/advanced97のmultimirror.c等がサンプルです。

でも結局よくわからないのでBulletデモのappBulletDinoを参考にしました。困ったものです。

posted by シンドラー at 00:39 | Comment(0) | TrackBack(0) | OpenGL & Metasequoia | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
※ブログオーナーが承認したコメントのみ表示されます。

この記事へのトラックバック