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 におけるモデルの足元
まぁ、こんな所です。


2013/04/28

LiSPSM にリベンジしていた

数年前、LiSPSM (Light Space Perspective Shadow Maps) の実装を試みたものの、どうにもおかしな振る舞いとなる部分を直せずに利用を断念していましたが、納得していなかったので再挑戦していました。

LiSPSM 公式サイトでは C コードと C++ コードをサンプルとして公開しており、過去の挑戦では C コードを参考にしていましたが、今回は C++ コードを元に実装してみました。実際には、Ogre (オープンソースの 3D グラフィックス エンジン) も参考にしています。Ogre では、LiSPSMShadowCameraSetup クラスが LiSPSM のロジックにあたり、コードの基本はオリジナルと同じです。

で、方向性光源 (directional light) についてのみの実装ですが、何とかまともに動くようになりました。基本はオリジナル コードの模倣ですが、どうもオリジナルのままではカメラを光源の方向へ向けた場合に描画が上手くいかないため、Ogre のコードを真似ました。後は、Ogre では、カメラとライトが概ね平行になる場合に N の算出に補正を掛けていたので、これは良いと思い、形を変えて真似たという所です。

まぁ、自分で納得するか否かの問題で再挑戦しただけであり、恐らく LiSPSM は使わないのですが。僕は PSSM (Parallel Split Shadow Maps) を使う前提としているため、この場合には LiSPSM を用いる必要がそれ程には無いと言いますか。

いずれにせよ、LiSPSM のサンプル コードが少ないことで難儀したので、整理して公開しようと考えています。今の実装は自作 SharpDX ラッパー上の実装であり、サンプル公開として適さないため、XNA コードとして再実装しようかなと。ただ、凸体の構築ロジックではバカスカと新規オブジェクトを生成しているので、これを何とかしてからですかねぇ・・・。

2013/04/01

XNA UI Framework 動画

開発で使用するために作成した UI Framework のデモをニコニコ動画にアップしました。

 

2013/03/28

ベクトルのパック値とレンダ ターゲットでハマる

XNA Particels 3D サンプルと Shadow Mapping サンプルを移植していて、色々とハマりました。

まず、Particels 3D では PackedVector の Short2 を頂点データの一部に利用し、シェーダでは float2 でこれを受け取りますが、同様にした所、どうにも float2 で受け取れませんでした。int2 ならば受け取れます。頂点要素のフォーマット指定に従って、パック値から(バイトの並びから)自動的に float へ展開するものと思っていたのですが、違うのですかね・・・。
パック化の目的は頂点サイズを減らす事にあると思うので、int2 で受け取れるならそれで良いかという事にしてしまいました・・・納得はしていませんが。

続いて、Shadow Mapping はバック バッファ以外のレンダ ターゲットを利用するので、レンダ ターゲット周りの動作確認に良いかと思って移植してみたのですが、光源からの深度を書き込んだレンダ ターゲットがシェーダに渡されない状態にハマっていました。Shadow Mapping サンプルでは左上隅にレンダ ターゲットに書き込まれた深度マップを表示するので、書き込みの成功をこれで確認できるのに、深度マップと合わせてモデルを描画する際に、何故か参照できない(全深度が 0 になっている)状態でした。

結論としては、レンダ ターゲットとして利用しているテクスチャは、レンダ ターゲットとして設定する際には RenderTargetView、シェーダへ渡す際には ShaderResourceView で包んで利用する事になりますが、この両方がアクティブな状態となっている場合、ShaderResourceView で渡している側には 0 埋めされたテクスチャが設定される事が原因でした。MSDN を良く読むと、ID3D11DeviceContext.PSSetShaderResources の Remarks 等に、これを示唆する事が書いてありました。
ShaderResourceView として利用した後に参照を外してやれば良いだけですが、参照の外し忘れで同じ問題にハマるのも怖いので自動解決する方法を模索しましたが、あまり良い方法が思い浮かばないなぁと。
追記:その後、よくよく調べてみると、RenderTargetView を DeviceContext へ設定する際に ShaderResourceView 等の読み取りビューが設定されたスロットが null でリセットされる事が原因でした。素直に書く場合、必要な時に ShaderResourceView を設定するため、仮に null であっても再設定されるコードになるはずですが、僕はラッパークラスで参照をキャッシュするなどしていたため、D3D 内では null であるにも関わらず、ラッパークラスでは null ではないという状態の不整合が発生していたようです。
これらの他に、ハードウェア オクルージョン カリングを使う必要があるので、XNA の OcclusionQuery に相当するクラスを作り、Lens Flare サンプルを移植して試していました。
そろそろ必要な機能が揃い、動作も安定してきましたが、インスタンシングに関する枠組みを作ってみてから、ブロック地形のコードを新たな枠組みの上に乗せてみようかなと思っています。

