2013/02/05

天空光

結局、木の生成はスルーし、天空光・・・と言うのが適切か分からないですが、天空光の処理を実装していました。描画の結果としては、以下のような影を落とすための光の処理です。


基礎的なライティングの問題点
基礎的なライティングとして、拡散色 (diffuse)、放射色 (emissive)、反射色 (specular)、環境光色 (ambient) を定め、シェーダ内で指向性光源 (directional light) の方向から最終的な色を決めるという方法があります。
この方法では、面の法線、光の方向、視点方向のみから色を決定します。つまり、光の到達は考慮されず、光が到達しない面であると我々が認識できる場合であっても光で照らします。

通常、これは大きな問題にはならないと思うのですが、洞窟のような地形の描画において期待しない結果をもたらします。洞窟の奥深くへ行く程、光は到達しなくなることを通常は期待しますが、基礎的なライティングでは、問答無用で洞窟を構成する面を照らしてしまいます。

CPU での天空光シミュレーション
そこで、Minecraft のように、ブロックの各位置への光の到達具合をシミュレートし、テクスチャの明るさを変更するという処理を実装していました。

なお、実装の大枠は自分で考えたのではなく、Terasology (Java) を参考にしています。
Terasology
http://blog.movingblocks.net/blockmania/
他にも幾つか Minecraft クローンのオープン ソースを調べたりしましたが、Terasology のコードが最も充実している、かつ、可読性が高いと感じます。
処理の概念は Terasology と大差ないと思いますが、Minecraft や Terasology とは異なり、僕の実装では上下方向についてもチャンクを無限に広げているため、若干、シミュレーションでの判定ロジックが異なります。また、それらとは元より方向性が異なることもあり、シミュレーション結果を得る前にメッシュを構築して描画してしまい、シミュレーション完了でメッシュを再構築するなどしています。
ゲームとして考える場合は、シミュレーション完了を待ってメッシュ構築すべきかと思います。
処理の流れ
前提として、太陽の位置を真上に固定させて考えます。詳しくは知らないですが、Wiki を見る限りでは Minecraft も同じであろうかと思います。太陽の方向を考慮できればベストでしょうが、恐らく、その場合は実用に耐える処理速度を得られない気がします。

大まかな流れとしては、真上からの光の到達具合を判定し、そこから光の拡散を処理するだけです。
このような処理であるため、グリッドに沿った天空光の処理なのかなと思っています。
なお、非同期処理が前提です。ここに限らず、既にほとんどの処理が非同期ですが。

第 1 フェーズ
あるチャンクの内部について、Y 軸についてどこまで光が到達するかを判定します。光レベルは 0 から 15 の値を用い、0 が最も暗い状態、15 が最も明るい状態を示しますが、このフェーズでは光レベル 15 で処理します。
15 は、すなわち 4 ビットです。つまり、byte で 2 つの位置を管理でき、メモリ節約の恩恵を得られます。
第 2 フェーズ
第 1 フェーズで定めた光レベル 15 の全ての位置から、上下前後左右に隣接するブロック位置へ、光レベルを減衰させながら、再帰的に拡散させます。減衰しながらの拡散とは、光レベルを -1 しながら、0 となるまで隣接位置へ光レベルを設定していく処理です。ただし、拡散先の位置により大きな光レベルが設定されている場合は、既により強い光で照らされているため拡散を止めます。

第 3 フェーズ
第 2 フェーズまでで、チャンクの内部で閉じた光レベルの情報が構築されるので、次に互いに隣接するチャンク同士の光の拡散を処理します。これには、あるチャンクについて、隣接チャンクの外周の光レベルを参照して自チャンクの外周の光レベルを更新し、ここから再度、光の拡散を自チャンク内部で行います。これにより、チャンクの光レベルの設定が全て完了するため、メッシュの更新を要求し、メッシュの頂点に光レベルに基づいた陰影情報を書き込むようにします。
この部分は Terasology とは異なる実装にしていると言うか、Terasology のロジックを理解できなかったので、極普通の思考のまま実装しています。もしかすると、演算回数を減らす工夫が Terasology にはあるのかもしれません。

スクリーン ショット
天空光を処理することで、以下のスクリーン ショットのように、洞窟の雰囲気を出せるようになります。

明るくなっている部分は洞窟の入り口であり、そこから奥へ向かうにつれ光が届かなくなり、ブロックは黒くなります。
実際には、環境光遮蔽 (ambient occlustion) を別途シミュレートしているため、ブロック同士で遮蔽する部分は、更に黒くなっています。
以下のスクリーン ショットのように、強引に地形の内部へカメラを置けば、完全に閉じている洞窟などでは、完全に影で覆われてしまう状態であったりします。

とまぁ、こんな事をしていました。ただ、非同期処理とは言え、随分と CPU が苦しそうにしているので、もう少しコードを改善しなければならないと感じています。

0 件のコメント:

コメントを投稿

libgdx いじり

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