3D Gaussian Splatting(3DGS)ファイルをブラウザで閲覧できるビューアを開発しました。本記事では、3DGSの概要と、通常のPLY Viewerでは表示できない理由、そして専用ビューアの実装について解説します。

デモ:

https://3dtiles-viewer.vercel.app/3dgs-viewer.html?manifest=https://3dtiles-viewer.vercel.app/iiif/ply/manifest.json

なぜ通常のPLY Viewerでは3DGSを表示できないのか

3DGSファイルは拡張子が.plyであるため、一見すると通常のPLYファイルと同じように扱えそうに見えます。しかし、Three.jsのPLYLoaderで読み込んでも正しく表示されません。

通常のPLYファイルと3DGS PLYファイルの違い

通常のPLY(メッシュ/点群):

element vertex 1000
property float x
property float y
property float z
property uchar red
property uchar green
property uchar blue
element face 500
property list uchar int vertex_indices

3DGS用PLY:

element vertex 142000
property float x
property float y
property float z
property float f_dc_0      # 球面調和関数(色)
property float f_dc_1
property float f_dc_2
property float opacity     # 不透明度
property float scale_0     # スケール(楕円体サイズ)
property float scale_1
property float scale_2
property float rot_0       # 回転(クォータニオン)
property float rot_1
property float rot_2
property float rot_3

PLYLoaderで読み込むとどうなるか

Three.jsのPLYLoaderは3DGS用のプロパティを理解できません。読み込むと以下のような問題が発生します:

  1. 色情報の欠落 : f_dc_0, f_dc_1, f_dc_2は球面調和関数の係数であり、RGBカラーではないため、正しい色が表示されない
  2. 単なる点群として表示 : 各ガウシアンの形状(スケール・回転)が無視され、ただの点として表示される
  3. レンダリング品質の低下 : ガウシアンスプラッティングの特性である滑らかな補間が行われない

専用レンダラーが必要な理由

3DGSを正しく表示するには、以下の処理が必要です:

  1. 深度ソート : 視点からの距離で全ガウシアンをソート(毎フレーム)
  2. 2D投影 : 3Dガウシアンを2D楕円に投影
  3. アルファブレンディング : 後ろから前に向かって合成

これらはGPUシェーダーで効率的に処理する必要があり、通常の点群・メッシュレンダラーでは対応できません。そこで、専用ライブラリであるSpark.jsを使用しました。

3D Gaussian Splattingとは

3D Gaussian Splatting(3DGS)は、2023年にSIGGRAPHで発表された革新的な3D表現技術です。従来のNeRF(Neural Radiance Fields)と比較して、リアルタイムレンダリングが可能で、高品質な3Dシーンを効率的に表現できます。

従来技術との比較

技術レンダリング速度品質編集性
メッシュ非常に高速
点群高速低〜中
NeRF低速(秒単位)
3DGSリアルタイム

3DGSの仕組み

3DGSは、3Dシーンを多数の「3Dガウシアン」(楕円体状の点)の集合として表現します。

各ガウシアンは以下のパラメータを持ちます:

  • 位置(Position) : 3D空間での中心座標
  • 共分散(Covariance) : 楕円体の形状と向き
  • 不透明度(Opacity) : 透明度
  • 球面調和関数係数(SH coefficients) : 視点依存の色情報

レンダリング時には、これらのガウシアンを2D画面に投影(スプラッティング)し、アルファブレンディングで合成します。

対応ファイル形式

PLY形式(.ply)

最も一般的な3DGS形式です。標準PLYと圧縮PLY(compressed.ply)の両方に対応しています。

ply
format binary_little_endian 1.0
element vertex 142000
property float x
property float y
property float z
property float f_dc_0
property float f_dc_1
property float f_dc_2
property float opacity
property float scale_0
property float scale_1
property float scale_2
property float rot_0
property float rot_1
property float rot_2
property float rot_3
end_header

SPLAT形式(.splat)

軽量なバイナリ形式で、Webでの配信に適しています。

KSPLAT形式(.ksplat)

Kevin Kwok氏が開発した効率的な形式です。

SPZ形式(.spz)

Niantic社が提案する圧縮形式で、ファイルサイズを大幅に削減できます。

実装の詳細

使用ライブラリ

ライブラリバージョン用途
Three.js0.170.03Dレンダリング基盤
OrbitControlsThree.js同梱カメラ操作
Spark.js0.1.103DGSレンダリング

Spark.jsについて

