ホーム < ゲームつくろー! < DirectX技術編 < 深度バッファシャドウの根っこ:0から原理を眺めてみよう
その44 深度バッファシャドウの根っこ:0から原理を眺めてみよう
今日の3Dゲームにおいて影は必須です。よりリアルに美しく見せるため、日夜その技術が磨き上げられてきています。影の生成方法は世の中に本当に沢山ありますが、その中でいわゆる「セルフシャドウ」を落とす事ができる手法は多くありません。セルフシャドウというのはキャラクタが自分自身に落とす影で、これを可能な影生成法として「ステンシルシャドウ」と今回取り上げる「深度バッファシャドウ」が良く知られています。
ステンシルバッファシャドウには既存のポリゴンの他に「縮退四角形」という蛇腹のようなポリゴンを別に用意する必要があります。ただし用意できれば実装は割りと簡単です。一方深度バッファシャドウは現存のポリゴンだけでセルフシャドウを落とせますが、実装はステンシルバッファシャドウより難しくなります。ただ、色々文献を調べると、深度バッファシャドウの方が技術革新が進んでいるように感じます。それは、見た目の綺麗さと細かな制御が利く点にあるかなと思います。
そこでこの章では深度バッファシャドウについてじっくりねっちり検討してみたいと思います。0から始める根っこシリーズ、早速行ってみましょう(^-^)
@ 『深度バッファシャドウ』って何だ?
題目にもあります深度バッファシャドウ(Depth Buffer Shadow)とは具体的にどういうものか?これは「Z値」を利用した影生成方法の事です。Z値というのはカメラのファインダを覗いた時に見える被写体までの縦距離の事です。なぜ距離がわかると影を作れるのか?これについて、以下でしっかり説明します。
A 深度バッファシャドウの原理:計算済みのZ値と実際の距離の比較
ではこの方法による影生成を0からじっくり見てみましょう。まずは下の図をご覧下さい:
この図は四角錐のオブジェクトに対してライトが当たっている様子を表した模式図です。今ライトは頂点Aにまっすぐ向いていると考えて下さい。この時、ライトの位置(L)から放出された光は頂点Aまでは届かずに、手前の面の赤い点Bにぶつかります。このライトから一番手前にあるオブジェクトの表面Bまでの縦距離が「Z値」になります(厳密にはその距離を0〜1に標準化した物がZ値です)。
ライトから点Aまでの縦距離LAは、明らかにLBよりも長いです。つまり、上の図からも明らかなように、LA>LBであれば「頂点Aは点Bの裏にいる=影の中にある」ことがわかります。
さて、上の図にちょっと床を加えてみましょう。
四角錐から影が伸びて床に投影されています。今床の上の1点である点Cに注目し、そこが影か否かを判定してみます。ライトから点Cに向かって線を引いた時、手前のポリゴンに先にぶつかります。つまりLC>LBなので、Cには光が届かない事がわかります。よって、点Cの位置は影であると結論付けられます。同様の判定を床一面に展開すれば、床は明るい部分と影の部分の2種類で色分けする事が可能になるというわけです。
「肝心のセルフシャドウは?」と思われるかもしれませんが、これもまったく同じ原理で自然発生します。上図四角錐のこちらを向いている面はきっと点Bを含んでいる面に覆い隠されます。つまり、手前のポリゴン内のどの点も右の面のZ値より大きくなるため、ポリゴンは影になります。
「カメラからのZ値を比較すると点が影か明かを判定できる」。これが深度バッファシャドウの基本原理です。
B カメラ目線が大切です
ところで、実は私たちが知りたいのはライト目線で見た影の有無ではありません。画面に表示されるのはあくまでも「カメラ目線」です。つまり、私たちは本当はカメラから見たある点が影かそうでないかを知りたいわけです。一見するとこれは難しそうに思えますが、実は「ワールド空間の1頂点は誰が見たって同じ座標」という事実を使うと、非常にうまく解決します。
具体的な図をご覧下さい:
カメラから見たとしても、ライトから見たとしても、点Cのワールド空間でのワールド座標は共通しています。ということは、カメラから見ようが見まいが、点Cが影であるかどうかはライト方向からの情報だけでわかるんです。後はカメラ自体が自分がスクリーンに穿つ予定の点Cについて「影かどうか調べて欲しいんですけど」とライト側に依頼すればいいんです。
C 深度バッファシャドウのプログラマブルシェーダ
以上を踏まえまして、下のプログラマブルシェーダのフローチャートをご覧下さい:
図が大きくですいません(詰めたのですが限界です)。これは深度バッファシャドウの計算の流れを示しています。見た目難しそうですが、じっくり見ればそれ程でもないので安心して下さい
まず、頂点シェーダにはローカル空間にあるオブジェクトの1つの頂点が入ってきます。その座標は頂点シェーダ内でワールド変換行列によって世界に置かれます。ここまでは、カメラもライトも関係ありません。ここで左右に分かれます。左側のルートを通ると、頂点はカメラの見た目線(カメラビュー変換)、そしてその射影変換によりカメラ目線のスクリーン座標に変換され、最終的に頂点シェーダの出力座標となります。一方右側はライト目線への変換です。ライト方向から見た場合のビュー射影変換を経て、最終的にはテクスチャ座標として登録されます。1つの頂点シェーダで出力座標とテクスチャ座標の2つを同時に出力する。これがポイントです。
ピクセルシェーダの段階では、頂点シェーダで算出したカメラ目線のスクリーン座標に対応する色、ライト目線のポリゴン表面の補間座標が入力値として入ってきます(補間座標については前章をご覧下さい)。まず右側のライトの流れから見ていきましょう。ポリゴン表面の補間座標のz成分をw成分で割ると、これはその座標のZ値となります。@の図で言うとLAの長さをここで求めた事になります。ではLBの長さはとなりますが、実はこれは事前に計算しておきます。これはライト方向から見たZ値をテクスチャに描画しておけばよいだけです(これについても前章をご覧下さい)。つまり、ここで補間点が影か否かを判定できるわけです。右側の流れの最終目的はこの判定を行うことなんです。
そこで次はカメラ目線である左側の流れを見てみます。ピクセルシェーダにはカメラ目線で見たポリゴン表面上の1点の色が入ってきます。この点は右側のライト目線の補間位置にある点です。そうなるように頂点シェーダで仕込んだんです。今、この点が影か否かはもうわかっています。ですから、もし影であれば入力されてきた色を暗くして出力すればいいんです。
頂点シェーダでカメラ側とライト側の2つに分かれた流れは、ここで見事に合流します。これを考えた人、本当に凄いなぁと私は思います。
D 実装は次の章で
この章では原理の説明でお腹いっぱいです。実際の実装は次の章にまわす事にします。実装のポイントはZ値テクスチャの作成と、Cで説明した深度バッファシャドウのシェーダプログラムの作り方です。このうち、Z値テクスチャの作成方法は前章で説明致しました。ただ、実は深度バッファシャドウを実装するに当たり、Z値テクスチャの作成をクラス化したいのです。そこで、次の章ではZ値テクスチャ作成部分のクラス化に挑戦してみます。