過去の独りごち/独りごとはこちら
過去のJavaアプレットは
こちら

 

11/11 ヤツは何をしてたんだい?

 だんだん寒くなってきました。 もう気分は冬。 鍋〜、すき焼き〜! …牛海綿状脳症に関する疑惑で世の中騒然としていますが、冬はすき焼き、決して命がけではありません。って、2001年の流行語大賞にノミネートされるんじゃないかと。

 やっぱり命がけはイヤなので、牛肉を買い控えているんですが、ひき肉も恐ろしくて買えない。 だから餃子も作れなければロールキャベツやピーマンの肉詰めもNGです。 ああ、なんたる事… よって豚バラですかねぇ。一応肉じゃがとかも豚でそれっぽく作れるし、丼モノがしたければ鶏がある。 好みの問題も有りますが、牛じゃなきゃどうしてもダメという料理は食べれなくても別に困らないような料理が多いし。うんうん。 そうだ、秋だし茶碗蒸しとかが食べたいなぁ…

 さてさて、気分は冬といいつつも、今年もビデオカードの秋がやってきました。今年はGeForce3Ti vs Radeon8500。

 以前においては秋にメジャーバージョンアップした製品(Riva128とかGeForce256とか)が出て、3月にマイナーバージョンアップした製品(RivaTNT2とかGeForce2とか)が出ていた感があるのですが、今では逆転してしまったようで、面白くない。ちぇ。

 ところで…え〜、XFC2nd報告が、実はまだでした(^^;)

 というわけで、今回はXFC2ndで発表しました、ランドスケープエンジンの手軽な実装について、WWW上で再発表したいと思います。

 ここでいうランドスケープエンジン、というのは言うまでも無く?僕が長いこと取り組んでたのになんだか全然結果の見えてこない謎のエンジンである、SXLandscapeの事なんですが、それをサンプルとしてランドスケープエンジンをいかにラクして作るかを検討していきたいと思います。

 くれぐれも注意したいのは、利用するのが手軽なのはもとより、実装するのも手軽であるという点です。過度な期待はなさらぬように…。

0.ランドスケープって?

 言ってみれば「景観」。 地下や建物内のような閉塞された空間てはなく、無限に広がる地表をPCで、リアルタイムで表示する事を目指します。今までに多くのアプローチが取られていますが、今回の発表はなんら目新しい手法を提案するものではありません。 

 しかし、聞いたことはあるけどやった事は無い、という方は多いのではないでしょうか。 そして、ただ地面さえ描ければそれでいいのかというとそうではないでしょう。地面に生えた樹木や、地上にある建物、そうしたものを描き入れる手段まで模索して初めて有用なランドスケープです。

 …まずは、ランドスケープエンジンを使うとどんな事が出来るのか、デモをどうぞ。

 

sxbb_test04b.zip
 
