«第26回 レンダリング画像をテクスチャに使う 最新 第1回 シェーダプログラムの読み込み»

床井研究室


■ 2005年09月26日 [OpenGL][テクスチャ] 第27回 シャドウマッピング

2013年08月20日 12:41更新

1年もかかってしまった

実はこの blog でテクスチャマッピングのシリーズを書き始めた頃に,ある人に「そのうち影付けの方法について説明を書く」と約束していました.ところが,ここにたどり着くまでに,結局丸1年かかってしまいました.面目ありません.でも,OpenGL 1.4 で標準機能に取り込まれたこの機能自体,それまでの OpenGL の機能拡張の集大成とも思える部分もあります.

実際これ以降の機能拡張は,キューブマッピングやシャドウマッピングのような固定機能の機能追加から,プログラマブルシェーダによる機能増強に重点が移っています.シャドウマッピングを実装した時点で,こういうスタイルでの機能追加は,もう限界に来たということかも知れません.

というわけで,今回もサンプルプログラムをもとに解説します.なおこのプログラムは,ビデオカードが OpenGL 1.4 以降をサポートしていなければ,正常に動作しません.

影付け処理

影の存在は単にシーンのリアリティを向上するだけでなく,オブジェクトやキャラクタの「接地感」の表現や,オブジェクト同士の相対的な位置関係の把握などに重要な役割を果たします.しかし,影付け処理は比較的コストの高い処理のため,しばしばこれはレンダリングのパフォーマンスとのトレードオフとなります.

影付け処理の手法には,あらかじめテクスチャに影を「焼き込む」ライトマップや,影をテクスチャとして投影するプロジェクティブシャドウ,シャドウボリューム法をステンシルバッファを使って効率的に処理するステンシルシャドウ,それに今回説明するシャドウマップ法など,様々なものが提案されています.このうちシャドウマップ法はセルフシャドウ(凹部をもつオブジェクトが自分自身に落とす影)も正しく生成できるなど,影の生成における制限の少ない方法ですが,効率の面からそのほかの方法もよく使用されます.影付け処理はベストな方法が存在するというものではなく,色々な手法を適材適所で使い分けるというのが一般的なようです.

シャドウマッピング法

シャドウマッピング法はテクスチャマッピングの機能を使って影付け処理を実現する手法です.

シャドウマッピング

以下にこの手順の概略を示します.

  1. 上図において視点から見た図形をレンダリングする際,図形の表面上のある点(参照点と呼ぶことにします)が影の領域に入っていなければ日向,影の領域に入っていれば影として,その点の陰影を求めます.この処理を行うためには,参照点のワールド座標形状での位置を求める必要があります.これはテクスチャ座標の自動生成を行うことによって,テクスチャ座標として得ることができます.
  2. 次に,参照点が影の領域にあるかどうかを判定するために,参照点のテクスチャ座標を光源位置に視点を置いた空間上に座標変換するよう,テクスチャ変換行列を設定します.こうして光源位置に視点を置いた空間における参照点の座標値を,テクスチャ座標値として得ます.
  3. 最後に,参照点が光源から見えるかどうかを判定します.参照点が光源から見えていれば,参照点は日向になります.参照点が他のオブジェクトに隠されている場合は,参照点は影になります.したがって,この判定は隠面消去処理と同じです.
  4. そこで,あらかじめ光源位置に視点を置いてレンダリングし,そのデプスバッファの内容をテクスチャメモリに転送しておきます.そして通常のテクスチャマッピングと同様に,参照点のテクスチャ座標を用いてこのテクスチャをサンプリングします.そしてサンプリングされた値(Z値)とテクスチャ座標のZ成分を比較し,テクスチャ座標のZ成分の方が小さければ参照点を日向とします.
以降,次のようなシーンに対して影付け処理を追加する方法について説明します(ファイル main1.cpp).
元のシーン

デプステクスチャ

