[Astro] #176 3D Gaussian Splatting Viewer — ブラウザだけで 3DGS を見て、切って、測って、VR でも見る

[Astro] #176 3D Gaussian Splatting Viewer — ブラウザだけで 3DGS を見て、切って、測って、VR でも見る

はじめに

前回は、不具合の修正でした。Draco の置き場所、VRM が壊れる原因、壊れても戻るアバターを、まとめて直しました。

今回は、新しいツールです。3D Gaussian Splatting(以下 3DGS)のビューアを作りました。ブラウザにファイルを落とすだけで、見られます。

3DGS のビューアは、すでにたくさんあります。そこで、ただ見られるだけではなく、見たあとに使える機能を、いくつか足しました。不要な部分を切る、距離を測る、書き出す、録画する、そして VR で見る、です。

画面

実写のサボテン(143 万点)

3DGS Viewer でサボテンの鉢の 3DGS を表示した画面。1,437,067 点、SH 3 次、323.4 MB、読み込み 2.32 秒、FPS 38

1,437,067 点、SH 3 次の PLY(323.4 MB)です。読み込みは 2.32 秒、FPS は 38 でした(DevTools を開いたままの、1 回の計測です)。

クロップと計測

クロップ箱で片側を削ったサボテンの 3DGS と、2 点間の距離を測っている画面

クロップ箱で片側を削ると、残った側が見えます。これは、断面を計算しているわけではありません。手前側の粒が消えて、奥の粒が見えている状態です。

ヤスデ(11 万点)

3DGS Viewer でヤスデの .splat を表示した画面。116,374 点、3.6 MB、読み込み 0.08 秒

116,374 点、3.6 MB の .splat です。読み込みは 0.08 秒でした。three.js のサンプル(webgpu_gaussian_splat)にあったデータを、使わせてもらいました。サボテンのデータは、NOTE の記事で配られていたものです。どちらも、配布元の条件に従ってください。

動画(PC)

動画(WebXR)

CREDIT

撮影したサンプルデータはsteam studio / 3D SCAN STUDIO iris 様からお借りしています。

Three.js公式サイト:

3DGS とは

3DGS は、点群に似ていますが、中身が違います。

  • 点群は、位置と色だけを持つ点の集まりです。近づくと、隙間が見えます
  • 3DGS は、1 つずつが、大きさ、向き、不透明度を持つ、半透明の楕円の粒です。重ねて描くので、隙間が埋まって、面のように見えます。さらに、見る角度で色が変わります(球面調和関数、SH)

描き方は、次のとおりです。

  1. 粒を、画面上の楕円に投影する
  2. 奥から手前の順に並べる
  3. 奥から順に、半透明で重ねる

作ったもの

読み込み

  • .splat(1 つが 32 バイト:位置、大きさ、色、回転)
  • 3DGS 標準の、バイナリ .ply(SH は 0〜3 次を、自動で判定)
  • ドラッグ&ドロップ、ファイル選択、URL

読み込みは、Web Worker の中で、少しずつ読みます。ファイル全体を、一度にメモリへ載せません。

表示

  • 投影は EWA(楕円の形を、視点に合わせて計算する方式)です。ぼかしの足し込み(+0.3)と、AA 補正(遠景のちらつきを抑える)を、選べます
  • SH は 0〜3 次を、切り替えられます
  • Splat、Solid(楕円を塗りつぶす)、Points(中心点だけ)の 3 モードです
  • 描画解像度のスケール、動的解像度(操作中だけ下げる)、視錐台カリング、極小の粒の除外を、入れました
  • 上方向の向きは、プリセットで切り替えられます。COLMAP と 3DGS の標準は、Y が下向きなので、それを既定にしました

編集と出力

  • 不透明度とサイズのフィルタ
  • クロップ箱(箱の内側を消す、反転も可)と、残った件数の表示
  • 2 点間の距離の計測。実際の長さを入れて、スケールを補正できます(3DGS は、学習時のスケールが不定なので、そのままでは実寸になりません)
  • フィルタ適用後の .splat と .ply の書き出し
  • PNG の保存(1、2、4 倍、背景の透過も可)と、録画(WebM)

