マップ画像生成の最近の更新など

最近全然書いてなかったけど,いろいろ変えたのでまとめておくことにしました。

最近の更新はおおまかに分けて次の3点です。

  • パフォーマンスの改善
  • 処理のパイプライン化
  • その他機能追加

パフォーマンスの改善

やることを減らすってのが一番の高速化の方法なので,いろいろ処理を省けるようにしました。 最初の愚直な実装から4つくらいやることを減らしています。

1つ目が更新していないチャンクを再描画しないというもので,これはチャンクの LastUpdate というタグを見て判定しています。LastUpdate の値を region ファイルごとに ファイルにダンプしておいて,次の再描画の時に LastUpdate の値がそれよりも 大きくなっていたら再描画して,そうでなかったら省くという感じになっています。 前の描画内容に上書きするために,もとのファイルを読むという処理が追加されているので, PNG のデコードとか走るんですが,チャンクを再描画するほうがずっと遅いので 問題ないです。あと,更新されているチャンクがあって初めてもとのファイルを開く ことにしているので,画像の範囲の中で更新されているチャンクが1つもない場合は 画像ファイルに触りもしないということになっています。

ただ,これだけだと全部のチャンクの更新を確認しないといけなくて,再描画がなくても そこそこ時間がかかります。なので最初は4チャンクごとにしか見ないことにして, 更新されていたら周りのチャンクが更新されていないか確認することにしました。 なぜそれでうまくいくのかというと,Minecraft の仕様上ポツンと1チャンクだけ 更新されるということはなくて,最低でもプレイヤーの周り半径3チャンク(ただしデフォルトだともっと広い) が更新されるからです。 ただ,最小の回数になるように書くと画像1枚の一辺あたりのチャンク数である 16 で割り切れなくなって 煩雑になるので,割り切れる数でそこそこ大きい数ということで,適当に4チャンクずつということにしました。

あと,ブロックを取得するとき,チャンクのY軸16ブロックずつに分けた部分ごとに palette という 情報があって,そこからブロックを選び出すという感じになっています。 で,この palette は欠けていることがあって,欠けているところはそもそも 描画されるわけがないので,このデータがある Y 座標から見ていくことにしました。 これはかなり簡単なことですが,それまでそのことを理解していなかったのでやっと この段階になって改善されたところです。

最後にこれが一番大変だったところですが,データを必要になるまでパースしないようにしました。 これは主に再描画が必要でないチャンクの処理速度を改善するのに役立っています。 ただ,これはいろいろ NBT の仕様上正しくない仮定をして実現しているので, けっこう汚いです。パーサーを書き直せば正しい実装にできると思うんですが。

処理のパイプライン化

これまではチャンクをスキャンしながら実際の画像描画もやっていたのですが, かなり汚かったので改善しました。今は以下のようなステップに分かれています。

  1. チャンクを読んで色や高さを取る
  2. 描画する

色を取ってくるときに3つレイヤーを用意していて,一番上のレイヤーが 最初の非空気ブロック,二番目のレイヤーが一番上のレイヤーの下に出現する一番最初の 透過ブロック,一番下のレイヤーがそれより下ブロックを非透明になるまで合成したもの になっています。なぜこのようになっているかというと,一番上の透過ブロックからの 距離で二番目移行の明るさを決定するようにしているからです。 より適当にやるなら二番目のレイヤーなしで背景のレイヤーでいいんですが, 海の中に海草があって海底がある,という構成を考えると, 海底と海草の明るさを変えたほうがいいので,このようになっています。 書き忘れていたんですが,同じブロックが連続するときは二番目からは無視されるように なっています。

描画するステップは表面の凹凸を見て色を調整する部分と,レイヤーを合成して実際に描画する部分に 分かれています。 左のブロック,上のブロックの高さを比較してブロックを明るくしたり暗くしたりして凹凸を 表現しています。チャンクの一番左のブロックと一番上のブロックは比較する相手がいないので, そこは適当にその1つ右の関係をコピーする感じになっています。 これはもうちょっと広範囲を見れば精度を上げられる気がしています。 複数チャンクにまたがって比較することは今の所考えてないです。

その他の機能追加

一番大きいのはバイオームが読めるようになったところです。 他はたしかわりとどうでもいい。

参考