2012/07/30

XNA Infinite Terrain 改

追記: ちょっとテクスチャを貼ったバージョンも作りました。
Blocks Project: Terrain Texturing

地形の浸食処理 (erosion) を試す上で、これまでの設計では十分な対応ができなかったため、大きく変更していました。
リファクタリングを繰り返していると、次々と自分のおかしなコードに気付き、酷いもんだと感じます。

なお、浸食処理は、コードで見た場合、下記サイトで紹介されている Erode メソッドが分かりやすいと思います。
float4x4: Generating realistic and playable terrain height-maps
上記サイトの Erode メソッドは、Kenton Musgrave の論文『The Synthesis and Rendering of Eroded Fractal Terrains』にある thermal weathering (thermal erosion) の実装例であろうと思います。もっと早くにこの論文に辿り着きたかったですね。

Thermal erosion のアルゴリズムですが、ある頂点の高さを隣接する頂点の高さへ分け与えたり、逆に高さを貰ったりを繰り返すだけの処理です。現実でイメージするならば、山の土が削られ、削られた土が下へ流れていく様ということです。

なお、thermal erosion だけでは見た目の変化が微妙過ぎるため、動画も画像も上げません。

ソースコード

下記にソースコードを置きました。
GitHub: TestXna (tag 20120730)
上記 Git プロジェクトのうち、下記の XNA プロジェクトが対象です (全てをダウンロードしなければコンパイルできません)。
  • TiledTerrainDemo: デモ アプリケーションのソリューション
  • DemoFramework: 基礎的なクラスを含むライブラリ
  • Framework.Noise: ノイズ生成クラスを含むライブラリ
  • Framework.Terrain: 地形描画の基礎となるクラスを含むライブラリ
  • Framework.Terrain.CDLOD: CDLOD のためのライブラリ
  • Framework.Landscape: 動的地形生成管理のためのライブラリ

隣接する Height map の動的マージ処理

ノイズ関数から生成された値をそのまま height map とするだけならば、height map の境界上の値は隣接する height map 同士で等しくさせることができるため、単に並べるだけで済みます。

しかし、Height map へ浸食処理を適用した場合、浸食処理はある頂点の高さを変位させる際に、隣接する頂点の高さも同時に変位させます。このため、隣接する height map 同士の境界上の高さは一致しなくなります。

そこで、height map をノイズで生成して浸食処理を適用したら、まずはそのまま利用し、隣接する height map のロードが完了したタイミングで、互いに等しい値とする境界上の高さを平均値でマージするようにしました。

なお、マージの前後で高さの変化が発生しますが、マージの発生は自然と遠方にある height map が対象となるため、その変化が視覚的な影響を与えることはないと考えています。

CPU 上での Normal Map の生成

過去の実装では、境界上にある頂点の法線を正しく算出するために、height map の上下左右を +1 分だけ余分にとっていました。これは、GPU 上で法線を算出していたこと、および、各 height map をなるべく独立させておきたいと考えていたことに原因があります。

今回の実装では、まずは height map のロード時に CPU 上で normal map を同時に作成して利用します。次に、height map のマージが発生した時に、隣接する height map を参照して normal map の境界上を再計算します。

しかし、描画上はこれで良いのですが、height map と normal map のマージ処理、および、Texture2D の更新の 2 つにより、その瞬間でやや大きめの負荷が掛かるようになってしまいました・・・。

余談ですが、DirectX には height map から normal map を作成するための ComputeNormalMap 関数が用意されているのに、なぜ XNA には無いのですかね?と思いながらも、別に無くて困ることもないと思いつつ。

テクスチャを切り替えながらの Height/Normal map の更新

マージにより height/normal map のための Texture2D の更新が発生するため、それらに対して Texture2D を 2 枚用意して切り替えて更新するようにしています。
この処理では、下記サイトで紹介されている FlipTexture2D クラスを用いています。
ひにけに XNA: 頂点テクスチャでスキンアニメーション
上記ページは頂点テクスチャについての説明ですが、通常のテクスチャにおいても同じことであろうと思います。

ただ、少し分からない問題があり、以下の「ひにけに XNA」にある説明ですが、
ひにけに XNA: 動的テクスチャとその注意点
ここでは、Texture2D.SetData の際に、「以前のテクスチャを GPU が使い終わるまで待ってからコピーが行われる」とあるのですが、自分の環境では GraphicsDevice に設定されていることで更新不能であるとの例外が発生するのですよね。

結局、この問題への対策が分からなかったため、テクスチャを切り替えながら更新することにしたのですが、恐らく、更新の間隔が極めて短くなると同じ現象が現れるのではないだろうかとも考えています。

こういう部分になると、僕の知識程度ではさっぱり分からない。

0 件のコメント:

コメントを投稿

libgdx いじり

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