ラベル Terrain の投稿を表示しています。 すべての投稿を表示
ラベル Terrain の投稿を表示しています。 すべての投稿を表示

2012/08/01

Hydraulic erosion 調査

Hydraulic erosion について色々と調べていました・・・と言うか、幾つかの実装を終えました。その内の 1 つが、F.Kenton Musgrave、Craig E. Kolb、Robert S. Mace による『The Synthesis and Rendering of Eroded Fractal Terrains』で述べられているアルゴリズムによるものです。

で、そのうち調べたことを忘れ、その時に再度英語で論文を読み返すのも疲れると考え、備忘のために自分の解釈でまとめてみることにしました。なお、図解はしませんし、専門家ではないので言い回しの問題はご容赦ください。また、原文の訳を載せているわけではないので注意してください。

Musgrave らの論文の PDF は以下から得られますが・・・これは大丈夫なんでしょうか?
ebookbrowse.com: The Synthesis and Rendering of Eroded Fractal Terrains pdf
日本語で hydraulic erosion を何と言えば良いのか分かりませんが、大雑把には、
  1. 雨が降り、
  2. 雨により土が削られて土砂が生まれ、
  3. 土砂が水に混じり、
  4. 水の流れと共に土砂が移動し、
  5. 時に土砂が堆積する、
という現象のシミュレートと言えば良いでしょうか。 なお、水の流れと言っても、河川のような複雑な水の流れを考えるのではありません。


アルゴリズム

※原文は時間 $t$ から $t + 1$ への変化で説明していますが、なんだか式が見づらくなるので表記を省き、数式をプログラミング言語としての式へ変えています。

Height map 上のセルについて、$a$ (altitude: 地形の高さ)、$w$ (water: 雨による水の量)、$s$ (sediment: 水に混じっている土砂の量) を考えます。

このアルゴリズムの上では、雨による $w$ の定め方については触れません。シンプルな雨の実装としては、各ループの最初に一律で $w$ へ値を加える方法がありますし、少しランダムな状態としたければノイズ関数で値を加えるなどの方法もあります。いずれにせよ、何らかの形で $w$ を与える必要はあります。

なお、シミュレーションの最初のループでは $s = 0$ であり、処理の過程において水が土を削ることで土砂が生まれる点に注意が必要です (最初から土砂があると考えてアルゴリズムを見ると混乱します)。
また、実装では height map 上の全セルについて水の移動を処理する必要がありますが、ここではセル $v$ について、隣接するセル $u$ への移動を説明している点に注意が必要です。

セル $v$ から $u$ へ流れ出る水の量 $\Delta w$ は次式となります。
\[ \Delta w = min(w_{v}, (w_{v} + a_{v}) - (w_{u} + a_{u})) \]
まず、$\Delta w \leq 0$ は、「$v$ には水がない」あるいは「$v$ から $u$ へは水は流れ出ることができない」ことを表しているため、水の移動を考えず、土砂の堆積のみを考えます。 ここで、堆積の割合を定数 $K_{d}$ (deposition constant) で定義し、堆積する量を $K_{d} s_{v}$ として堆積を処理します。
\begin{eqnarray} a_{v} &=& a_{v} + K_{d} s_{v} \\ s_{v} &=& s_{v} - K_{d} s_{v} = (1 - K_{d}) s_{v} \end{eqnarray}
一方、$0 < \Delta w$ では水が移動できるため、土砂の移動についても考えます。まず、水の移動は単純に次式で表せます。
\begin{eqnarray} w_{v} &=& w_{v} - \Delta w \\ w_{u} &=& w_{u} + \Delta w \end{eqnarray}
次に、水と共に移動する土砂の量ですが、水が全ての土砂を運べるとは限らず、その能力には限界があります。そこで、水が運べる土砂の割合を定数 $K_{c}$ (sediment capacity constant) で定義し、$\Delta w$ について共に移動する土砂の量 $c$ (sediment capacity) を次式で定めます。
\[ c = K_{c} \Delta w \]
ここで、$c$ と $s_{v}$ の比較で処理が分岐します。 まず、$s_{v} \geq c$ では $c$ 分を移動させるだけの $s_{v}$ があるため、そのまま移動させます。そして、移動後の残りの分 $s_{v} - c$ について堆積を考えます。
\begin{eqnarray} s_{u} &=& s_{u} + c \\ a_{v} &=& a_{v} + K_{d} (s_{v} - c) \\ s_{v} &=& s_{v} - c - K_{d}(s_{v} - c) = (1 - K_{d})(s_{v} - c) \end{eqnarray}
一方、$s_{v} < c$ では、$c$ 分の移動には $s_{v}$ が不足していますが、この状態は、「更に土が水に混じる余地のある状態」と考えることができるため、土を削って土砂を生み出すこととします。