テクスチャの画素の値として,色ではなく奥行き値を保持しているものを,テプステクスチャと呼びます.デプステクスチャは隠面消去処理に用いるデプスバッファの内容として得られます.テクスチャとしてデプステクスチャを用いるには,glTexImage2D() の target と format に GL_DEPTH_COMPONENT を指定します.

・・・
 
/*
** 初期化
*/
static void init(void)
{
  /* テクスチャの割り当て */
  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);

デプステクスチャは色の情報ではないので,マッピングしたときも色としては用いられません.そのかわり,ある画素にマッピングされた値は,その画素におけるテクスチャ座標との比較に用いることができます.ここではテクスチャ座標 (S T R Q) によってサンプリングされたテクスチャの値と,そのテクスチャ座標の R 成分との比較を行うよう設定します.

  /* 書き込むポリゴンのテクスチャ座標値のRとテクスチャとの比較を行うようにする */
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
  
  /* もしRの値がテクスチャの値以下なら真(つまり日向) */
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);

GL_TEXTURE_COMPARE_FUNC に指定するテクスチャの比較方法には,OpenGL 1.4 では GL_GEQUAL と GL_LEQUAL が指定できます.ちなみに OpenGL 1.5 以降では,他に GL_NEVER, GL_LESS, GL_EQUAL, GL_NOTEQUAL, GL_GREATER, および GL_ALWAYS が指定できます.

この比較の結果を GL_LUMINANCE(輝度値)としてポリゴンにマッピングします.この輝度値は,比較の結果が真なら1,偽なら0となるので,この値によって下地の色が変調されて,真なら下地の色がそのまま現れ,偽なら黒がマッピングされることになります.

  /* 比較の結果を輝度値として得る */
  glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE);

あとはテクスチャ座標の自動生成の設定をします.テクスチャ座標は視点座標系で生成し,それがそのまま (S T R Q) に用いられるようにします.

  /* テクスチャ座標に視点座標系における物体の座標値を用いる */
  glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
  glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
  glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
  glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
  
  /* 生成したテクスチャ座標をそのまま (S, T, R, Q) に使う */
  static const GLdouble genfunc[][4] = {
    { 1.0, 0.0, 0.0, 0.0 },
    { 0.0, 1.0, 0.0, 0.0 },
    { 0.0, 0.0, 1.0, 0.0 },
    { 0.0, 0.0, 0.0, 1.0 },
  };
  glTexGendv(GL_S, GL_EYE_PLANE, genfunc[0]);
  glTexGendv(GL_T, GL_EYE_PLANE, genfunc[1]);
  glTexGendv(GL_R, GL_EYE_PLANE, genfunc[2]);
  glTexGendv(GL_Q, GL_EYE_PLANE, genfunc[3]);
  
  ・・・

デプステクスチャの作成

デプステクスチャは光源位置から見たシーンをレンダリングして作成します.そのためにはレンダリング画像をテクスチャに使うテクニックを用いる必要があります.光源位置から見たシーンをレンダリングした後,視点位置から見てレンダリングするので,モデルビュー変換行列や透視変換行列,それにビューポートなどを一旦保存しておく配列を用意しておきます.このほか,アニメーションに用いる時間の変数も求めておきます.

・・・
 
/* アニメーションのサイクル */
#define FRAMES 600
 
