第11章: 全体統合とこれから

全レイヤーを統合した完全版WebGISビューア

はじめに

全11章を通じて、GIS技術者が「ブラックボックス」として利用していたWeb地図ライブラリの内部構造をゼロから組み立ててきました。Scene/Camera/Rendererの初期化から始まり、座標変換、タイルメッシュ生成、LOD制御、ラスター・ベクトル・3Dタイルの統合、カスタムシェーダーによるスタイリングまで、WebGISエンジンの全要素を体験しました。

この章ではGlobeViewerのアーキテクチャを俯瞰し、パフォーマンスのボトルネックとその最適化方針、本書でカバーしなかった発展課題、そして次のステップを整理します。ここが今後の拡張の出発点です。

レイヤーの合成

第7章〜第9章で実装したレイヤーは、+page.svelteで配列としてGlobeViewerに渡されます。 左のキャンバスではラスタータイルと3D Tilesの2レイヤーを統合表示しています。VectorTileLayerを加えれば3レイヤー構成にすることも可能です。

レイヤー統合コード
const viewer = new GlobeViewer({
  canvas: canvasEl,
  layers: [
    new RasterTileLayer(),
    new Tiles3DLayer({
      url: '...tileset.json'
    })
  ]
});
viewer.start();

描画順序

レイヤーは配列の順序でシーンに追加され、深度バッファにより正しい前後関係で描画されます。

  1. RasterTileLayer — OSMタイル画像を球面メッシュとして描画(最背面)
  2. VectorTileLayer — 道路・河川・境界線を線として描画(50m浮上でZ-fighting回避)
  3. Tiles3DLayer — PLATEAU建物モデルを描画(地表面に配置)

各レイヤーは独立したTHREE.Groupを持ち、互いのリソースに干渉しません。この独立性により、レイヤーの組み合わせを自由に変更できます。

毎フレームの処理フロー

GlobeViewerの中核は RenderLoop です。 requestAnimationFrameで毎フレーム呼ばれるコールバック内で、 すべてのレイヤーの update() を順に実行し、最後に描画を行います。

requestAnimationFrame コールバック
│
├── RasterTileLayer.update(ctx)
│     ├── カメラ変更チェック
│     ├── スロットリング(200ms制限)
│     ├── SSEタイル選択
│     ├── 差分計算(追加・削除)
│     └── テクスチャロード開始
│
├── VectorTileLayer.update(ctx)
│     ├── ビルドキュー処理(フレーム分散)
│     ├── 破棄キュー処理(バジェット考慮)
│     ├── カメラ変更チェック
│     ├── スロットリング(200ms制限)
│     ├── SSEタイル選択
│     ├── 差分計算(追加・削除)
│     └── MVTロード → ビルドキューへ追加
│
├── Tiles3DLayer.update(ctx)
│     └── adapter.update(camera, renderer)
│           ├── processDisposalQueue()
│           ├── カメラ変更チェック
│           ├── traverseTileset()
│           │     ├── バウンディングボリューム → SceneBounds
│           │     ├── ホライズン + フラスタムカリング
│           │     ├── SSE計算 + リファインメント判定
│           │     └── desired / toRemove / toCancel 生成
│           ├── キャンセル + シーン除去
│           ├── キャッシュヒット → シーン追加
│           └── キャッシュミス → ロードリクエスト
│
└── ThreeRenderer.render()
      ├── ズーム補間(楕円体半径 + 表面距離)
      ├── ターゲット遷移(地表↔原点)
      ├── 回転速度調整
      ├── 動的 near/far 調整(水平線距離)
      ├── controls.update()
      └── renderer.render(scene, camera)

update → render の順序が重要です。レイヤーが先にタイルの追加・削除を行い、その結果をThreeRendererが描画します。

フレームループ
const loop = new RenderLoop(() => {
  const ctx = createContext(
    performance.now()
  );
  for (const layer of layers) {
    layer.update(ctx);
  }
  threeRenderer.render();
});

LayerContext

各レイヤーのupdate()に渡される LayerContext には、 レイヤーが描画処理に必要なすべての情報が含まれています:

  • camera — PerspectiveCamera参照
  • screenHeight — 描画領域の高さ(SSE計算用)
  • renderer — WebGLRenderer参照
  • frameBudgetMs / remainingBudgetMs() — フレームバジェット管理
  • selectVisibleTiles() — SSE選択ヘルパー(キャッシュ付き)

selectVisibleTilesはキャッシュ機構を内蔵しており、 同じパラメータで複数のレイヤーから呼ばれた場合は 1フレーム内で同じ結果を再利用します。 これにより、RasterTileLayerとVectorTileLayerが同じSSE選択結果を共有でき、 四分木走査のCPUコストを削減しています。