なお、シミュレーションの最初の段階では $s = 0$ であり、必ずこの処理に入ります。そして、この処理で土砂が生まれ、$s$ が増加するということになります。

ここで、土砂として水に混じることのできる量に対して、実際に土砂として削る割合を定数 $K_{s}$ (soil softness constant) で定義し、$K_{s} (c - s_{v})$ だけ地形を削り新たな土砂とし、次式で水と土砂の移動と堆積を考えます。
\begin{eqnarray} s_{u} &=& s_{u} + s_{v} + K_{s} (c - s_{v}) \\ a_{v} &=& a_{v} - K_{s} (c - s_{v}) \\ s_{v} &=& 0 \end{eqnarray}
・・・と、以上のようなアルゴリズムを考えるそうです。そして、ここまでの処理を $n$ 回繰り返してシミュレーションとします。

2012/07/30

Terrain Texturing

あまり興味がなかったので、地形にテクスチャを貼ることを無視していたのですが、何だか貼りたくなる発作が起こり、貼りました。


上図では MinFilter = Anisotropic、MaxAnisotropy = 4 としています。MaxAnisotropy = 16 でもそれなりに動いたのですが、描画する範囲が増える局面では 60fps を維持できなくなってしまいました・・・。この辺りを考えると、やはりまだまだ改善すべき点があるのだろうと感じます。

この実装では、下記サイトのデモ コードからテクスチャを拝借しています。
dhpoware: XNA 4.0 Terrain Texturing Demo
HLSL も上記サイトとほぼ同じにしていますが、かなりやっつけ仕事として既存の実装へマージした状態です。
仕組は、4 枚の diffuse map を渡し、高さから定まる割合を元に各 diffuse map から得られる色への重み付けを決定してブレンドするという、数多のサイトで紹介されているシンプルな実装です。

ソースコードは以下のタグで取得できます (プロジェクト構成については「XNA Infinite Terrain 改」を参照してください)。
GitHub: TestXna (tag 20120730-2)

やはり、テクスチャを貼ると、それだけで質が上がったような気分になれますね。むしろ、「最初からそうしろよ!」という気がしないでもありません。

XNA Infinite Terrain 改

2012/07/27

Midpoint Displacement による地形生成

Midpoint Displacement による地形生成を実装していました。
Wikipedia (en): Midpoint displacement algorithm - Diamond-square algorithm
日本語では「中点変位法」と呼ぶようです。なお、Diamond-square algorithm については実装していません。

アルゴリズムについては、下記サイトの説明が分かりやすいと思います。
Generating Random Fractal Terrain: Midpoint Displacement in One Dimension
fractalterraingeneration: Midpoint Displacement
極めて簡単にアルゴリズムを説明すると、最初に height map の 4 隅に値を与え、2 点間の中点に対してそれら平均値 + 誤差 (乱数) を与えて決定し、この中点の決定を必要な所まで繰り返すというものです。

検索すると数多の公開コードがありましたが、サイト fractalterraingeneration で公開されているコードが最も綺麗に見えたので、これを元に実装しています。

なお、僕のコードでは、実行中に複数の height map を生成して並べたいため、シードを与えた後、(x, y) に対して一意に値が定まるような擬似乱数を用いています (ノイズの利用で行なっている事と同じ)。
見かけた公開コードの全ては (探せば別の物もあるでしょうが)、基点となる 4 隅の高さや中点の変位量を定める際に、C# ならば Random.NextDouble() に代表されるような擬似乱数生成器の値をそのまま利用していましたが、この方法では height map 間の境界上での高さを一致させることは困難です。

また、サイト fractalterraingeneration のコードでは、変位の際に与える誤差の重み付けが大雑把なため、サイト Generating Random Fractal Terrain の説明にあるようなパラメータ H による制御としています。なお、他のサイトでは、変位量の計算メソッドをオーバライド可にし、柔軟に実装を変更できるようにしているものもありました。

下図は、Midpoint displacement で height map を生成し、高度による色付けを行ってみた結果です。


