simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

AIに身体性を与えるためのマイクロワールドの構築5(構築といいつつまた構想)

AIに身体性を与えるためのマイクロワールドの構築5です。

ビジュアライゼーションのパフォーマンス確認も一区切りのところまで来ました。
なんとか数百万ブロックまでなら30FPS出そうです。
ということで、モデルの方を詰め始めましょう!

これまで生きてきて、心から尊敬する人物が二人いるのですが、その一人の方に相談したところ
Minecraft のようにチャンク構造で作るのが良いのではないかとのことでした。
世界を2Dの平面として定義して、その平面の一部領域を切り取り、チャンクを定義します。

世界は seed 値によって決まり、無限に広がる世界のほんの一部だけを構築して、チャンク単位の読み込みを行いビジュアライズします。
境界ブロックは隣接チャンクのブロックを見に行くようにし、プレイヤーが移動して遠くなったチャンクはパージされるようにします。
ブロックの情報はメモリ容量について極めて重要な値なのですが、ここは 32bit など、とても小さいデータ構造にするのが大切とのことです。
最初に考えるのは、そのうち 16bit がブロックの種類、残りの 16bit は予約領域というものでした。

メモリ容量について簡単に計算してみましょう。
16x16x256 x 4[byte] = 262,144 [byte]
つまり、1チャンク 0.2 [MB] もメモリを消費することになります。

昨今のPCゲーム実行環境の最低メモリサイズは 3 [GB] ほどなので、最大でも 11,444 チャンクが限度といったところでしょうか?(一辺107チャンク四方程度)
シーン内の最大ブロック数は 750,000,000 (7億5千万) 程度ということになります。

もし1ブロックあたりのメモリサイズを 4byte よりも広げた場合、これらの数字は 1/2, 1/4, 1/16 とどんどん小さくなることになります。
なるべく広大な世界であることもプレイヤーを楽しませる重要な要素なので、1ブロックを占める情報は小さくしましょう。

ここで重要になってくるのが、ブロック同士の相互作用をどのようなアルゴリズムで実現するかというものです。
以前、水が蒸発していき、周囲のブロックに水蒸気を含ませ、やがて雲になって雨が降るといった自然現象を作ろうと構想したことがあります。

もし、空気ブロックに水蒸気が湿度50%ほど含まれているとしたい場合、ブロックには構成要素と割合の情報を記録したいところです。
これをどうやって4byteのデータに格納するのか、また、その水蒸気の情報をどのようにして拡散するのかを考えていきましょう。

まず、ブロックが何のブロックであるのかを識別する ID が必要です。
Minecraft では初期の頃 1byte をこれに割り当てたそうですが、その後どんどんブロックの種類が増えていき、1byte では足りなくなったそうです。
ここは 2byte にして、最大でブロックは 65,536 種類とします。

そのうちの一つ、0 を空気ブロックとした場合、構成要素を 2byte でどのように表現するか考えてみましょう。
割合を 16段階とするのはどうでしょうか? 4bit で表現できます。
空気を構成している要素を最大 16種とするのはどうでしょうか? 4bit で表現できます。
つまり 8bit (1byte) でメイン要素の種類と割合が書き込めます。

例えば、水ブロックであれば、メインの水の割合が 100% あり、その他不純物が無いという表現が可能となります。
もし何かを水の中に落としたとしても、落としたものが何ブロックに存在するかという情報にすることで、ブロック情報を拡張することなく解決します。

ただ、メインの要素をわざわざ割り当てるのももったいないですので、サブの不純物を 2種類までブロックに割合とともに書き込めるようにしてはどうかと考えます。
例えば鉄が多く含まれている岩ブロックがあるとします。そのブロックの 2byte はまず岩ブロックであることを定義し、メインの岩要素の割合を 2種類のサブ要素の割合の余りとする手法です。
不純物としてまずサブ要素の代表の鉄が1/16の割合で含まれているとし、サブが鉄であることに 4bit, 割合が1/16 であることに 4bit、合計 1byte でサブ要素がわかり
第二のサブ要素として、水が 2/16 の割合で含まれているとします。こちらも 1byte の情報が入り、岩のメイン要素は 13/16 の割合で含まれたブロックとします。

なんだか、このような構想は以前行っていたような気がしてきました。
ここで以前の構想の話を思い出してみます。
simplestar-tech.hatenablog.com

「すべてのブロックを大気ブロックとして、水位、密度、割合、圧力、熱という表現にオブジェクトがその割合だけ存在するというのはどうでしょうか?」

なるほど、圧力(応力)と熱(温度)ですか。
もし水ブロックがたい積していた場合、下の方の水の圧力が高まり、もし圧力の低いブロックがあるならば、圧が低い方へ移動するようにして、かなり単純なアルゴリズムで流体の動き出す方向が決められます。
熱もまた、隣接するものを凍らせたり、溶かしたりと、例えば雪原の中に溶岩ブロックがあれば、周囲の土ブロックは熱くなり、雪がそこだけ積もらないで、水や、水たまりが温泉になるなど
ほか、日中の太陽が当たると、一様に地面が温まり、地表の大気温度が上昇したり、雪が溶けだすなどの動きが、温度を見るだけで簡単に定義できます。

ということで、仮決めします。
12bit はブロックの種類(4096種)、4bit はそのブロックの種類における不純物の種類(16種)、4bit はその不純物の割合(16段階)、6bit は圧力(64段階)、6bitは温度(64段階) です。
合計 32bit の int 型として、16x16x256 の四角柱のチャンクを1チャンクとして合計 65536 要素の int 配列をチャンクとします。
配列の実装は3次元配列として、幅(x)、奥行き(z)、高さ(y) ですぐにアクセスできるものとし、ブロックを参照するときはチャンクインデックスとともに、チャンクをまたぐアクセスも可能とします。
チャンクもまた2次元配列とします。
チャンクは幅と奥行きそれぞれ 256 x 256 のチャンクがあるとし、地球一周 4,096 ブロックとします。
チャンク一つあたり 262,144 byte なので、地球丸ごと一つ 17,179,869,184 [byte] (17.2GB) ということになります。

4,096 ブロックで地球一周、1ブロックの幅は 1.732 unit [m] なので、地球一周 7094.272 [m] ということになります。
地球の直径は 2258.2 [m] です。(実際の 13,000,000 [m] と比べると、577倍も小さい惑星となります。)
Unity のカメラの最大深度は 1000 unit [m] なので、フォグの先の隠れた場所にしか循環先は現れないため、直線的に絶対にもう一人の自分を視認することができないということです。

参考イメージ
f:id:simplestar_tech:20171030085617j:plain
4096個先のブロックは 1pixel 以下で実際にカメラには 577ブロック程度しか映らない。

これならなんとかゲーム内のエージェントにとっては矛盾なくマイクロワールドとして機能しそうです。
それでは世界創造のコーディングを行っていきましょう!(次回以降)