static void display(void)
{
  GLint viewport[4];       /* ビューポートの保存用     */
  GLdouble modelview[16];  /* モデルビュー変換行列の保存用 */
  GLdouble projection[16]; /* 透視変換行列の保存用     */
  static int frame = 0;    /* フレーム数のカウント     */
  double t = (double)frame / (double)FRAMES; /* 経過時間  */
  
  if (++frame >= FRAMES) frame = 0;

デプステクスチャを作成する場合は,フレームバッファは用いないので,デプスバッファのみをクリアしておきます.またビューポートはテクスチャのサイズに,透視変換行列は単位行列(つまり無変換)に設定しておきます.

  /*
  ** 第1ステップ:デプステクスチャの作成
  */
  
  /* デプスバッファをクリアする */
  glClear(GL_DEPTH_BUFFER_BIT);
  
  /* 現在のビューポートを保存しておく */
  glGetIntegerv(GL_VIEWPORT, viewport);
  
  /* ビューポートをテクスチャのサイズに設定する */
  glViewport(0, 0, TEXWIDTH, TEXHEIGHT);
  
  /* 現在の透視変換行列を保存しておく */
  glGetDoublev(GL_PROJECTION_MATRIX, projection);
  
  /* 透視変換行列を単位行列に設定する */
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

モデルビュー変換行列には,視点を光源位置に置き,シーン全体が視野に収まるよう透視変換と視野変換を設定します.後で透視変換と視野変換の合成変換が必要になるので,透視変換行列は使用せずに,モデルビュー変換行列に透視変換と視野変換を合成しておきます.ここでは陰影付けを行わないので,このようにしても問題ありません.また,求めたモデルビュー変換行列の内容を保存しておきます.

  /* 光源位置を視点としシーンが視野に収まるようモデルビュー変換行列を設定する */
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluPerspective(40.0, (GLdouble)TEXWIDTH / (GLdouble)TEXHEIGHT, 1.0, 20.0);
  gluLookAt(lightpos[0], lightpos[1], lightpos[2], 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
  
  /* 設定したモデルビュー変換行列を保存しておく */
  glGetDoublev(GL_MODELVIEW_MATRIX, modelview);

あとはフレームバッファへの書き込みを禁止し,陰影付けもオフにしておきます.背面ポリゴンを描画するようにしているのは,影の領域がオブジェクトの(光源に対する)背面から始まるようにするためです.こうしないと影の領域がオブジェクトの光の当たっている面と重なってしまい,影が日向の領域にはみ出てしまうことがあります.

  /* デプスバッファの内容だけを取得するのでフレームバッファには書き込まない */
  glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
  
  /* したがって陰影付けも不要なのでライティングをオフにする */
  glDisable(GL_LIGHTING);
  
  /* デプスバッファには背面のポリゴンの奥行きを記録するようにする */
  glCullFace(GL_FRONT);
  
  /* シーンを描画する */
  scene(t);

この結果のデプスバッファの内容をテクスチャメモリに転送します.こうしてシーンを光源から見た時の奥行きがデプステクスチャに得られます.

  /* デプスバッファの内容をテクスチャメモリに転送する */
  glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, TEXWIDTH, TEXHEIGHT);

描画の方法を通常に戻します.ビューポートと透視変換行列を元に戻し,フレームバッファへの描画と陰影付けを有効にします.また,前面ポリゴンを描画するようにします.

  /* 通常の描画の設定に戻す */
  glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
  glMatrixMode(GL_PROJECTION);
  glLoadMatrixd(projection);
  glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
  glEnable(GL_LIGHTING);
  glCullFace(GL_BACK);

視点位置から見た図形の描画

次に,視点位置から見た図形の描画を行います.モデルビュー変換行列には,視点位置の設定とトラックボール式の回転を与えます.

  /*
  ** 第2ステップ:全体の描画
  */
  
  /* フレームバッファとデプスバッファをクリアする */
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
  /* モデルビュー変換行列の設定 */
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  
  /* 視点の位置を設定する(物体の方を奥に移動する)*/
  glTranslated(0.0, 0.0, -10.0);
  
  /* トラックボール式の回転を与える */
  glMultMatrixd(trackballRotation());
  
  /* 光源の位置を設定する */
  glLightfv(GL_LIGHT0, GL_POSITION, lightpos);

描画するシーンには,デプステクスチャをマッピングします.そのために,テクスチャ変換行列の設定を行います.なお,以下の説明に対して,その下のプログラムの実行順序は逆になっています.

自動生成したテクスチャ座標値はワールド座標系におけるオブジェクトの座標値です.まずこれに,デプステクスチャを生成したときに用いた座標変換に含まれていない,現在のモデルビュー変換の逆変換を行います.これは現在のモデルビュー変換行列を取り出して逆行列を求めても構わないのですが,ここでは実際に行った変換の逆変換を用いています.

これでデプステクスチャを生成したときのオブジェクトのワールド座標値が得られますから,これに保存しておいた光源位置に視点を置いたときのモデルビュー変換と透視変換の合成変換を施して,光源位置を視点とするスクリーン上の座標値に変換します.

こうして得られた座標値は,正規化デバイス座標系(クリッピング座標系)の2点 (-1, -1),(1, 1) を結ぶ線分を対角線とする領域内にあるので,これを2点 (0, 0),(1, 1) を結ぶ線分を対角線とするテクスチャ空間の領域に収める変換を行います.

  /* テクスチャ変換行列を設定する */
  glMatrixMode(GL_TEXTURE);
  glLoadIdentity();
  
  /* テクスチャ座標の [-1,1] の範囲を [0,1] の範囲に収める */
  glTranslated(0.5, 0.5, 0.5);
  glScaled(0.5, 0.5, 0.5);
  
  /* テクスチャのモデルビュー変換行列と透視変換行列の積をかける */
  glMultMatrixd(modelview);
  
  /* 現在のモデルビュー変換の逆変換をかけておく */
  glMultTransposeMatrixd(trackballRotation());
  glTranslated(0.0, 0.0, 10.0);

テクスチャ変換行列の設定が終わったら,設定対象の変換行列をモデルビュー変換行列に戻し,テクスチャマッピングを有効にしてシーンを描画します.

  /* モデルビュー変換行列に戻す */
  glMatrixMode(GL_MODELVIEW);
  
  /* テクスチャマッピングとテクスチャ座標の自動生成を有効にする */
  glEnable(GL_TEXTURE_2D);
  glEnable(GL_TEXTURE_GEN_S);
  glEnable(GL_TEXTURE_GEN_T);
  glEnable(GL_TEXTURE_GEN_R);
  glEnable(GL_TEXTURE_GEN_Q);
  
  /* 光源の明るさを日向の部分での明るさに設定 */
  glLightfv(GL_LIGHT0, GL_DIFFUSE, lightcol);
  glLightfv(GL_LIGHT0, GL_SPECULAR, lightcol);
  
  /* シーンを描画する */
  scene(t);
  
  /* テクスチャマッピングとテクスチャ座標の自動生成を無効にする */
  glDisable(GL_TEXTURE_GEN_S);
  glDisable(GL_TEXTURE_GEN_T);
  glDisable(GL_TEXTURE_GEN_R);
  glDisable(GL_TEXTURE_GEN_Q);
  glDisable(GL_TEXTURE_2D);
  
  /* ダブルバッファリング */
  glutSwapBuffers();
}
 