下図は、「XNA Infinite Terrain アップ」で紹介したコードの Midpoint displacement 版を実行した結果です。


まぁ・・・ノイズ計算と重ね合わせを実行することに比べると、極めて高速です。見た目も十分に地形であり、今まで僕がノイズで作っていた地形よりも地形らしく見えるような気がしないでもありません。

ただし、多様な地形を表現できるかどうかについては、Midpoint displacement では厳しいであろうと考えています (特定の座標にある値を即座に得ることはできない仕組みであるため)。

また別の問題として、僕には今のところ解決の術が見つからないのですが、中点を選んでの変位を繰り返す処理ゆえに、綺麗に中点を選択できなくなる height map サイズを用いる場合、height map 同士の特定の位置にある高さを一致させることが難しくなります。
地形をポリゴンで描画する際、境界上にある頂点の法線を地形同士で一致させるために、僕は通常用いられる height map よりも 1 ピクセル分だけ余分に確保して法線の算出に用いていたのですが、Midpoint displacement ではこれを行うことができません。実行中の height map 生成でなければ、隣接する height map から情報を拝借して済む問題ではあるのですが。
このため、地形描画コードでは、height map 同士が隣接する境界での法線が不正となり、繋ぎ目が見える状態です。

下記にソースコードを置きました。
GitHub: TestXna (tag 20120727)
上記 Git プロジェクトのうち、下記の XNA プロジェクトが対象です (全てをダウンロードしなければコンパイルできません)。
  • MidpointDisplacementDemo: デモ アプリケーションのソリューション
  • DemoFramework: 基礎的なクラスを含むライブラリ
  • Framework.Terrain: 地形描画の基礎となるクラスを含むライブラリ
  • Framework.Terrain.CDLOD: CDLOD のためのライブラリ
  • Framework.Landscape: 動的地形生成管理のためのライブラリ
また、MidpointDisplacementDemo ソリューションは、以下の 2 つのスタート アップ プロジェクトを持ちます。
  • MidpointDisplacementDemo: 単に色付けしてテクスチャとして表示するデモ
  • MDTerrainDemo: ポリゴンで実際に描画するデモ
なお、Midpoint displacement の実装そのものだけならば、以下のファイルとなります。
GitHub: MidpointDisplacement.cs

2012/07/22

ボロノイ分割による地形生成

複数の height map を並べる処理をしている間、ボロノイ分割による地形生成も実装していました。
Wikipedia: ボロノイ図
ソースコードは、前回の XNA Infinite Terrain のコードと同じですが、修正を加えた状態が以下となります。
GitHub: TestXna / TiledTerrainDemo (tag 20120722)
(TiledTerrainDemo.Noise.Voronoi クラス周辺)
実行例は、以下のようになります。

距離メソッド = Squred, 選択方法 = Distance0, Frequency = 1


ロジック

簡単に説明すると次のようになります (実際には 3 次元で値を生成しますが 2 次元で考えます)。

まず、float 値の (x, y) について周辺のセル (int) を特定し、それらセルに含まれる母点を乱数で定めます (乱数といっても同じセルなら必ず同じ母点)。
続いて、周辺の各セルについて (x, y) から母点までの距離を測定し、最も近い母点を選択します。
最後に、選択された最も近い母点について乱数を生成し、これを (x, y) の高さの値とします。
結果として、ある母点に近いところにある (x, y) の高さが母点の高さと同一となります。

なお、Wikipedia では分割された領域に色を設定している説明ですが、ここでは地形生成に用いるため、乱数で生成した高さを設定しています。

参考にしたソース コードは以下の通りです。
libnoise: http://libnoise.sourceforge.net/
(module にある voronoi.h/voronoi.cpp)
Blender: http://www.blender.org/
(blenlib にある BLI_noise.h/noise.c)
各種パラメータによる変化を 2D テクスチャで見たい場合には、以下のサイトが分かりやすいです (ソースコードも公開していますが、Blender とほぼ同一です)。
The Nugget Mines: Voronoi Noise

距離メソッド

僕のコードの基本形は libnoise に類似していますが、libnoise の Voronoi クラスでは、Blender にあるような距離の測定方法の変更が行えません (ユークリッド距離^2 で固定)。僕のコードでは Blender にならい、複数の距離メソッドを用意し、delegate で変更できるようにしています。
実装してある距離メソッドは以下の通りです。
  • Squared: ユークリッド距離^2 (XNA での Vector3.DistanceSquared() に相当)
  • Real: ユークリッド距離 (XNA での Vector3.Distance() に相当)
  • Manhattan: マンハッタン距離
  • Chebychev: チェビシェフ距離
  • Minkowski: ミンコフスキー距離