2013/03/23

XNB モデル データの読み込みと描画まで到達

XNB ファイルを読み込み、自分のモデル クラスへデシリアライズし、描画が可能という所まで来ました。
公式サイトの XNB フォーマットの説明にコード例(C++)も含まれているので、読み込みに関する実装は簡単でしたが、自分が作成した Texture2D クラスのバグと言うか、自分の知識不足のために難儀しました・・・。

ただ、読み込めるデータには制約を設け、例えば、モデルで扱えるエフェクトを BasicEffect に限定したりなどしています。また、今はモデルを読み込みたいだけなので、その他は無視しています。

モデルがデフォルトのまま BasicEffect を用いる場合、BasicEffect へ設定するプロパティが XNB に埋め込まれるだけです。このため、読み込んだプロパティを自作クラスへ設定するだけで済みます。カスタム エフェクトを用いる場合、エフェクトのバイトコード、および、エフェクトに設定するプロパティがディクショナリ形式で埋め込まれます。それらの読み込みは簡単ですが、データの利用には汎用的なエフェクトの枠組みが必要であろうかと思います。XNB はシェーダ モデル 3.0 以下だと思うので、その考慮も大変そうです。

これで、FBX ファイルの読み込みと最適化を XNA コンテンツ プロジェクトに任せてしまい、自分は XNB を利用するだけ、という事が可能になったので、テスト コードやデバッグ コードの記述が随分と楽になるんじゃないかなぁと。

ただ、こうなると、「SpriteFont もコンテンツ プロジェクトで管理して XNB から読み込めばいいんじゃないの?」・・・みたいな。自分で専用コードを実装してしまいましたが、破棄しようかなぁ~と。

2013/03/20

非 XNA プロジェクトから XNA コンテンツ プロジェクトを参照する

Assimp を利用するという事は、参照アセンブリを増やす事であり、ならばそれが XNA のアセンブリでも良いのではと考え始め、試行錯誤していました。X ファイルも FBX ファイルも自分にとって本質的には重要ではないとなると、それらのインポート機能を備えた XNA をそのまま利用する方法を模索してみようという所です。

XNA コンテンツ プロジェクト (.contentproj) を非 XNA プロジェクトからビルド時に参照できれば、後は XNB ファイルを自作クラスへデシリアライズする機能を実装する事で、XNA Content Pipeline の恩恵を得られる・・・はず。コンテンツ プロジェクトは XNA 依存であっても、生成される .xnb はただのバイナリ ファイルであり、実行環境を問わないですからね。

XNA コンテンツ プロジェクトは、XNA がインストールされていれば、Visual Studio で簡単に作成できます。しかし、Visual Studio 上では、非 XNA プロジェクトから参照する術がありません。そこで、どうせ MSBuild でビルドしているのだから .proj ファイルを弄れば良いのでは、と思い弄っていたら上手くいきました。

もしかしたら、もっと簡単にやる方法があるのかもしれませんが、以下、要点を記録として記載しておきます。

1. 参照元プロジェクトで XnaPlatformXnaProfile を定義

参照元 .proj に、<XnaPlatform><XnaProfile> を追加します。例えば、以下のように追加します。
<Project ...>
  <PropertyGroup>
    <Configuration ...>
    ...
    <XnaPlatform>Windows</XnaPlatform>
    <XnaProfile>HiDef</XnaProfile>
  </PropertyGroup>
  ...