パフォーマンスのボトルネック

本書の実装には、学習目的のために単純化した部分があり、プロダクション利用時にはボトルネックになり得ます。

1. タイルメッシュ生成のCPUコスト

computeTileGeometryは毎回頂点を計算しています。同じタイルのジオメトリは不変(タイル座標が同じなら同じメッシュ)なので、LRUキャッシュで結果を保持することで再計算を避けられます。

2. MVTデコードのメインスレッド負荷

MVTのProtocol Buffersデコードと座標変換(三角関数を含む)はメインスレッド(UIスレッド)で行っています。大量のフィーチャーを含むタイルでフレーム落ちが発生する可能性があります。VectorTileLayerの200msスロットリングはこれを緩和していますが、根本的な解決にはWeb Workerでのオフスレッド処理が必要です。

3. ドローコール数

各タイルが1つのMeshまたはGroupを持つため、表示タイル数に比例してドローコールが増加します。InstancedMeshTexture Atlasによるバッチングで削減できますが、本書では未実装です。

最適化の方向性

問題解決策
メッシュ再計算ジオメトリのLRUキャッシュ
メインスレッド負荷Web Workerでのデコード・変換
ドローコールInstancedMeshによるバッチング
テクスチャメモリTexture Atlasによる統合
MVT描画品質球面分割後にearcut三角分割(極地精度改善)

本書でカバーしなかった機能

カメラのピッチ(チルト)

本書のカメラ操作はOrbitControlsによる回転とカスタムズームのみで、カメラを水平線に向けて傾ける(ピッチ/チルト)操作には対応していません。Google MapsやCesiumJS、MapLibre GLのように地表近くで視点を傾けて街並みを見渡すには、OrbitControlsの極角(polar angle)制限の緩和、またはカスタムのカメラコントローラーの実装が必要です。傾き角に応じてFrustumの形状が大きく変わるため、タイル選択アルゴリズム(SSE計算)やnear/farクリップ面の調整も連動して見直す必要があります。

地形(Terrain)

本書のタイルメッシュは標高0の楕円体面上に生成しています。computeTileGeometryheightFnパラメータが標高対応の準備ですが、実装は行っていません。地形メッシュを実現するには、標高タイル(Terrain-RGB等)のロード、heightFnへの標高値供給、法線の再計算(隣接頂点からの外積)が必要です。

大気散乱(Atmosphere)

宇宙から見たときの地球の縁にかかる大気散乱効果は、カスタムシェーダーで実現します。第10章のカスタムシェーダーの知識がここで活きます。CesiumJSの大気散乱シェーダが参考になります。

ラベル描画

MVTのラベルレイヤー(place_labels等)はポイントとしてのみ描画しています。テキストラベルの描画には、SDFテクスチャ(Signed Distance Field)やCanvasテクスチャの生成が必要です。

インタラクション

本書のビューアはカメラ操作のみで、オブジェクトのクリック選択やポップアップ表示は実装していません。Three.jsのRaycasterとフィーチャー属性の紐付けで実現できます。

WebGPU対応

Three.jsのWebGPUバックエンドはWebGPURendererとして提供されていますが、本書で使用しているShaderMaterialonBeforeCompileはWebGPURendererではサポートされていません。移行にはTSL(Three.js Shading Language)を使ったノードマテリアルへの書き換えが必要です。

アーキテクチャの振り返り

┌──────────────────────────────────────────┐
│            +page.svelte                  │
│  GlobeViewer(ファサード)                │
│  ├── ThreeRenderer                       │
│  │   (描画・カメラ・ズーム・ピンチ対応)│
│  ├── RenderLoop(rAF)                   │
│  └── Layer[]                             │
│       ├── RasterTileLayer                │
│       │   ├── selectVisibleTiles(SSE)   │
│       │   ├── TileTextureLoader(LRU)    │
│       │   └── createTileMesh             │
│       ├── VectorTileLayer                │
│       │   ├── selectVisibleTiles(共有)  │
│       │   ├── MvtTileLoader(PBF)        │
│       │   └── createMvtGroup             │
│       └── Tiles3DLayer                   │
│           └── Tiles3DAdapter             │
│               ├── tileset-parser         │
│               │   (ツリー構築)          │
│               ├── tile-traversal         │
│               │   (SSE走査)            │
│               ├── bounding-volume        │
│               │   (座標変換)           │
│               ├── tile-cache             │
│               │   (LRU + リクエスト管理)│
│               └── content-loader         │
│                   (B3DM/GLBパース)      │
├──────────────────────────────────────────┤
│  core/(Three.js非依存)                  │
│  ellipsoid → coordinates → projection    │
│                   → tiles                │
│              tile-geometry → tile-tree    │
└──────────────────────────────────────────┘