各距離の意味は、以下がまとめられていて分かりやすいと思います。
CatTail Wiki: 類似度と距離
なお、上記は統計解析で用いられる SAS というアプリケーションのための Wiki です (関係する人で知らない人はいないのでは?)。実用経験はないですが、僕も訳があり多少かじっていたりします。

以下、距離メソッドを変更した場合での地形の雰囲気を画像で並べます。後述する母点選択方法は Distance0、Frequency は 1 で固定。また、Real は除外。

Squred
Manhattan
Chebychev
Minkowski (p = 0.5)
Minkowski (p = 4)

以上、違いを認識しづらい画像になってしまいましたが、Squared を基本に比較すると、Manhattan や Chebychev はより矩形に近い分割に見える気がします。Minkowski だと領域がより複雑な形状となるのでしょうか。

母点選択方法

libnoise の Voronoi クラスでは、Blender にあるような母点の選択方法の変更が行えません (常に最も近い母点を選択する)。僕のコードでは Blender にならい、母点の選択方法を変更できるようにしています。Blender では距離の選択方法と呼ぶのが正しいですが、僕のコードでは母点の選択方法です。

実装してある母点選択方法は以下の通りです。
  • Distance0: 最も近い母点を選択
  • Distance1: 2 番目に近い母点を選択
  • Distance2: 3 番目に近い母点を選択
  • Distance3: 4 番目に近い母点を選択
  • Difference21: Distance1 と Distance2 が示す各母点の中間点を母点に選択
  • Difference32: Distance2 と Distance3 が示す各母点の中間点を母点に選択
  • Crackle: よく分からずに実装
Difference21 と Difference32 については、Blender では距離の算出のみで、そこから高さを乱数で算出するようなことをしていないため、自分で勝手に中間点を選択するようにアレンジしました。

以下、母点選択方法を変更した場合での地形の雰囲気を画像で並べます。距離メソッドは Squared、後述する Frequency は 1 で固定。


Distance0
Distance1
Distance2
Disntance3
Difference21
Difference32
Crackle
より遠い母点を選ぶことで、より粗い分割が行われるように見えます。Difference21、Difference32 については、勝手なアレンジをしたので何とも言えません。Crackle は理解していないので更に何とも言えません。

Frequency による調整

Voronoi クラスの Frequency プロパティは、Voronoi 分割を行うまえに、指定した (x, y, z) に対して乗算されます。上手く表現する方法が分からないのですが、名前の通り、生成する値の幅を調整する役目を果たします・・・と言えば良いのでしょうか・・・。

以下、Frequency を変更した場合での地形の雰囲気を画像で並べます。距離メソッドは Squared、母点選択方法は Distance0 で固定。

Frequency = 1
Frequency = 4
Frequency 値を大きくすると、より狭い範囲で分割を行うことになります。

距離を高さに足す

Voronoi クラスの DistanceEnabled プロパティを true にすると、算出した高さに対し、その算出の元となった距離を足します。
つまり、母点に近い位置では算出した高さのままであり、母点から遠のく程に (領域の境界へ近づく程に) 高さが上がる状態となります。

以下、DistanceEnabled を変更した場合での地形の雰囲気を画像で並べます。距離メソッドは Squred、母点選択方法は Distance0、Frequency は 1 で固定。

DistanceEnabled = false
DistanceEnabled = true
距離を高さに足すことで、クレータのような部分が作られます。

2012/07/21

XNA Infinite Terrain アップ

実行時地形生成の実装を一段落させました。





ソース コードは以下にあります。
GitHub: TestXna / TiledTerrainDemo (tag 20120721)
※ソースコードを大幅に変更しました。下記ページを参照してください。
Blocks Project: XNA Infinite Terrain 改
ソース コードには、複数のノイズ生成および加工方法を組み合わせて height map を生成するためのテスト コードも色々と含まれており、とても見辛い状態であると思いますが、もう面倒なのでそのままにしています。