・・・

これで,次のような影が得られます.

影をつけたシーン

見てのとおり,この影は真っ黒で,とても不自然です.また,球の光源に対する裏面も,なんだか汚いことになっています.これは物体の光源に対する背面から始まる影の領域と,物体表面が重なっているために起こります.

影と日向の合成

デプステクスチャによって直接輝度値を制御すると,影の部分の陰影が失われてしまいます.これを防ぐ方法はいくつかあるように思いますが,ここではデプステクスチャとの比較の結果をアルファ値に反映し,アルファテストを使って影と日向の部分を別々にレンダリングして合成する手法について説明します(ファイル main2.cpp).

まず,比較の結果を輝度値 (GL_LUMINANCE) として得る代わりに,アルファ値 (GL_ALPHA) として得ます.また,アルファテストのしきい値を設定しておきます.

/*
** アルファテスト
*/
#define USEALPHA 1                                        /* アルファテスト使用 */
 
/*
** 初期化
*/
static void init(void)
{
  ・・・
  
  /* 書き込むポリゴンのテクスチャ座標値のRとテクスチャとの比較を行うようにする */
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
  
  /* もしRの値がテクスチャの値以下なら真(つまり日向) */
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
  
#if USEALPHA
  /* 比較の結果をアルファ値として得る */
  glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_ALPHA);
  
  /* アルファテストの比較関数(しきい値) */
  glAlphaFunc(GL_GEQUAL, 0.5f);
