全レイヤーを統合した完全版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();レイヤーは配列の順序でシーンに追加され、深度バッファにより正しい前後関係で描画されます。
各レイヤーは独立した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();
});各レイヤーのupdate()に渡される LayerContext には、 レイヤーが描画処理に必要なすべての情報が含まれています:
selectVisibleTilesはキャッシュ機構を内蔵しており、 同じパラメータで複数のレイヤーから呼ばれた場合は 1フレーム内で同じ結果を再利用します。 これにより、RasterTileLayerとVectorTileLayerが同じSSE選択結果を共有でき、 四分木走査のCPUコストを削減しています。
本書の実装には、学習目的のために単純化した部分があり、プロダクション利用時にはボトルネックになり得ます。
computeTileGeometryは毎回頂点を計算しています。同じタイルのジオメトリは不変(タイル座標が同じなら同じメッシュ)なので、LRUキャッシュで結果を保持することで再計算を避けられます。
MVTのProtocol Buffersデコードと座標変換(三角関数を含む)はメインスレッド(UIスレッド)で行っています。大量のフィーチャーを含むタイルでフレーム落ちが発生する可能性があります。VectorTileLayerの200msスロットリングはこれを緩和していますが、根本的な解決にはWeb Workerでのオフスレッド処理が必要です。
各タイルが1つのMeshまたはGroupを持つため、表示タイル数に比例してドローコールが増加します。InstancedMeshやTexture Atlasによるバッチングで削減できますが、本書では未実装です。
| 問題 | 解決策 |
|---|---|
| メッシュ再計算 | ジオメトリのLRUキャッシュ |
| メインスレッド負荷 | Web Workerでのデコード・変換 |
| ドローコール | InstancedMeshによるバッチング |
| テクスチャメモリ | Texture Atlasによる統合 |
| MVT描画品質 | 球面分割後にearcut三角分割(極地精度改善) |
本書のカメラ操作はOrbitControlsによる回転とカスタムズームのみで、カメラを水平線に向けて傾ける(ピッチ/チルト)操作には対応していません。Google MapsやCesiumJS、MapLibre GLのように地表近くで視点を傾けて街並みを見渡すには、OrbitControlsの極角(polar angle)制限の緩和、またはカスタムのカメラコントローラーの実装が必要です。傾き角に応じてFrustumの形状が大きく変わるため、タイル選択アルゴリズム(SSE計算)やnear/farクリップ面の調整も連動して見直す必要があります。
本書のタイルメッシュは標高0の楕円体面上に生成しています。computeTileGeometryのheightFnパラメータが標高対応の準備ですが、実装は行っていません。地形メッシュを実現するには、標高タイル(Terrain-RGB等)のロード、heightFnへの標高値供給、法線の再計算(隣接頂点からの外積)が必要です。
宇宙から見たときの地球の縁にかかる大気散乱効果は、カスタムシェーダーで実現します。第10章のカスタムシェーダーの知識がここで活きます。CesiumJSの大気散乱シェーダが参考になります。
MVTのラベルレイヤー(place_labels等)はポイントとしてのみ描画しています。テキストラベルの描画には、SDFテクスチャ(Signed
Distance Field)やCanvasテクスチャの生成が必要です。
本書のビューアはカメラ操作のみで、オブジェクトのクリック選択やポップアップ表示は実装していません。Three.jsのRaycasterとフィーチャー属性の紐付けで実現できます。
Three.jsのWebGPUバックエンドはWebGPURendererとして提供されていますが、本書で使用しているShaderMaterialやonBeforeCompileは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抽象クラスにより、新しいレイヤータイプの追加が容易selectVisibleTilesがRasterとVectorで共有され、LOD制御が一貫tile-layer-utils.tsへの共通化でRaster/Vectorの差分更新ロジック重複を解消プロジェクト全体は明確に責務分離されたモジュールで構成されています:
純粋な数学関数。Three.jsに依存しない。 座標変換(geodetic/ECEF/ENU/scene)、 WGS84楕円体定数、タイル座標計算、 四分木探索アルゴリズムなどを提供。 単体テストが容易で、他のレンダリングエンジンにも移植可能。
Three.js依存の描画基盤。 WebGLRenderer、PerspectiveCamera、 OrbitControls、カスタムズーム(ホイール・ピンチ)、ライティングの セットアップと管理を担当。 動的near/far調整とターゲット遷移も含む。 カスタムシェーダー(custom-shader.ts)、 タイルメッシュ生成、MVTメッシュ生成を含む。
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がこれらを統合する制御層として機能する。
レイヤーのライフサイクル管理。 抽象Layerクラスを基底に、 Raster/Vector/Tiles3Dの各レイヤーが initialize/update/disposeパターンで 描画を管理。tile-layer-utils.tsが 差分更新ロジックを共通化している。
レンダーループとフレームバジェット管理。 requestAnimationFrameの抽象化と、 レイヤーごとの処理時間制御を提供。
本書の実装を出発点として、以下の方向に発展させることができます:
heightFnの基盤は第5章で実装済み)第1章から第11章まで、以下の要素を段階的に積み上げてきました:
| 章 | 要素 | 核心技術 |
|---|---|---|
| 1 | Three.jsセットアップ | 対数深度バッファ、ライティング |
| 2 | カメラ操作 | 表面距離ベースズーム、楕円体方向半径 |
| 3 | 座標変換 | WGS84楕円体、ECEF、ENU、軸変換 |
| 4 | 投影・タイル | Webメルカトル、Slippy Map Tiles |
| 5 | 球面メッシュ | メルカトルY補間、UV座標 |
| 6 | LOD制御 | SSE、Quadtree、Frustumカリング |
| 7 | ラスタータイル | LRUキャッシュ、差分更新、親子遷移 |
| 8 | ベクトルタイル | MVTデコード、earcut三角分割 |
| 9 | 3D Tiles | B3DMパーサー、SSE走査、LRUキャッシュ、座標変換行列 |
| 10 | カスタムシェーダー | ShaderMaterial、onBeforeCompile、uniform |
| 11 | 全体統合 | ファサード、フレームバジェット、拡張設計 |
WebGISエンジンの内部は、座標変換の数理、GPU描画の技術、そして非同期リソース管理のアーキテクチャが複雑に絡み合っています。本書がそのブラックボックスを開ける一助となれば幸いです。