2013/04/29

XNA LiSPSM (および VSM)

XNA での LiSPSM (Light Space Perspective Shadow Maps) 実装のサンプルを公開しておきます。
LiSPSMDemo
https://github.com/willcraftia/TestXna/tree/20130429/LiSPSMDemo
ZIP でのダウンロードは以下から (関係ない物も含まれてしまいますが)
https://github.com/willcraftia/TestXna/tree/20130429
XNA Shadow Mapping を原型として LiSPSM の機能を乗せ、それらを切り替えて確認できるようにしてあります。ただし、ライトとしては方向性光源のみを実装し、点光源には対応していません。
なお、ビルドに必要な XNA コンテンツは Git 管理外としているため、別途、XNA 公式サイトより XNA Shadow Mapping をダウンロードし、XNA コンテンツを LiSPSMDemo へコピーしてプロパティを設定する必要があります。
XBOX LIVE indie games - シャドウ シリーズ 1: 基本シャドウ マッピング
http://xbox.create.msdn.com/ja-JP/education/catalog/sample/shadow_mapping_1
LiSPSM 公式サイトにある C++ コード (オリジナル コード) をベースにしていますが、オリジナル コードのままでは問題が幾らかあるため、Ogre の LiSPSM 実装を加えて参考にしています。なお、Ogre では LiSPSMShadowCameraSetup が LiSPSM の実装部分であり、その原型はオリジナル コードです。
Light Space Perspective Shadow Maps
http://www.cg.tuwien.ac.at/research/vr/lispsm/
Ogre
http://www.ogre3d.org/
また、日本語での LiSPSM の解説については Project ASURA を参考にしています。ここでは LiSPSM の仕組みについては述べないため、詳しく知りたい方は Project ASURA の解説を参照すると良いでしょう。
Project ASURA - Light Space Perspective Shadow Maps
http://asura.iaigiri.com/OpenGL/gl59.html
なお、VSM  (Variance Shadow Maps) も合わせて乗せています。
Variance Shadow Maps
http://www.punkuser.net/vsm/
以下、シャドウ マップを VSM とし、LiSPSMLightCamera、FocusedLightCamera、BasicCamera による各ライト カメラでの比較画像です。いずれも、XNA Shadow Mapping で用いられるシャドウ マップ サイズ 2048x2048 です。

LiSPSM + VSM
Focused + VSM
Basic + VSM
LiSPSMLightCamera は、LiSPSM 実装そのものです。FocusedLightCamera は、LiSPSM で用いられる視錐台の歪みに相当する行列を単位行列としたものです。Basic は XNA Shadow Mapping で用いられる基礎的なライト カメラです (実際にはそこから更に不要と判断したコードを除外しています)。

カメラとライトが平行になる状況における補正

カメラとライトが平行に近づくと LiSPSM は大きく品質が劣化しますが、LiSPSMDemo ではそのような場合に n 値に補正を掛け、品質を向上させています。これは、Ogre で実装されている仕組みを参考に、オリジナルの n 算出式の結果に対して適用しています。なお、Ogre では n 算出式が完全自動ではなく、プロパティによる調整を前提に作られているようです (描画するシーンに応じてプロパティを適切に調整する必要があると思います)。

以下、補正 ON と OFF での比較画像です。差異を顕著にするため、VSM ではなく Basic でシャドウ マップを生成しています。
LiSPSM + Basic + 補正 ON
LiSPSM + Basic + 補正 OFF
ここでの補正の仕組みは、カメラとライトの内積の絶対値が、指定の閾値を越える場合に並行であると見做し、並行と見做せる場合に n 値を若干大きくしてしまうという物です。カメラとライトが並行になる場合に発生する品質の劣化は、n が 0 に近づくことで発生するため、0 に近づけさせないようにして解決するという事です。

凸体 B の押し出しに関する問題

方向性光源を用いる場合、凸体 B を構成する際には、表示カメラの視錐台をシーン領域でクリップした後、B の各頂点からライト方向へレイを飛ばし、幾らか B の範囲をライトのある方向へ押し出して領域を拡張する必要があります。
これを行わない場合、投影オブジェクトがカメラの外に出るような状況において、その分だけ影が描画されない問題が発生します。この問題は、例えば、このサンプルにおいては、デュード モデルの影に非常に接近する場合に起こりえます。
オリジナル コードでは、そのようなレイとシーン領域との交点を B へ追加しますが、どうやらレイの始点となる各頂点がシーン領域の縁に存在し、結果として始点が交点となってしまい、交点の意味が無いという状態になってしまいました。もしかすると、凸体 B のクリッピングに誤りがあるのかもしれません。

これに対し、Ogre では、そのようなレイにそって一定の距離 (ライトの遠平面距離) にある点を追加して解決しています。そこで、LiSPSMDemo においても同様の概念で凸体 B を押し出しています。

ただ、実際にはどうなんでしょう?
表示カメラの視錐台をシーン領域でクリップする処理は、シーン領域の一部が視錐台によりクリップされる事も含んでいます。つまり、視錐台の外にある投影オブジェクトは凸体 B の外に出てしまうので、描画範囲から除外されてしまいます。そこで、このような投影オブジェクトを描画範囲に含めるため、凸体 B をライトのある方向へ少し押し出し、最終的なクリッピング領域を広げているのだと思います。ただ、これ自体は、LiSPSM に限らず、一般的にシャドウ マッピングにおいて考えなければならないクリッピング領域の問題であろうかと思います。
最小限必要となる凸体 B の構築を考えた場合、投影オブジェクトで構成される領域を別途管理し、凸体 B へこれを含めるなどの処理を行うべきなのかもしれません。実際、Ogre では投影オブジェクト領域と非投影オブジェクト領域を別途管理し、それらを考慮した領域の結合などを行なっているようです。
ですが、ある範囲に対して投影しうるオブジェクトであるか否かの判定自体が面倒でもあるので、厳密な判定が不要であるならば、一定距離で範囲を広げてしまえば良い気もします。

その他思うこと

Basic でシャドウ マップを作ると、セルフ シャドウが汚いなぁと。VSM でシャドウ マップを作ると、セルフ シャドウは綺麗になるものの、VSM ではモデルと影の関係が変になるんだよなぁと。
LiSPSM + Basic におけるセルフ シャドウ
LiSPSM + VSM におけるセルフ シャドウ
LiSPSM + Basic におけるモデルの足元
LiSPSM + VSM におけるモデルの足元
まぁ、こんな所です。


0 件のコメント:

コメントを投稿

libgdx いじり

Google が提供している Java 版の Tango Examples は Rajawali をベースにしているため、自分が仕事で開発する Tango アプリも Rajawali ベースとしていましたが、最近は libGDX への移行を進めています。一応、要点については移行が...