#else
  /* 比較の結果を輝度値として得る */
  glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE);
#endif
  
  ・・・

こうしてアルファテストを有効にして描画すると,日向の部分だけを描画することができます.

日向の部分だけを描画

そこで,日向の部分を描く前に,光源の明るさを落としてシーン全体を描きます.このとき影の部分にハイライトが発生しないよう,光源強度の鏡面反射成分は0にしておきます.

static void display(void)
{
  ・・・
  
  /*
  ** 第2ステップ:全体の描画
  */
  
  /* フレームバッファとデプスバッファをクリアする */
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
  /* モデルビュー変換行列の設定 */
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  
  /* 視点の位置を設定する(物体の方を奥に移動する)*/
  glTranslated(0.0, 0.0, -10.0);
  
  /* トラックボール式の回転を与える */
  glMultMatrixd(trackballRotation());
  
  /* 光源の位置を設定する */
  glLightfv(GL_LIGHT0, GL_POSITION, lightpos);
  
#if USEALPHA
  /* 光源の明るさを影の部分での明るさに設定 */
  glLightfv(GL_LIGHT0, GL_DIFFUSE, lightdim);
  glLightfv(GL_LIGHT0, GL_SPECULAR, lightblk);
  
  /* シーンを描画する */
  scene(t);
#endif
  
  ・・・

そしてアルファテストを有効にして,日向の部分を描きます.日向の部分は先に描いた光を落としたシーンに重ねるので,奥行きが一致した部分は後から描いたものが反映されるように,奥行きの比較関数を変更しておきます.

  /* テクスチャマッピングとテクスチャ座標の自動生成を有効にする */
  glEnable(GL_TEXTURE_2D);
  glEnable(GL_TEXTURE_GEN_S);
  glEnable(GL_TEXTURE_GEN_T);
  glEnable(GL_TEXTURE_GEN_R);
  glEnable(GL_TEXTURE_GEN_Q);
  
#if USEALPHA
  /* アルファテストを有効にして影の部分だけを描画する */
  glEnable(GL_ALPHA_TEST);
  
  /* 日向の部分がもとの図形に重ねて描かれるように奥行きの比較関数を変更する */
  glDepthFunc(GL_LEQUAL);
#endif
  
  /* 光源の明るさを日向の部分での明るさに設定 */
  glLightfv(GL_LIGHT0, GL_DIFFUSE, lightcol);
  glLightfv(GL_LIGHT0, GL_SPECULAR, lightcol);
  
  /* シーンを描画する */
  scene(t);
  
#if USEALPHA
  /* 奥行きの比較関数を元に戻す */
  glDepthFunc(GL_LESS);
  
  /* アルファテストを無効にする */
  glDisable(GL_ALPHA_TEST);
#endif
  
  /* テクスチャマッピングとテクスチャ座標の自動生成を無効にする */
  glDisable(GL_TEXTURE_GEN_S);
  glDisable(GL_TEXTURE_GEN_T);
  glDisable(GL_TEXTURE_GEN_R);
  glDisable(GL_TEXTURE_GEN_Q);
  glDisable(GL_TEXTURE_2D);
  
  /* ダブルバッファリング */
  glutSwapBuffers();
}
 
・・・

これで,次のように影の部分のオブジェクトにも陰影をつけることができます.

影の部分に陰影をつけて描画

なお,影の部分もアルファテストで切り抜いて合成する方法もあります(ファイル main3.cpp).こっちのほうがプログラムがきれいなんですけど,パフォーマンスは悪いでしょうね.