(1.09MB ソース+バイナリ)

 このデモでは飛んでるメカとかを除けば

  • 地面の表示
  • 水面の表示
  • 地形に載っている物体(このデモでは樹木)の表示
  • フォグの設定

 が出来ます。上3つがSXLandscapeの主な仕事です。

 

 さて、先に述べた「地下や建物」を表示する場合とランドスケープを表示する場合では、ちょっと考え方を変えなきゃならない部分、というのがあります。それは高速化についてです。 何も作ってないのに高速化とは何事か!と仰る方は置いといて(^^;) 後の説明を読むと分かるのですが、高速化の指針という物がデータ構造と密接に関わってくるので、先に高速化の指針を述べてしまいます。

 地下や建物内といった閉塞された空間は、言うまでも無く遮蔽物が多いですよね。 つまり、遮蔽物の向こう側は描かなくて良いわけです。この事より、描画に関してはBSPなどの手法で高速化を行うことが出来ます。 一方、ランドスケープはどうかといいますと、遮蔽物がアテにできないので、

  1. 視界に入らない物は描かない
  2. 遠くにあるモノは適当に描いていい

 というアプローチで高速化していく事になります。 ちなみに、2はいささか面倒なので、SXLandscapeではとりあえず1だけしか実装してません(え〜

1.まずはハイトフィールド

 そんなランドスケープの表現に適したデータ構造、というのがやはり有るものでして、SXLandscapeではハイトフィールドというデータ構造を採用しました。

 ハイトフィールド、というのは直訳すると高さの場。2次元配列に高さデータを突っ込んで、それをもとに地形を作成するわけです。 配列をXZ平面と見立て、配列に入ったデータをY値とするのです。



Fig.1 ハイトフィールド

 とりあえずこれで表示まで実装する事はできますよね。地形データ全体を1個のメッシュにぶち込んで、あとはレンダリングするだけです。簡単簡単

 …まて!

 と思った方も多いでしょうね(^^;) そうですね。地形データはデカい事が多いので、100万トライアングルとかのデータが平気で作られてしまうでしょう。 そんなもんを一気にレンダリングしろといわれても、ノロくてかないません。

 そういうわけで、先ほどに述べた、視界に入らない物は描かないという手段に出ます。



Fig.2 視界に入ってる部分だけ描けばいいんです

 ここで、Fig.2の赤い三角形に入った部分だけを切り取って描画したいのですが、視界は3角形じゃないですね。 視界は視点と遠方クリッピング面で構成される四角錐です。近方クリッピング面まで考えると6面体ですが、とりあえず四角錐で近似という事で、5個の頂点を持つ事になります。 視界と地面の関係は、真上から見下ろすとFig.3のように。最大で5角形として視界を考えないといけません



Fig.3 視界は3角形なんかじゃない

 しかも、これでは地形の高さ方向を考慮していません。 下を見ずに上空を飛んでいる場合や、上の方を見上げている場合は、視点の真下にある地形は書かなくて良いはずですよね。

 確かに、視界を地面に投影して、その影の射している部分を切り取る、という方法は、高さ以外の方向に関してはムダ無く切り取りを行うことが出来ますが、高さ方向を考慮してないし、多角形で塗りつぶされる部分を求めたりするのは面倒です(^^;) 

2.次に、quadtree

 というわけで、もうちょっとマシな方法として、quadtreeを導入する、というテがあります。

 quadtreeというのは、日本語で言うと4分木、地形全体をトップノードであらわし、地形を4つに分割した物をそれぞれの子ノードに入れます。分割後の地形を更に4つずつに分割し、それぞれを孫ノードに入れます…というのを分割後の地形が適当な大きさになるまで繰り返してツリーを作ります。

 

Fig.4 quadtree

 quadtreeは可視判定に便利に使うことが出来ます。

 例えば図の場合、ノードAaの可視判定を行う際には、トップノードから辿って、

  1. トップノードは視界に入っている→ノードA,B,C,Dを調べる
  2. サプノードAが視界に入っている→ノードAa,Ab,Ac,Adを調べる
  3. ノードAaは視界から外れている→ノードAaは見えない

 というように行います。 ツリー構造なので再帰が随所に散らばるコードになりがちですが、我慢我慢(^^;)

 で、各ノードの可視判定には、視界(角錐)と、ノードに外接する立方体の交差判定を行うことでOK、と。各ノードのサイズを小さく取るほど細かく地形を切り取れますが、可視判定の時間が長くなるので、この辺りはサジ加減が重要です。

 ちなみに、サンプルではノードのサイズは8x8マスという事にしています。

3.そして、交差判定

 さて、実を言うとですね、この視界と各ノードを示す直方体との交差判定が曲者だったりします。なぜかというと、交差条件として

  • 視界の中に直方体を構成する点のうち1つ以上が入っている

 という事を交差条件にするのが一見、一番簡単ですし、計算コスト的にも宜しいのですが、これだとこういう状態に対応できません



Fig.5 さっきの条件では、交差してないと判定される例

 …これが結構良く起こるもので、ボコボコと地面に穴があきます。

 というわけで、

  • 「片方の多面体のいずれかの稜線が、他方の多面体いずれかの面と交差している」 
  • 「片方の多面体が他方の多面体を包んでいる」

 こんな感じで真面目に交差判定を行う…と、計算コスト的にイヤです。当然ながら、各可視ノードに対しては少なくとも1回、交差判定を行わないといけないので、結構バカになりません。正直にレンダリングした方が早いと言うのでは目も当てられませんし(^^;)

 そういうわけなので、SXLandscapeではノードを立方体ではなくて、大胆に球とし、視界を円錐として、球と円錐の交差判定を行わないと行うことにしました。 球と円錐の交差判定は、距離と角度で簡単に行うことが出来ます。 直方体が立方体に近ければそれほどロスは無いのですが…やっぱり勿体無いかな(^^;)

4.テクスチャを貼ろう

 さて、地面の表示はこれでOK…と言いたいところですが、まだまだ。

 高さデータしか与えてないので、地面がのっぺらぼうなんですね。

 そういうわけで、テクスチャ座標も与えます。ここで注意したいのが、先ほどまで考えていた高さデータというのは、XZ平面をグリッド状に分割した時の、グリッド上の一点についての情報という事です。 一方で、テクスチャ座標は一点についての情報だけしか与えないと、地形全体について連続したテクスチャでないと貼れなくなってしまいますよね。 そういうわけなので、グリッドのセルについての情報という事で、セルの四隅のテクスチャ座標を与えます。 こうする事で、各セルでテクスチャ座標が不連続でも貼っていくことができるようになりました。

 以降、セル、という言葉をXZ平面の一部で、高さ情報の配列のうち、一つの要素でカバーされる範囲を指す言葉として用います。



Fig.6 地名データとテクスチャ座標

5.物体を置こう

 テクスチャも貼って、一応地面らしくなったという事で、木などを置くことを考えます。

 一番単純なアプローチとしては、各セルに、高さデータと一緒に整数値の物体IDでも埋め込んでおくことです。 レンダリング時に、可視ノードに含まれる全ての物体IDと、その物体がどこに置いてあるかという情報を書いたリスト(可視物体リストとでも呼びます)を出すようにすれば、あとはIDと位置情報にしたがって物体を置けばOKです。 ちなみに、SXLandscapeでは物体リストの作成までしかやらないので、アプリケーション側でIDを読み取って自前で物体を描画しないといけないのですが、代わりにどんなものでも地面の上に置ける、というメリットがあります。

 しかし、本当にこれだけでは複数セルにまたがるような大きな物体は表現できませんね。そうした物体は、物体の載っている複数のセルのうち、一つのセルでも可視ならば、表示しなくてはなりません。



Fig.7 複数セルに跨る物体は厄介だ

 そういうわけで、IDの使い方を考えます。

  1. その物体が木であるとか、洞窟の入り口であるといった物体の種別を示す情報(種別ID)を用意する
  2. その物体が何番目の木なのか、といった個体を識別する情報(個体ID)の両方を用意する
  3. 個体ID別に、位置情報を用意する
  4. 同じ個体IDを持った物体は、異なるセルに配置されていても、ひと続きの同一の物体と見なす。
  5. 同じ個体IDを持つ物体は、可視物体リストに1回しか登録されない

 というようにして、複数セルに跨るような物体をつつがなく描画できるように配慮します。現在のSXLandscapeの実装ではDWord値であるIDの上位2バイトでも3バイトでも、好きなだけを種別IDとして、下位1バイトでも3バイトでも、好きなだけを個体IDとしてくれ、というなんだかなげやりな事になっています(^^;)

6.マルチレイヤーで広がる世界

 さて、ここまでの実装で、一応木や建物の生えた地面を描画する事はできるようになりました。

 しかし、これじゃ水面が描けませんよね。

 そこで、ランドスケープ2枚重ね!といきましょう。 水面を表現するSXLandscapeオブジェクトと、地面を表現するオブジェクトを一枚ずつ配置すればヨシ。 

 しかし、水面というのは水のある所にしか出来ない(当然)ので、水の無い所は透明な部分になって欲しいですね。 そこで、セルの可視属性をつける事にします。

 応用として、面の裏表の反転したSXLandscapeオブジェクトを上下向かい合わせに配置することで、洞窟なども作成できます。SXLandscapeではセルに、「面の反転」属性も設定できます。

7.最後に…使用時の注意

 「手軽な」と言った割には随分と長丁場になってしまいましたが、以上でランドスケープエンジンの実装については終わりです。

 以降はランドスケープの描画時の注意などを。

 ランドスケープの描画において、無限に遠くまで描画する、というわけにはいくらなんでもいきませんので、適当な距離で描画を打ち切る必要が出てきますね。 このとき、フォグをかけないと地平線の向こうからいきなり山が降って湧いたように見えて、実にみっともないです。

 さて、このフォグ、Direct3Dでは3種類の掛り方があります。

  1. D3DFOG_LINEAR…フォグ開始距離から終了距離までにフォグを描ける、元の色に対するフォグ色の濃度はその区間を線形に変化
  2. D3DFOG_EXP…1 - exp(-濃度・距離) でフォグ色の濃さが決まる
  3. D3DFOG_EXP2… D3DFOG_EXPを二乗した濃度になる

 現実の霧に一番即しているといえそうなのは、D3DFOG_EXPです。 なぜなら、100メートル進むごとに霧によって視界のうち1/2が霧に隠れるとしたら、200メートルでは1 - (1/2 * 1/2) で3/4が霧に隠れることになるはずですね。 ですから、霧の濃度が至る所で一定ならば、フォグ色の濃さは距離に対して指数的な変化をするはずです。

 しかし、これだとシーン全体が燻った感じになって、ゲームとかに使うには面白くないんです(^^;) そこで、フォグ色の濃さにもう少しコントラストが付くように、指数を取った後に2乗したのが、EXP2です。 自然なフォグにちょっとした味付け、という感じで。

 けれど、これでも地平線から突然現れる地形を隠そうとするには、高め目の濃度を設定しないといけません。 そういうわけなので、確実に隠したい部分だけ隠したい向きには、LINEARが一番、という事になります。確かに、情緒という面ではイマイチですが、サポートしている環境も多いので、最も無難な選択でもあります。

 久々に長文を書いたので、ちょっと疲れました(^^;) 以前は週1本くらいの割合でこのくらいの分量書いてた時期も有ったような気もしますが、あの頃は若かった…のかなm(__

 …ちょっと雑記。メール世代のコミュニケーションについての雑感(大袈裟

 一人でメシ食らうのも淋しいので知人にお食事に行こうメール出を出したとしましょう。

 そして、返事まだかな〜まだかな〜まだかな〜とした結果、遂にメールがっ!というわけで急いで受信トレイに目を移すと

SANDMAN 様

突然のメール、失礼いたします。
○○編集部の××と申します。

<要約>貴殿の作成されたソフトをウチの特集に掲載させてちょーだい</要約>

 なるメールが来たとして、貴方はいいっすよ(^^)と応えるでしょうか。 やっぱり電話で済ませられる用事は電話の方がいいのかも…と思ったり。仕事で真面目にメール作ってる人がかわいそうだ。

 しかし、こんな場合は後で冷静になってから返事をつければ言い訳ですね!

 で、散々待って知人から「ごめん、今日は行けません」メールを貰ったとしましょう…

 極めて冷静に、「申し訳有りませんが、掲載の許可をいたしかねます、なぜなら貴誌の特集の趣旨が…そもそもフリーウェアの文化と言う物は…(以下256Kバイトくらいの文句)」 メールを編集部の方に送ってしまいそうですね。そういう時は結局メールでも電話でも変わらない(w

 兎にも角にも、こういう時に米を研ぐ水の冷たさといったらまた格別ですm(__

 さて、ここいらへんでDirectX8版dddd.pasであるところの、DG-CARADの予告をしますか。

 現在実装が終わっているのは

  1. 楽チンな初期化・解放処理
  2. テクスチャのカプセル化…ロック,自動退避/復元,BMP、QDA内のBMP、ストリーム、TBitmapオブジェクトからの画像読み込み
  3. VertexBuffer、IndexBufferのカプセル化…ロック,自動退避/復元
  4. SXLibのDirectX8化の完了

 といったところです。VertexBuffer、IndexBufferの自動復元もライブラリ側で行うようになった、というのが新しい。 また、テクスチャの自動復元も、今までのようにLoadなんとかメソッドによる結果だけ復元するのではなく、ストリームから読み込もうがTBitmapオブジェクトから読み込もうが、あまつさえロックして中身を書き換えていても自動復元しやがります。

 また、画像の読み込みの際には、テクスチャのサイズは読み込み元から自動的に計算され、テクスチャを再生成します。今までは未知の画像が入力される際には、画像のヘッダを読んで、原画のサイズに合わせてテクスチャのサイズを決定しなくてはいけなかったんですが、その手間が一切なくなりました。

 更に、SXLibのDirectX8化も行いました。 SXファイルから読んだメッシュをグリグリ回してます。 移植作業はかなり楽で、1時間ほどで全部終わりました。特にSXLibのTSXMeshは、VertexBufferの自動復元とかをDGCaradがやるようになったので、非常に簡素な実装になりました。この事は、SXLib使わない方々にもDGCaradの使用によるメリットがある事の一例と言えるのではないでしょうか。

 ただ、何度も何度もいっているようにDirectDrawが使えないので、その辺りの苦情はMicrosoftにいってください(^^;) けれど、TBitmapオブジェクトからの読み込みができるので、文字の描画なども十分可能です。実はDGCarad動作テスト用に最初に作ったのが、2chから探してきたAAをテクスチャに貼るプログラムだったり(ぉ

 というわけで、物凄く困る状況というのは背景用に、画面を一杯に埋めるような画像を作る際、勿体無い思いをするくらいでしょうか(^^;)

 まだ他にも色々とリリース前に作ることを予定しています。詳しくはQuadruple D MLにて!

 ともかく、待っててくだされ〜

 

Taku Hayase(SANDMAN)

戻る