動画での設定は以下の通りです。
  • 解像度: 1280x720
  • 60fps (録画 30fps)
  • 各 height map: 1025x1025
  • Far plane distance: 300000.0f
  • フォグ範囲: 20000.0f - 30000.0f
  • 地形ロードに使用するスレッド数: 4
  • 地形生成方法: Improved Perlin noise + sum fractal (octave 7)

遠方の描画がちらつきますが、実際にはより手前でフォグをかけるのだと思います。しかし、あまり手前でフォグをかけてしまうと、遠方で地形が動的に生成される様を見せることができなくなるため、ちらつきが多いまま録画しています。恐らく、通常ならば far plane distance を 300000.0f
に設定することはないであろうと思います。

以下、ひとまず簡単な解説です。もう少し詳しく説明しようと思ってはいたのですが、今のところ非常に実装で疲れてしまい、「興味がある人はソースコード見ればいいじゃん!そもそも興味ある人いないかもしれないし!」へ傾きつつあります。

地形の動的ロードの枠組み

本題となる地形の動的ロードですが、やっていることは極めて簡単なものです。
まず、空間をロードの単位とするための仮想的な区画で区切ります。そして、カメラ位置からどの程度の距離まで近づいたらロードするか、および、どの程度の距離まで離れたらアンロードするかを定め、その距離に含まれる区画の位置を算出します。こうして、ロード範囲にある区画について、それが未ロードならばロードします。また、アンロード範囲から外れた区画について、ロード済ならばアンロードします。

基本はこれだけです。より効率の良い方法がありそうですが、他には特に思いつきませんでした。

Improved Perlin noise の採用

先日、Perlin noise が遅いため Simplex noise を実装しましたが (Simplex noise 実装と計測)、この動画では Improved Perlin noise で height map を生成させました。これは、Simplex noise は確かに処理が高速なものの、生成されるノイズがあまり滑らかではない印象を受けたからです。

原点から大きく離れたカメラ位置での問題

数日、カメラ位置が原点から極めて離れた場合 (例えば x = 100000.0f くらい) に、根本的に描画がちらつく問題に手こずっていました。
どうも、カメラ位置の値があまり大きくなると、頂点を View * Projection で変換させた時に精度が大きく落ちるようで、最終的には各地形の位置を原点とした空間を考え、そこからの相対位置で演算することで解決しました。

基本的に、今回の CDLOD に関わるコードでは、地形の位置を原点とした地形空間で頂点を計算するように統一して修正しています。

しかしながら、カメラ~地形間の距離が離れすぎると結局は同じ問題が発生し、遠方にある地形の描画がちらつきます。これを直そうと思ったのですが、そう簡単に解決できる問題でもないと考え始め、放置することにしました。


地形間の繋ぎ目の問題

地形間の繋ぎ目の問題ですが、これは height map 上でのデータ表現と頂点データ表現とのギャップが原因です。

例えば、height map の右端の値は右端の頂点の高さになりますが、その隣の height map の左端は左端の頂点の高さになります。つまり、height map から生成される頂点データを並べる際には、右端の値は、その右に並ぶ height map の左端の値と一致していなければならないということです。これは、上下の並びについても言えることです。

僕のコードではノイズから height map を生成しているので、値の出力範囲を調整することで、上下左右が隣接する height map と重なるようにしています。

ただし、それだけでは、動的な height map 生成ではまだ問題が発生します。ある頂点の法線情報は隣接する頂点の高さから算出しますが、上下左右の境界上では隣接する頂点の一部を得ることができないため法線を正しく計算できず、ライティングを行うと陰影による繋ぎ目が発生します。

僕のコードでは、height map のサイズを上下左右を更に 1 つ分増やし、隣接する height map と 1 つ分だけ重なるように値を生成し、境界上にある法線の算出においても必ず隣接する頂点を得られるようにしています。
しかしながら、完全に繋ぎ目を消すことはできていません。CDLOD の設定次第では、非常にわずかな繋ぎ目が表れる場合があります。

2012/07/17

XNA Infinite Terran (Alpha)

想像したよりも簡単に動的な height map ロード/アンロードの仕組みが作れたので、alpha 版としてニコ動へアップしました。

なお、タイトルでは Infinite と書いていますが、実際には座標を示す float の限界に到達したら落ちると思います。また、Height map をタイル状に繋げてロード/アンロードを繰り返す仕組みを考える場合、無限を想定しなければならず、そこに height map の動的生成を組み合わせると、「結果的に無限のような状態となった」と言うところです。