(ああ,くたびれた)

コメント(23) [コメントを投稿する]
fuki 2008年11月12日 20:49

いつも参考にさせていただいています。<br>現在シャドウマップとテクスチャをはったオブジェクトを混在させることに頭を悩ませています。<br>テクスチャメモリが上書きされてしまうのはglCopyTexImage2Dのせいなのでしょうか?<br>駄文ですみませんが、ポイントかなにかあれば返信お願いします。

とこ 2008年11月18日 11:14

 返事がとても遅くなってしまって申し訳ありません,コメントがあったことに全然気づいておりませんでした.<br> テクスチャを貼ったオブジェクトにシャドウマッピングで影を落とすには,オブジェクトのテクスチャとデプステクスチャをマルチテクスチャを使って別々のテクスチャユニットに保持する必要があると思いますが,その点いかがでしょうか.

fuki 2008年11月25日 18:09

返信ありがとうございます。<br>マルチテクスチャやったことありませんでした。<br>調べて挑戦してみます。

zun 2009年04月15日 17:44

第15回のキューブマッピングでご質問させて頂いた者です。<br>シャドウマッピングでご質問があるのですが、直方体を<br>床の上に置いた状態にすると、直方体の影が正しく表示<br>されないようなのですが、これはなにか原理的な問題が<br>あるのでしょうか?<br><br>お忙しいところ申し訳ございませんが,ご回答頂ければ<br>幸いです。

とこ 2009年04月16日 09:00

試してみました.scene.cpp で,<br><br> /* 箱を描く */<br> glPushMatrix();<br> glTranslated(-1.0, -2.0, -1.0);<br> box(2.0, 1.0, 2.0);<br> glPopMatrix();<br><br>として箱を床にくっつけてみましたけど,影がおかしくなっているようには見えませんでした.床にめり込ませてみても同様でした.<br> シャドウマッピングは,原理的にオブジェクトの形には依存しない手法だと思いますが,影はどのように表示されたのでしょうか.

とこ 2009年04月16日 09:05

 箱を床の下に持っていくと,床の影は箱に落ちませんね.床は閉じた形状ではなく,背面ポリゴンを持っていないので,この手法では床は影を落としませんね.

zun 2009年04月16日 10:31

床井先生<br><br>ご回答ありがとうございます。先生のおっしゃる通り、<br>この例で箱を床に置いたときはうまくいくのですが、<br>OpenGL入門の12章の2つの立方体の例をこのシャドウ<br>マッピングに組み込んだときに、箱が回転する際に、<br>箱の位置によってうまく影が表示されない場合が<br>ありましたので、ご質問させて頂いた次第です。<br><br>もしご迷惑でなければプログラムをお送りさせて頂いても<br>よろしいでしょうか?

とこ 2009年04月16日 11:05

 はい,メールに添付して送っていただくか,適当なところにアップロードしていただければ取りに参ります.ただ,ちょっと気になったんですが,2つの立方体は,両方とも光源から見える範囲に入っていますでしょうか?オブジェクトが光源から見える範囲からはみ出ると,シャドウマップに反映されなくなるため,うまく影が付かなくなります.

zun 2009年04月16日 12:28

床井先生<br><br>ご回答ありがとうございます。光源の位置をいろいろ変更して<br>試してみました。一部だけ影がつかない現象ですので、<br>ご指摘のように、光源から見える範囲に入っていない可能性が<br>あるのかも知れません。不勉強申し訳ございません。

鴨居 2009年09月25日 00:01

はじめまして<br>いつも拝見させて頂いております。<br><br>恐らくzunさんと同じ現象だと思うのですが、<br>光源の位置を高くすると正しく影が描画されないようです。<br>例えば、<br>static const GLfloat lightpos[] = { 4.0, 9.0, 5.0, 1.0 }; <br>を以下のようにしますと、<br>static const GLfloat lightpos[] = { 4.0, 18.0, 5.0, 1.0 };<br>直方体の影は消え、球の影は途中でリング状になってしまうようです。<br>どのように対策すれば良いでしょうか?<br>よろしくお願い致します。