</Project>

2. コンテンツ プロジェクトへの参照を追加

参照元 .proj に、参照したい .contentproj<ProjectReference> で定義します。例えば、以下のように追加します。
<Project ...>
  ...
    <ItemGroup>
    ...
    <ProjectReference Include="..\SampleXnbContent\SampleXnbContent.contentproj">
      <Name>SampleXnbContent</Name>
      <XnaReferenceType>Content</XnaReferenceType>
      <Project>{nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn}</Project>
    </ProjectReference>
    ...
  </ItemGroup>
<Project> には、参照先 .contentproj で定義されている <ProjectGuid> の値をコピーします。

3. コンテンツ用ビルド定義をインポート

参照元 .proj に、<Import> で XNA コンテンツ用定義をインポートします。例えば、以下のように追加します。
<Project ...>
  ...
  <Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA Game Studio\v4.0\Microsoft.Xna.GameStudio.Content.targets" />
  ...
</Project>
$(MSBuildExtensionsPath) は、自分の環境では "C:\Program Files (x86)\MSBuild" です。
ここまでで、参照元プロジェクトのビルドではコンテンツ プロジェクトが同時にビルドされ、参照元プロジェクトの出力先に .xnb ファイルが配置されるかと思います。

4. コンテンツ ルート ディレクトリを定義

ここまででは、参照元プロジェクトの出力先にはコンテンツ プロジェクトの名前がそのままディレクトリとして作られ、その中に .xnb ファイルが出力されます。XNA のデフォルトのように、Content ディレクトリへ .xnb を出力したい場合には、参照先 .contentproj にこれを定義します。
<Project ...>
  <PropertyGroup>
    <ProjectGuid>...</ProjectGuid>
    ...
    <ContentRootDirectory>Content</ContentRootDirectory>
  </PropertyGroup>
</Project>

・・・と、ひとまず出力先に .xnb を含める事が出来たので、後は XNB の内容を解析し、自分のクラスへデシリアライズするだけだなと。

2013/03/19

モデル データを読み込むためのライブラリ: Assimp

XNA 公式サンプルを簡単に移植するには、Model クラスを作り、モデル データを Model へインスタンス化する必要があるなぁ・・・と思い、MonoGame を見ていたら Assimp なる物を呼び出していたので、ダウンロードしてラッパーを作成し、Model クラスの実装を試していました。
Assimp
http://assimp.sourceforge.net/
Assimp 自体は C++ で、これを C# で呼び出すための assimp-net を利用してみました。
assimp-net
https://code.google.com/p/assimp-net/
MonoGame の開発途中の Content Pipeline の FBXImporter を見て Assimp を見つけたのですが、サイトを見る限りでは、Assimp は FBX 非対応なんですよね。実際に FBX ファイルを読み込んでみましたが、インスタンス化は可能な物の、情報は設定されませんでした。X ファイルならば、サイトの機能紹介にあるように読み込めているようです。

で、X で良いのなら FBX も X に変換すれば良いだけですが・・・。過去に FBX と X 間の相互変換を試した事があり、その時の記憶では、どんなツールを落としてきても期待通りに変換できた試しが無いんですよね。それから数年が経過したため、再度、色々と調べてみるのも良い気はしますが・・・。

僕は、自分でモデル データを作る事も、他の人が作ったモデル データを利用することも無く。殆どの場合、僕はコード上でモデル データを自動生成しています。つまり、モデル データのフォーマットに関しては、深く考えるだけ時間の無駄かな、と。

ただし、それ以外の部分で XNA サンプルで試したい事が多いので、強引な対策を採ってみようかなと。XNA 公式サンプルで描画可能なモデル データは、少なくとも XNA 内部で Model インスタンスであるため、XNA で Model インスタンス化した上でデータを抜き出し、自作の JSON データへ出力して読み込む事にしてやろうか、などと考えています。

libgdx いじり

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