今回、以下のソフトを用いて、ようやくまともな画質でアップロードできました。
アマレココ: アマレコTV公式ホームページ
つんでれんこ: つんでれんこのお部屋
アプリケーションを画面サイズ 1280x720、60fps で実行し、アマレココで 30fps で録画し、つんでれんこへ Drag & Drop するだけという簡単なお仕事でした。今までの苦労は何だったのか。

現時点では枠組みが完成しただけであり、数多のバグが存在し、また、GC に対する考慮を無視しています (ゆえに、Alpha)。それらの修正を終えたら正式版を公開し、同時にロジックの解説を行う予定です。今回のロジックには元ネタが無いため、わりと真面目に解説する・・・気持ちでいます。

簡単な説明を書いておくと、視点からの距離に応じて必要とする height map を決定し、必要となった時点で Thread を用いて Perlin noise により height map を生成し、後は CDLOD でデータ構造作成と描画を行なっているだけです。

動画では 1025x1025 の height map を生成しています。Perlin noise による height map 生成はかなりの処理負荷を招き、今回のような視点の高速移動を行う場合、Thread で生成処理を並列化しても描画までに追いつかない状態です。
ただし、現段階では、簡易な height map 作成手段として Perlin noise を採用しているに過ぎず、後々は事前準備した height map テクスチャを基本として実装を進めると思います。

2012/07/15

XNA CDLOD Terrain

XNA へ移植していた Continuous Distance-Dependent Level of Detail (CDLOD) の実装を概ね終えました。
CDLOD: Filip Strugar: Oh no, another terrain rendering paper!
デモ コードでは、Perlin noise を用いて実行時に height map を生成し、CDLOD を用いて地形の描画を行なっています。
ソース コード: https://github.com/willcraftia/TestXna/tree/master/TerrainDemo
一応、ざっくりと CDLOD を説明しておきます (深く説明する知識がないのでざっくり)。

CDLOD では、まず quadtree を用いて height map が表す地形情報を level of dedail (LOD) のレベルごとにノードへ分割しておきます。そして、視点からどの程度の距離でどの LOD レベルを用いるかを示すリストを用意しておき、このリストに基いて quadtree から表示対象とするノードを選択します。なお、各ノードは描画で必要とする頂点情報は持たず、対応する height map 上での位置と height map から得られる高さを持つのみです。

続いて、1 つの矩形メッシュを用意しておき、GPU へ矩形メッシュとノードの情報を渡し、vertex shader  で適切な座標へ変換しながら描画します。この時、ある LOD レベルのノードで描画するメッシュが、次に粗い LOD レベルのノードで描画するメッシュの形状に近づくように、視点からノードまでの距離に応じて頂点位置をモーフィングさせながら描画します。これにより、LOD の違いによる繋ぎ目や t-junction の発生がなくなるという仕組みです。

こんな感じでしょうか?なお、地形の分割とモーフィングの説明としては、以下のサイトが分かりやすい気がします。
Mistal Research: GPU Terrain Subdivision and Tessellation
僕のコードは、そのままの移植ではなく、幾つかの改変が加えられています。
一番大きな点には、HW インスタンシングの利用、および、そのためのノード選択ロジックの変更があります (オリジナル コードは各ノードごとに DrawIndexedPrimitives() を呼び出す)。ただし、改善と呼べるかどうかは分かりません。

まず、オリジナル コードでは、単一の矩形メッシュを用意するのみであるものの、描画時には矩形メッシュの中で必要とする区画のみを描画しています。これには、必要十分な細かい LOD のノードを選択するというロジックが関与しています。

あるノードには 4 区画が存在し、各区画には対応する子ノード (より細かい LOD のノード) がぶら下がります。ノード選択において、全区画で子ノードが選択されなければ、そのノードをそのまま選択することになりますが、一部の区画では子ノードが選択される (より細かい LOD が選択される) という状況も生まれます。

ここで、親ノードを N、その子ノードを n0、n1、n2、n3 とし、ノード選択において n0、n1 が選択されるとします。オリジナルでは、N、n0、n1 を選択状態にし、n0 と n1 はそのまま矩形メッシュを描画しますが、N は n0 と n1 の区画を含むため、n0 と n1 に重なる区画を描画しないようにインデックス データを調整してから矩形メッシュを描画します。