とこ 2009年09月25日 13:19

 鴨居さま, コメントありがとうございます.<br> 光源の位置を高くすると物体から遠ざかってしまいますので, gluPerspective() で設定している near / far の値を調整する必要があるように思います. このシーンで lightpos[] = { 4.0, 18.0, 5.0, 1.0 }; とするなら, 床の高さが y = -2 なので, gluPerspective(20.0, (GLdouble)TEXWIDTH / (GLdouble)TEXHEIGHT, 16.0, 21.0); くらいにして試していただけますでしょうか. なお, このとき fovy も小さく (20 くらい?) しないと影の境界が汚くなります. よろしくお願いします.<br><br> zun さまの問題は,まだ原因がつかめていません.ごめんなさいごめんなさいごめんなさい.

鴨居 2009年09月25日 14:33

床井先生<br>さっそくのご回答ありがとうございます。<br>御蔭様で、うまく表示できるようになりました。どうもありがとうございます。<br>また、以下のようにnerを0付近、farを大きい値にしてしまっても問題ないものでしょうか?<br> gluPerspective(20.0, (GLdouble)TEXWIDTH / (GLdouble)TEXHEIGHT, 0.01, 99999.0);<br><br>またfovyの設定のことも教えて頂きありがとうございます。<br>確かに言われてみればそうなりますね。

とこ 2009年09月25日 18:45

> また、以下のようにnerを0付近、farを大きい値にしてしまっても問題ないものでしょうか?<br><br> 描画するものの奥行きの範囲に対して near / far の間隔が非常に広いと, デプスバッファの精度を有効に使えないので, デプステストが正確に行えなくなるようです. 大雑把なシーンだと問題が現れないかもしれませんが, 奥行き方向が込み入った部分でデプステストに失敗しやすくなります. 特にシャドウマッピングの場合は, 影と日向がまばらに現れてノイズのようになるデプスファイティングが起こりやすくなると思います. 設定してみて問題が起これば, near / far を調整してみてください.

鴨居 2009年09月27日 16:33

ご回答ありがとうございます。<br>なるほど、勉強になりました。適宜near/farを調整して試してみようと思います。<br>どうもありがとうございます。

工藤 2011年06月25日 10:41

床井先生<br><br>いつもこのブログの情報には本当にお世話になっております。<br>ずいぶん昔のエントリですが、現在openGLにおけるシャドウマッピングを勉強しており、2つほど質問があり初めて投稿をさせていただきました。<br><br><br>1)あるボックス内(部屋の中のような状況)に点光源1つと任意のオブジェクトがあり、点光源によって壁に落ちる影を光源の全周囲にて表現することを考えております。<br>視野角90°のビューボリュームをもつシャドウマップを光源中心に6つ直交して配置することで再現しようと考えているのですが、問題が起きております。<br>1つのビューボリュームにつき、そのテクスチャマッピングのR値が−の範囲(ビューボリュームの逆方向)においても、ちょうどカメラの投影のように”逆さ影”が生成されてしまいます。<br>これはR値が常に絶対値計算をされているということなのでしょうか。もし何らかの原因を思いつかれたら、助言を宜しくお願い致します。<br><br>2)また今後、光源に球のような領域を持たせてソフトシャドウを表現しようと考えております。基本的にはシャドウ領域において、R値とテクスチャ値の比を用いて、カラーバッファ描画時の各ピクセルのアルファ値をいじろうと思っております。<br>openGLにおいて、カラーバッファ描画時の各ピクセルのアルファ値を随時計算してくれる関数などは用意されているのでしょうか。やはり別のバッファオブジェクトに自分で書き込むプログラムを各必要がありますでしょうか。<br><br><br>以上二点、長文になり本当に申し訳ありません・・・。もしお時間があるときがあれば、どうぞ宜しくお願い致します。

