11 月 3 日、Android のタブレットを買った。Galaxy の一台で、GPU は Mali-G615、メモリは 4GB。自分のお金で買った、人生初の Android である。
高級機ではなく、あえて 4GB の普及機を選んだ。多くの人が手にしているのは、こういう機械だからだ。普及機でどれだけ動くのかを、自分の目で確かめたかった。
身の回りは Mac と iPhone と iPad ばかり。個人で作っていると、手元に無い機械のことは、どうしても想像で補うしかない。そこへ一台やって来たら、もう触らずにはいられなかった。2.4 は、その一台と過ごした 6 日間の記録である。
11 月 3 日、WebGPU が動いた
これまで Android は、念のため WebGL2 で動かしていた。8 月に、ある Android の機種で移動中に地図が黒くなる報告があり、大事を取ったのである。
タブレットが届いた日に、まず GPU の診断ページ(/check/)を作った。WebGPU か WebGL2 か、アダプタ、機能、上限、試しの描画を一枚で見られる。何も外へ送らない。
/japan/ を WebGPU で開くと、ちゃんと動いた。しかも PLATEAU の建物は WebGL2 より速い。その日のうちに、Android も WebGPU で始めるようにした。2.3 で入れた映り込みや霧などの効果も、これで Android で見られる。万一のときは ?gl2=1 で WebGL2 に戻せる。
ついでに、ノート PC では内蔵と外付けのうち速い方の GPU を使うようにした(powerPreference の既定が "high-performance")。Windows の実機でも確かめた。
実機でしか見えなかったもの
動いたら、次は気持ちよく動かしたい。タブレットで触ると、Mac では見えなかった伸びしろが次々に見つかった。HUD(?hud=1)にフレームの時間の使い道を部位ごとに出し、宝探しのように一つずつ当たった。
- 解像度が下がらない。PLATEAU の上を飛ぶと 1 フレーム約 120 ms かかるのに、動的解像度は等倍のままだった。タイル型の GPU は、GPU の時間計測を少なめに報告することがある。実時間で 50 ms を超えたら、そちらも見るようにした。
- メインスレッドが 800 ms 止まる。標高を引く関数が、IndexedDB から読んだタイルを手元に残していなかった。動くたびに数十 MB のタイルを読み直していた。最後の 3 枚を残すようにした。
- 近くの建物がにじむ。地球を半径 1 の球として計算しているので、近くの頂点の w が 1e-6 ほどになり、Mali はそれを 0 とみなしていた。クリップ空間を 220 倍してから渡すと、にじみは消えた。Mac の絵は変わらない。
- タイルがメインスレッドを経由していた。タイルの形を、タイルの worker から描画側の worker へ直接送るようにした。メインは目次だけを受け取る。
起動が軽くなった
タブレットでは、最初の絵が出たあと、しばらく画面が待ってしまうことがあった。起動の時間割([boot] timeline)を出して見ると、GPU のプロセスがシェーダを 1 本ずつコンパイルしていて、合わせて 6〜14 秒かかっていた。その間、描画側は待たされる。
そこで、地球と地形だけを待って先に描き、残りの層はできた順に現れるようにした(進行描き)。要るパイプラインだけを先に作り、残りは後に回す。固まる時間は 6.5〜6.9 秒から 1.0〜2.1 秒になった。Mali ではシェーダの使わない枝を生成時に落とし、コンパイルを短くした。焼いた文字を IndexedDB に残して、次の起動で焼き直さない仕組みも入れた(?glyphdb=1)。
京都から日本列島へ
デモの一つに、京都の街から日本列島の全体まで一気に引く滑走がある。タブレットでは最初の 8 秒が 8〜10 fps だった。
犯人の一人は、意外なところにいた。フレームが空くと、飛行中でも「着地した」とみなして URL を書き換えていた。すると Android の Chrome がアドレスバーを描き直し、それが GPU のプロセスを 0.1〜6 秒ふさぐ。20 秒の間に 102 回、合わせて 11.4 秒である。飛行中は URL を書かず、着地まで待つようにすると、コマ数は 167 から 227 に増えた(約 36% 増)。
もう一つは地形である。地形を消すと約 60 fps 出た。動いている間だけ、地形を粗いメッシュで描くようにした。4GB 機では、線が引く標高も軽くした。
丸の内からレインボーブリッジへ
最後に残ったのは PLATEAU の建物だった。丸の内からレインボーブリッジへの滑走は、最初の 6〜8 秒が 12〜18 fps。東京の真ん中は、それだけ描くものが多い。
そこで、動いている間だけ少し手を抜くことにした。止まればすぐ元の絵に戻るので、見た目は変わらない。
| 動いている間だけ | 滑走 4〜12 秒のコマ数 |
|---|---|
| 低い建物から描かない(6 → 12 → 24 m) | 182・240 → 280・262(約 3 割増) |
| 細かい三角形を落とす(並べ替えだけで GPU メモリは増えない) | 208 → 295 |
| 建物を軽いピクセルシェーダで描く | 平均 236 → 294(約 25% 増、交互に 3 組とも勝ち) |
注:どれもタブレット(Mali-G615)の実測。順に入れていったので、行ごとの起点は同じではない。三つとも、今は全機種の既定である。発動するのは 1 コマ 40 ms を超えてカメラが動いている時だけなので、速い機械では何も変わらない。
ゲームへ、そして地球儀へ
もう一つの大きな新顔が、ゲームとのつながりである。行き先は三つ。Unity、Unreal Engine、そして Minecraft。地球儀の上の街をゲームの中へ持っていき、ゲームで作った物をまた地球儀へ戻せる。
持っていけるのは、地形と PLATEAU の建物(Unity と Unreal には建物の属性と点群も)。範囲は 2〜3 街区くらいを想定している。ゲームの中でタイルを次々に読む仕組みは Cesium for Unity や PLATEAU SDK for Unity がすでにあるので、こちらは「切り出して、ファイルで渡す」に絞った。
約束は一つだけ
三つとも、同じ一つの約束でつながっている。地面の上の一点を錨(経度・緯度・楕円体高、と向き)に決め、すべてをそこから見た東・北・上のメートルで表す。受け渡しは錨入りの GLB(asset.extras.orthoEarth)。Minecraft では 1 ブロックを 1 m にして、錨をブロック (0, 0, 0) に置く。
| Unity 6 | Unreal Engine 5.6 以降 | Minecraft | |
|---|---|---|---|
| 入れ方 | プラグイン(Package Manager) | プラグイン(Plugins/ に zip) | 要らない(ワールドのファイルを直接書く) |
| 地球儀から | メニューの Place export folder… | 同じメニュー | ortho-minecraft がワールドに焼く |
| 地球儀へ | Export selection to GLB… | 同じメニュー | 範囲か差分を GLB に取り出す |
| 軸と単位 | X 東・Y 上・Z 北・m | X 東・Y 南・Z 上・cm | X 東・Y 上・Z 南・1 ブロック=1 m |
| 建物の属性 | Inspector に出る | Details に出る | — |
| 点群 | PLY | LAS | — |
地球儀からゲームへ
出発点は三つとも同じ ortho-export である。範囲を書き出すと、錨・地形・建物・建物の属性表が一つのフォルダにそろう。点群は /japan/pointcloud で読んで、PLY か LAS で落とす。
Unity と Unreal では、メニューの Place export folder… を選ぶだけで、錨を親にして全部が正しい位置に並ぶ。建物は PLATEAU の gml_id ごとに 1 棟ずつ選べて、用途・高さ・住所・浸水などの属性が出る。コードからも b.Get("bldg:usage") や b.MeasuredHeight で読める。PLATEAU の webp のテクスチャは、エンジンが読める jpeg に焼き直して渡す。
Minecraft では、同じフォルダを ortho-minecraft に渡す。地形と建物がブロックに焼かれ、地面と屋根は航空写真にいちばん近い色のブロックで塗られ、建物には床も入る。出てくるのは Java 版のワールド、WorldEdit の .schem、統合版の .mcworld の 3 つ。統合版ならダブルクリックで開ける。錨は出現点で、F3 で出る座標がそのまま錨からのメートルになる。クリエイティブで、昼のまま止まる。
ゲームから地球儀へ
帰り道も三つとも同じ口に着く。Unity と Unreal では、模型を錨の子に置いて Export selection to GLB…。Minecraft では、建てた家を範囲で指定するか、書き出した時のワールドとの差分で取り出す。どれも錨入りの GLB になる。
その GLB を /japan/ にドロップするだけで、元の場所に立つ。Unity で作った模型も、Minecraft で建てた家も、丸の内に並ぶ。map.model() も、位置を渡さなければ GLB の錨で置くようになった。大きな物は glbconv で 3D Tiles にできる。
丸の内で確かめた
見本は三つとも丸の内である。
- Unity 6:並べた地形の四隅が書き出した範囲とぴったり重なり、いちばん高い 238 m の建物は東京ミッドタウン八重洲の位置に立った。わざと錨を 158 m 東・139 m 南にずらした検査用の点群も、4 点とも 1 cm ほどの差で戻った。書き出し直した地形を読み戻すと、頂点数も範囲も元と一致した。
- Unreal Engine 5.8.4:同じ見本で、軸の向きと点群の位置を確かめた。Nanite のメッシュもそのまま書き出せる。
- Minecraft:ワールドのファイルは Node だけで読み書きしている。別の読み手(Amulet)で Java 版と統合版の両方を開き、全部のブロックが一致することを確かめた。
Unity と Unreal のプラグイン(どちらも 0.1.0)は GitHub Release に載せる。Minecraft は ortho-export に入っている。
ほかに入ったもの
- 点群。図郭を並べて継ぎ目なく表示し、読むメモリに上限を設け、分類で絞れる(
map.readPointCloud)。Unity 向けの PLY 書き出し。 - 線と地形。傾けた 3D で太い道路の縁が地形に食われてギザギザになるのと、近くの線が半透明の数珠になるのを直した。
一台で、みんな速く
6 日間で入った変更は 130 を超える。タブレットのために入れた工夫の多くは、そのまま全機種の既定になった。速い機械では何も変わらず、普及機ほどよく効く。普及機を選んだのは正解だった。一台の Android が、みんなの地球儀を軽くしてくれた。
お手元の Android や Windows でも、ぜひ試してみてほしい。気になることがあれば、/check/ の結果を添えて教えてもらえると、とても助かる。
速さだけではない。Android に夢中だった 6 日間も、Cesium へのフォローは止めなかった。最後に Sandcastle の 309 本を撮り直すと(v259・v260)、同じ絵は 226 本から 234 本に増えた。動かない例は 0 本のままである。
| 撮影 | 3 | 2 | 1 | 0 | 何が入ったか |
|---|---|---|---|---|---|
| v2.3.0 | 226 | 77 | 0 | 0 | 前回の最終の数 |
| v2.4.0 | 234 | 69 | 0 | 0 | 地形に載せる図形、3D Tiles の塗り分け、Cesium と同じタイルの選び方、線と点の縁取り |
注:目で確かめた例を含む 303 本(Mac の実 GPU)。全部の絵は Sandcastle のギャラリーで、本物と並べて見られる。MapLibre の公式例も同じ日に撮り直し、91 / 43 / 3 / 0 を保った。
- 地形に載せる図形。GroundPrimitive の多角形が、標高 0 m に置かれて 2.5 km のセント・ヘレンズ山の中に埋もれていた。三角形から輪郭を起こし、地形に沿って塗るようにした。地形の上の塗りが、Cesium の画像の下に隠れていたのも直した。
- 3D Tiles の塗り分け。ClassificationPrimitive の木や建物の箱が、半透明の立体として描かれていた。今は箱の中にある 3D Tiles の面だけが色づく。
- Cesium と同じタイルの選び方。Cesium の画像の層だけ、タイルまでの距離を目にいちばん近い点で測るようにした。尾根の手前の小さなタイルも残すと、霧の例(fog)は 56.6% から 95.0%、点群の例は 58.2% から 91.7% まで本物に近づいた。
- 線と点。エンティティの縁の太さを Cesium と同じ 1 px にし、path の縁(polylineOutline)と光(PolylineGlow の細い芯と淡いにじみ)を描き、点の縁取りを pixelSize の外側に付けた。画像の細かさの選び方で cos φ を二重に掛けていたのも直した。
普及機で軽く動く地球儀の上で、Cesium のコードがまた一歩、本物と同じ絵に近づいた。
そして v3 の仕様へ
これで v2 の開発は、ほぼやり切った。次は v3 の仕様を考え始めている。WebGPU は、パソコンの Chrome と Edge、Android の Chrome、iPhone・iPad・Mac の Safari(26 から)と、もうほとんどのブラウザで使える。Firefox も対応が進んでいる。そしてこの 6 日間で、普及機の上でも十分に走ることが分かった。これまでは WebGL2 でも同じように動くことを守ってきたが、その分、できることを抑えてきた面もある。v3 では、思い切って WebGL2 をやめ、WebGPU 一本にすることも考えている。それでも API の仕様は変えないつもりなので、今のコードはそのまま動く。時期は年末を予定している。どうぞお楽しみに。
公開:@ortho-earth/globe・@ortho-earth/core 2.4.0、@ortho-earth/cesium 0.3.0(globe 2.4 以上)、@ortho-earth/voxel 0.6.2、glbconv 0.3.0、Unity・Unreal のプラグイン 0.1.0。使い方は 2.3 のまま(npm i @ortho-earth/[email protected])。動いている間の描き方を前に戻すノブ(?meshshed=0 など)と、Android で WebGL2 に戻す ?gl2=1 は残してある。