一方、僕のコードでは、HW インスタンシングを行うために、n0、n1 が選択されたら n2、n3 も強制的に選択し、N を破棄します。つまり、全ノードを同一メッシュで描画するために、N が矩形ではなくなる状態を回避しています。しかし、これは、定義した LOD レベルの範囲を超えたノード選択を行なっている (選択されるノード数がオリジナルよりも増える) ことにもなります。

細かい変更としては、Draw() 呼び出しごとに渡さずに済む Effect パラメータを事前に設定したり、選択する LOD の範囲を定めるロジックをわずかに簡易な物にしたりなどしています。

ただし、改善すべき点は、簡単に気付く所でもまだまだ残されています。僕のコードでは (オリジナルもそうですが)、view frustum とノードの AABB との単純な交差判定で frustum calling を行なっています。これには、事前に球同士の交差判定で除外するなどの余地があります。また、CDLOD で期待する描画を維持するには、かなり遠方まで距離と LOD レベルの関係を定義をしておく必要があり、僕のコードではそれをそのまま用いているために、視覚範囲を越えた部分のノードまで判定してしまいます。

一応、動画を YouTube とニコニコ動画へ上げてあります。ニコ動版は、どうもアップロード時に画質が劣化してしまったようで (もうニコ動は投稿が面倒すぎて直す気が起きません)、YouTube 版を見た方が良い気がします。





実行画面におけるワイヤフレームの立方体は、ノードの AABB を表しています。その色は LOD レベルを表しており、レベル %= 4 で 4 階調にしたものです。ある地点に近づくにつれ、より大きな AABB がより小さな AABB に置き換えられていく (LOD レベルが切り替わる) 様子を見ることができると思います。
また、ワイヤフレーム表示時には、モーフィングしていく様子を見ることができると思います (解像度の問題で若干見辛いですが・・・)。

Height map は、実行時に Perlin noise で作成しています。動画でのサイズは 4096x4096 ですが、実際に使う場合には、(512 + 1)x(512 + 1) や (1024 + 1)x(1024 + 1) などにするかと思います。なお、CDLOD では height map をテクスチャとしてシェーダへ渡す必要があり、このテクスチャは SurfaceFormat.Single となるわけですが、 XNA HiDef プロファイルで扱える最大サイズが 4096x4096 であるらしく、その限界で heigh map を作成してみたという所です。

地形描画は、その高度情報に応じた色分けとブレンドで済ませています。リアルな地形を望むならばテクスチャをどのように貼るかを考えなければならない所ですが、今のところ僕には興味が無いため無視しています。

この後は、複数の height map を用いて、実行時にロード/アンロードを繰り返しながら、より大きな地形を描画するためのコードを書こうと思っています。

2012/07/01

Perlin Noise と Height Map

Perlin noise および height map 実装が一段落しました。


上の画像は、Perlin noise を元に 257x257 の height map を 5x5 個生成し、各 height map に対して高度による色付けとライティング (バグっている気がしないでもない) を行って作成したテクスチャを並べたものです。例えば、右下隅の height map をテクスチャ単体で見た場合は、以下の画像となります。


タイル状に繋がる height map は、ノイズを生成する関数 (重ね合わせも含む) が、入力に対して一意のスカラー値を返すことを利用し、各 height map ごとに利用する入力の範囲を区切ることで作成できます。このロジックは libnoise を参考に実装したので、仕組みについては Tutorial 3: Generating and rendering a terrain height map の "Tiling terrain height maps" を読むと早いかと思います。

ソースコードは以下に置いてあります。
GitHub: TestXna / PerlinNoiseDemo
シンプルな fBm、少し複雑な fBm、その他色々なコードをネットで拾えたので、それらも実装して効果を試していましたが、どうにも複数のパラメータをコード上でいじりながら試すのは難しいですね。
で、実装を終えた後もネット上でソースコードを調べていたら、多くの人は以下の書籍を参考にしているのだと知りました。
Amazon: Texturing & Modeling A Procedural Approach Third Edition 日本語版 [単行本]
せっかくなので買おうと思いましたが、価格を見てやめました・・・14,700 円って。中古もあるようですが、どうもこの手の本を中古で買うのは嫌で。英語版ならばもっと安いようなので、どうしようかなぁ・・・と。

次は、Height map を作成しただけでは何の意味も無いので、続いて頂点データの構築と表示を実装しようかと思います。ひとまずは、ごく普通の地形表現で頂点データを作り、3D における height map の状態を確認したいところです。

libgdx いじり

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