工藤 2011年06月28日 11:02

床井先生<br><br><br>上で質問をした者ですが、自己解決致しました。<br>本当にありがとうございました。<br>1)に関しては、テクスチャ座標の自動生成においてRがマイナスとなる領域について、openGLの内部計算が”テクスチャ値Dtは参照する”にも関わらず、”Rは常に無限遠の値をとる”ため起こっていたことのようです。<br>したがってオブジェクトが存在する領域(参照されるテクスチャ値Dtが無限遠の値ではない領域)においては常にDt<Rが成立し、影が生成されるようです。<br>視点から描画したい点が、光源からの6つのシャドウマップボリュームのどの領域に属するかを随時判定する(そのシャドウマップのみでRとDtを比べる)構造にすることで解決致しました。<br><br>2)については、上記の判定構造がそのまま使えるので解決致しました。<br><br><br>本当にお忙しいところありがとうございました。<br>失礼致します。

とこ 2011年06月30日 19:00

工藤様,すみませーん,コメントをいただいたことに全然気づいておりませんでした.すみませーん.解決されたようで,何よりです.勉強になりました.

近藤 2012年01月17日 18:32

床井先生<br><br>シャドウマッピングについて、非常に勉強させていただきました。<br>ありがとうございます。<br><br>質問なのですが、箱の影だけを無効化するようなことはできるのでしょうか?<br>箱だけ別の関数で描画しようかと考えましたが、それだと箱に球の影が掛かりません。<br><br>古い記事に対して申し訳ありませんが、何か方法がありましたら是非ご教授ください。

とこ 2012年01月18日 20:37

近藤さま,<br><br>デプスバッファ法では描く順番は自由ですので,影の処理が終わってから影を落とさないものを描くだけでいいと思います.上の例であれば,例えば glutSwapBuffers() の直前で描いてみてはどうでしょうか.

近藤 2012年01月19日 12:01

床井先生<br><br>返信ありがとうございます.<br>早速試してみます.

どこかの学校の学生 2013年08月06日 14:55

床井先生<br>はじめまして、OpenGLを使う際、いつも床井先生のページを参考にさせてもらっています。<br><br>シャドウマッピングについて一つ質問があるのですが<br>光源から特定の部分だけ離れた時、無限に影が伸びてしまう現象を回避したいのですが、何か方法はありますでしょうか?<br>あるいは、光源に対してオブジェクトが大きすぎたとき、影の付加具合がおかしくなるのですが、回避策が見当たりません。<br><br>時間があれば、回答のほうをよろしくお願いします。

とこ 2013年08月07日 12:39

どこかの学校の学生さま、コメントありがとうございます。<br><br>シャドウマップ(デプスマップ)を作成する際、光源の位置に配置したカメラの可視範囲からオブジェクト(あるいはシーン)がはみ出ているのではないでしょうか。試しに光源の位置に配置したカメラの設定を調整してみてはいかがでしょうか。<br><br>光源から離れたところの影がおかしくなる場合は、カメラの far を大きくしてみるとか、光源に対してオブジェクトが大きい場合はカメラの fovy を大きくしてみるとか、あとは向きなども調整してみていただけますでしょうか。<br><br>あと、シャドウマッピング法ではなく、シャドウボリューム法(ステンシルバッファを使う方法)に切り替えるのも一つの手だと思います。手前味噌ですが、下記に影の付け方に関する資料を作っています。<br><br>http://www.wakayama-u.ac.jp/~tokoi/lecture/gg/ggnote11.pdf

どこかの学校の学生 2013年08月20日 12:41

返信が遅くなってしまい、申し訳ありません<br><br>床井先生ありがとうございます。<br>いろいろ調整してみようと思います。<br><br>資料の記載もありがとうございます。<br>参考にしてみます。


編集 «第26回 レンダリング画像をテクスチャに使う 最新 第1回 シェーダプログラムの読み込み»