カメラ

左ドラッグで回転、右ドラッグか Shift+ドラッグで移動、ホイールでズームです。ダブルクリックで、その点へ注視点が移ります。WASD と Q/E で、飛び回ることもできます。自動回転もあります。

仕組み

内部のデータ

1 つの粒を、GPU のテクスチャに、次の形で詰めています。

  • 位置、大きさ:float
  • 色と不透明度:8 ビット × 4
  • 回転:四元数を、最大の成分を捨てて、10 ビット × 3 に詰める(10-10-10-2)
  • SH:8 ビットに圧縮(符号付きの平方根で、小さい値の精度を残す)

SH なしなら 1 つ 32 バイト、SH 3 次なら、それに 48 バイトが足されます。400 万点の SH 3 次では、GPU 側のデータは 320 MB でした。

並べ替え

並べ替えは、Worker で、非同期にやります。奥行きを float のビット列として見て、16 ビットずつ 2 回の基数ソートで、奥から手前の順にします。視点が動くたびに、新しく並べ直します。

描画

粒 1 つにつき、四角形 1 枚を、インスタンス描画します。頂点シェーダーで楕円を計算して、フラグメントシェーダーで、ガウス分布の濃さを塗ります。ブレンドは、事前乗算のアルファです。

作り方:正しさを、どう確かめたか

3DGS は、描画が少しずれても、それらしく見えてしまいます。そのため、見た目ではなく、数字で確かめました。

  • Worker の単体テスト(23 件):読み込みが元データと厳密に一致するか、SH の各次数、異常なファイルの扱い、ソートの順序、書き出しと再読み込みの往復、ピック
  • 描画の数値テスト(23 件):ブラウザで描いた画像を、独立に書いた CPU の参照実装と比べました。参照実装は、投影のヤコビアンを、解析式ではなく、数値微分で出しています。等方、異方、視点が軸から外れた場合で、平均の誤差は 0.3 段階未満でした
  • 操作テスト(23 件):回転、ズーム、ピック、クロップ、計測、書き出し、PNG、録画
  • VR のテスト(19 件):後述

最大の誤差が 5 段階になる箇所がありました。原因は、ガウスの打ち切りの境界で、1 画素ずれることでした。内側の領域では、最大 1.0 段階です。

テストが見つけた、本物の不具合もありました。頂点シェーダーとフラグメントシェーダーで、整数の精度の指定がずれていて、ページが起動しませんでした。見た目の確認だけでは、気づけなかったと思います。

VR(Meta Quest 3)

WebXR で、VR にも対応しました。VR に対応したブラウザでだけ、ENTER VR ボタンが出ます。

  • 左スティック:前後左右の移動
  • 右スティック:左右で回転、上下で上昇と下降
  • 握る(左右):縮小と拡大
  • 右の B ボタン:最初の配置に戻す

最初は、モデルが、正面 1.6 m、最大辺 1.2 m に、収まって表示されます。

左右の目ごとに、行列を作って描きます。1 枚のテクスチャを、左右で分け合うので、はみ出さないように、目ごとに描画範囲を切っています。並べ替えは、頭の位置を基準に、1 回だけです。

実機がなくても確かめられるように、偽の WebXR セッションを作って、左右の目の描画が、参照実装と合うことを確かめました。視野が非対称な場合も、含めています。そのうえで、Meta Quest 3 の実機でも、動きました。ただし、最初から動いたわけではありません。次の節のとおり、PCVR(PC につないで使う方式)では動いたのに、Quest 単体のブラウザでは真っ黒でした。

Quest 単体で真っ黒になった件

PCVR(Link)では、問題なく動きました。ところが、Quest 単体のブラウザでは、VR に入ると、真っ黒のままでした。2D の画面では、同じ Quest のブラウザで、ちゃんと表示されていました。