設計判断の評価

うまくいった点

  • core/の分離により、座標変換のユニットテストが容易になった
  • Layer抽象クラスにより、新しいレイヤータイプの追加が容易
  • 3D Tilesローダーを6モジュール構成で独自実装し、外部依存をGLTFLoader/DRACOLoaderのみに限定した
  • selectVisibleTilesがRasterとVectorで共有され、LOD制御が一貫
  • tile-layer-utils.tsへの共通化でRaster/Vectorの差分更新ロジック重複を解消

改善の余地がある点

  • VectorTileLayerのスロットリングは実装したが、Web Worker化は未着手
  • earcutは2D三角分割のため、極地付近の大きなポリゴンでは球面歪みによる精度に課題が残る
  • InstancedMeshによるドローコールのバッチングは未実装
  • レイヤーの動的追加・削除がサポートされていない
  • 3D TilesのPNTS(ポイントクラウド)/ I3DM(インスタンスモデル)/ CMPT(複合タイル)形式に未対応
  • Implicit Tiling(暗黙的タイリング)に未対応

モジュール設計

プロジェクト全体は明確に責務分離されたモジュールで構成されています:

core/

純粋な数学関数。Three.jsに依存しない。 座標変換(geodetic/ECEF/ENU/scene)、 WGS84楕円体定数、タイル座標計算、 四分木探索アルゴリズムなどを提供。 単体テストが容易で、他のレンダリングエンジンにも移植可能。

renderer/

Three.js依存の描画基盤。 WebGLRenderer、PerspectiveCamera、 OrbitControls、カスタムズーム(ホイール・ピンチ)、ライティングの セットアップと管理を担当。 動的near/far調整とターゲット遷移も含む。 カスタムシェーダー(custom-shader.ts)、 タイルメッシュ生成、MVTメッシュ生成を含む。

renderer/tiles3d/

3D Tiles独自ローダーの6モジュール。 types.ts(型定義)、tileset-parser.ts(ツリー構築)、 tile-traversal.ts(SSE走査)、bounding-volume.ts(座標変換)、 tile-cache.ts(LRUキャッシュ + リクエスト管理)、 content-loader.ts(B3DM/GLBパース + GLTFLoader)。 tiles3d-adapter.tsがこれらを統合する制御層として機能する。

layers/

レイヤーのライフサイクル管理。 抽象Layerクラスを基底に、 Raster/Vector/Tiles3Dの各レイヤーが initialize/update/disposeパターンで 描画を管理。tile-layer-utils.tsが 差分更新ロジックを共通化している。

engine/

レンダーループとフレームバジェット管理。 requestAnimationFrameの抽象化と、 レイヤーごとの処理時間制御を提供。

次のステップ

本書の実装を出発点として、以下の方向に発展させることができます:

  1. 地形メッシュ — 標高タイルを使った地形起伏の表現(heightFnの基盤は第5章で実装済み)
  2. Web Worker化 — MVTデコードとメッシュ生成のオフスレッド処理
  3. 大気・水面シェーダー — 第10章のカスタムシェーダー技法を応用したビジュアル向上
  4. WebGPU移行 — 次世代GPU APIへの対応
  5. PNTS/I3DM形式のサポート — ポイントクラウドとインスタンスモデルへの対応
  6. Implicit Tiling対応 — テンプレートURLによる暗黙的タイリングの実装
  7. レイヤー管理UI — レイヤーのオン/オフ切り替えUI
  8. インタラクション — Raycasterによるフィーチャー選択とポップアップ

全体の学習まとめ

第1章から第11章まで、以下の要素を段階的に積み上げてきました:

要素核心技術
1Three.jsセットアップ対数深度バッファ、ライティング
2カメラ操作表面距離ベースズーム、楕円体方向半径
3座標変換WGS84楕円体、ECEF、ENU、軸変換
4投影・タイルWebメルカトル、Slippy Map Tiles
5球面メッシュメルカトルY補間、UV座標
6LOD制御SSE、Quadtree、Frustumカリング
7ラスタータイルLRUキャッシュ、差分更新、親子遷移
8ベクトルタイルMVTデコード、earcut三角分割
93D TilesB3DMパーサー、SSE走査、LRUキャッシュ、座標変換行列
10カスタムシェーダーShaderMaterial、onBeforeCompile、uniform
11全体統合ファサード、フレームバジェット、拡張設計

WebGISエンジンの内部は、座標変換の数理、GPU描画の技術、そして非同期リソース管理のアーキテクチャが複雑に絡み合っています。本書がそのブラックボックスを開ける一助となれば幸いです。