Spark.jsは、3D Gaussian SplattingをThree.jsで扱うためのライブラリです。WebGLシェーダーを使用して、ガウシアンのソートとスプラッティングを効率的に行います。

import { SplatMesh } from '@sparkjsdev/spark';

// SplatMeshを作成してシーンに追加
const splatMesh = new SplatMesh({
  url: 'model.ply',
  onProgress: (progress) => {
    console.log(`${progress.loaded / progress.total * 100}%`);
  }
});

await splatMesh.loadPromise;
scene.add(splatMesh);

Import Mapsの活用

ES Modulesを使用し、CDN経由でライブラリを読み込んでいます。

script type="importmap">
{
  "imports": {
    "three": "https://esm.sh/three@0.170.0",
    "three/": "https://esm.sh/three@0.170.0/",
    "@sparkjsdev/spark": "https://sparkjs.dev/releases/spark/0.1.10/spark.module.js"
  }
}
script>

これにより、ビルドツールなしで最新のJavaScriptモジュールシステムを活用できます。

カメラの自動調整

モデル読み込み後、バウンディングスフィアを基にカメラ位置を自動調整します。

if (currentSplat.geometry && currentSplat.geometry.boundingSphere) {
  const sphere = currentSplat.geometry.boundingSphere;
  const center = sphere.center;
  const radius = sphere.radius || 5;

  // カメラターゲットをモデル中心に
  controls.target.copy(center);

  // カメラをモデルから適切な距離に配置
  camera.position.set(
    center.x + radius * 1.5,
    center.y + radius * 0.5,
    center.z + radius * 1.5
  );

  // 座標軸ヘルパーもモデルに合わせてスケール
  axesHelper.scale.set(radius / 2, radius / 2, radius / 2);
  axesHelper.position.copy(center);
}

モデル回転機能

3DGSファイルは作成時の座標系によって向きが異なることがあります。スライダーとプリセットボタンで調整できるようにしました。

function setRotation(x, y, z) {
  rotX.value = x;
  rotY.value = y;
  rotZ.value = z;
  currentSplat.rotation.set(
    x * Math.PI / 180,
    y * Math.PI / 180,
    z * Math.PI / 180
  );
}

// プリセット
document.getElementById('preset-default').addEventListener('click', () => setRotation(0, 0, 0));
document.getElementById('preset-zup').addEventListener('click', () => setRotation(-90, 0, 0));
document.getElementById('preset-flip').addEventListener('click', () => setRotation(180, 0, 0));

ドラッグ&ドロップ実装

ファイル読み込み中でもドラッグ&ドロップで新しいファイルを読み込めるよう、オーバーレイUIを実装しています。

// ドラッグ中のオーバーレイ表示
document.body.addEventListener('dragenter', () => {
  dragCounter++;
  if (dropZone.classList.contains('hidden')) {
    dragOverlay.classList.add('visible');
  }
});

document.body.addEventListener('drop', e => {
  const files = e.dataTransfer.files;
  if (files.length > 0 && files[0].name.match(/\.(ply|splat|ksplat|spz)$/i)) {
    loadSplat(files[0]);
  }
});

IIIF対応

IIIF Presentation API 3.0のマニフェストに対応しています。マニフェストから3DGSファイルのURLを抽出して読み込みます。

{
  "@context": ["http://iiif.io/api/presentation/3/context.json"],
  "type": "Manifest",
  "label": { "en": ["cactus"] },
  "items": [{
    "type": "Scene",
    "items": [{
      "type": "AnnotationPage",
      "items": [{
        "type": "Annotation",
        "body": {
          "id": "https://example.com/model.ply",
          "type": "Model",
          "format": "model/ply"
        }
      }]
    }]
  }]
}

パフォーマンス考慮

ピクセル比の制限

高解像度ディスプレイでのGPU負荷を軽減しています。

renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

メモリ管理

新しいモデルを読み込む際は、前のモデルを適切に破棄します。

if (currentSplat) {
  scene.remove(currentSplat);
  if (currentSplat.dispose) currentSplat.dispose();
  currentSplat = null;
}

制限事項

制限説明
ファイルサイズブラウザのメモリ制限に依存(数百MB程度が目安)
ガウシアン数数百万程度まで(GPU性能に依存)
モバイル動作するが、大きなファイルは推奨しない

今後の改善案

  1. LOD対応 : 視点距離に応じた詳細度の切り替え
  2. 複数モデル対応 : 複数の3DGSファイルを同時に表示
  3. アニメーション : 4D Gaussian Splatting対応
  4. エクスポート : 他形式への変換機能

参考リンク