最初は、性能の問題かと思いました。でも、小さいファイル(11 万点)でも黒でした。性能が足りないだけなら、遅くなるはずで、黒にはなりにくいはずです。

原因を探るために、診断を入れました。VR を終了したあとに、フレーム数やエラーを表示する、というものです。デバッグ画面がない Quest 単体では、こうして、終了後にページに残すしかありませんでした。PCVR では、フレームも描画も、エラーなしでした。

それでも、単体では、診断を入れた版でも黒のままでした。そこで、背景を塗るだけの、最小のテストページを作って、レイヤーの設定を 1 つずつ変えて、比べました。

設定結果
alpha: false(ビューアと同じ設定)真っ黒
alpha: false、foveation なし真っ黒
設定なし(既定値)表示された
alpha: true(ほかはビューアと同じ)表示された

違いは、XRWebGLLayer の alpha だけでした。alpha: true にすると、Quest 単体でも、表示されました。画面は、背景を α=1 で塗っているので、見た目は、不透明のままです。

「透明にしないのだから、alpha: false のほうが軽いだろう」と考えて入れた設定でしたが、実機で確かめていませんでした。PCVR では、この設定でも動いていたので、気づけませんでした。

教訓は、2 つです。PCVR で動いても、単体の Quest で動く保証には、ならないこと。そして、デバッグ画面がない環境では、最小のテストページで、設定を 1 つずつ変えて比べるのが、早いことです。

大きいデータの検証

400 万点、SH 3 次、約 1 GB の PLY を、合成して読ませました。

  • 読み込みは、最後まで通りました(エラーなし)
  • GPU 側のデータは、320 MB でした
  • 並べ替えは、約 0.2 秒でした

読み込みの時間は、184〜260 秒かかりましたが、この環境が、ソフトウェア描画だったためで、実機の値ではありません。

SH の圧縮の処理が、無駄に重かったので、直しました。Worker のパースだけを、Node で測ると、400 万点の SH 3 次で、85 秒から 34 秒になりました(Node の値なので、実機の Worker とは、絶対値が違います)。

確認できたこと

  • 実データ(サボテン 143 万点、ヤスデ 11 万点)で、表示、クロップ、計測、録画が動く(録画ファイルは WebM 形式です)
  • Meta Quest 3 で、VR が動き、左スティックで移動、右スティックで回転と上下の移動ができる(PCVR と、Quest 単体のブラウザの、両方)
  • 描画は、独立の参照実装と、数値で合う
  • 400 万点、約 1 GB の PLY が、落ちずに読み込める(ソフトウェア描画で)

未確認のこと・未対応のこと

  • 400 万点を超えるデータや、実機の GPU での速度は、測っていません。物によっては、重いと思います
  • VR の、握りと B ボタンの動作は、確かめていません。VR での FPS は、測っていません。Quest 単体で、サボテン(143 万点)は、設定を軽くすればギリギリ動きますが、コマ落ちしました(体感で 10〜20 FPS 程度。測った値ではありません)。軽いデータなら、問題ありませんでした
  • 他のビューアと比べて、速い、きれいといった比較は、していません
  • .spz、.ksplat、圧縮 PLY には、対応していません。.spz の新しい版は、データを ZSTD で圧縮していると、Niantic の説明にありました。ブラウザの標準では解凍できないので、対応するなら、デコーダを入れる必要があります
  • VR の中の編集機能(計測やクロップ)と、コントローラの表示は、ありません

おわりに

3DGS は、点群に似ていても、描き方は、まったく別でした。投影の式、並べ替え、SH を、自分で書いて、数字で確かめたことで、仕組みが、だいぶわかりました。

見た目が、それらしく出ても、正しいとは限りません。参照実装と比べる、という確かめ方は、3DGS に限らず、使えると思います。

今は、v1 です。重いデータに当たったり、気になるところが出てきたりしたら、そのときに、バージョンアップします。