地球規模のシーンで高精度でモデルをレンダリングする
2025-10-28
こんにちは。Eukaryaの佐々木です。
弊社では、新しい3D地図エンジンの開発を進めています。地図エンジンとは、Google Earthのように3次元空間上で地球儀を表示し、ズームするとより詳細な画像を表示する仕組みで、Google Earthのような地図としての用途だけでなく、位置情報データのビジュアライゼーションにも活用できます。既存の地図エンジンの例としては、CesiumJSやMaplibre GL JSなどがあります。
地図エンジンでは、約 6000 km という非常に大きなシーンを扱います。GPUでこのような大きな値を扱うと精度が足りずにレンダリングが安定しないことがあります。
GPUにおける精度の問題
地球規模で 3D を描くと、カメラを動かすたびに、「頂点がガタガタ震える」、「ポリゴンがチラつく(Z-fighting)」、「位置が微妙にズレる」といった現象に直面します。これらは 32bit 浮動小数点(float32)の精度が足りないために起こります。
WebGL ではシェーダの精度指定(lowp/mediump/highp)ができ、最大精度は 32bit です。そのため、大きな値を f32 で扱うと桁落ちが起き、誤差が出てしまいます。この辺りの詳しい話は WebGL Precision Issues - WebGL Fundamentals や AGI の「Precisions, Precisions」で詳しく解説されています。
ネイティブ API では一部、GPU 側で 64bit の浮動小数点を使用できます。しかし、WebGL は f64 をサポートしていません。また、WebGPU でも WGSL の f64 公式サポートは現在も議論中です。WebGPUに関しては、extensionなどが今後追加される可能性もありそうです。
解決策
本記事では、地球スケールでも f32 を高精度で扱うための 2 つのアプローチを紹介します。
- RTC(Relative-To-Center): メッシュ(タイル)ごとに局所原点(中心)を定め、頂点はそのローカル相対座標で保持する
- RTE(Relative-To-Eye): 各頂点を High/Low の 2 つの f32 に分割して符号化し、カメラ位置からの相対値をシェーダで計算する
どちらも f32 を分割して、小さく保った上で計算することで精度を保つ手法です。どちらも一長一短があるので、使い分ける必要があります。
Cesium 3D Tiles やMVT、ラスタータイルのようなタイルベースのレイヤーを描画する場合は、RTCが良いです。Cesium 3D Tiles は CESIUM_RTC という glTF の拡張を使用しており、RTCをベースとした設計になっています。MVTやラスタータイルもタイルの中心から相対的に頂点を構成することができるのでRTCが向いています。
それ以外の GeoJSON や単一のモデルは RTE を使用して、そのままの距離で頂点を構成した上で、high/low に分割する方が、相対座標を改めて計算する必要がなく単純です。
RTC実装
タイルの中心 C(ECEF などの世界座標)を求め、各頂点 P を p_local = P - C に変換して GPU に渡します。メッシュのモデル行列(mesh.position)には中心 C を持たせ、シェーダは通常の modelViewMatrix で処理します。これにより、頂点自体は小さな値になり、f32 で十分な精度が得られます。
処理フローは以下のようになります。
- タイル中心の計算
- 頂点を構成する際に、中心を引いてローカル化
- GPU へはローカル座標だけを送り、モデル行列の平行移動に中心を持たせる
また、Three.jsで以下のように実装できます。非常にシンプルです。
// centerからの相対距離で頂点を構成し、モデル行列の平行移動に center を設定
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(localPositions, 3));
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(center.x, center.y, center.z); // RTC: 中心Cをモデル行列に
メリット
- シェーダは標準のままで OK(メンテ容易)
- 特別なロジックが不要
デメリット
- タイル中心の管理が必要
- 移動するモデルに対応できない
RTE実装
各頂点 P を 高精度の f64 から f32×2(High/Low)に分割し、同様に カメラ位置 E も High/Low に分割します。さらに、頂点シェーダで (P_high - E_high) + (P_low - E_low) を計算してカメラ相対座標に変換します。これにより毎フレーム、小さな値での計算で済ませることができます。
High/Low分割は次のように実装します。
// f64(Number) -> f32 high/low に近似分割
function encodeHighLow(v: number) {
const high = Math.fround(v);
const low = Math.fround(v - high);
return { high, low };
}
// カメラ位置も同様に分割
function encodeCameraRTE(camera: Camera) {
const { x, y, z } = camera.position;
const ex = encodeHighLow(x);
const ey = encodeHighLow(y);
const ez = encodeHighLow(z);
return {
high: new THREE.Vector3(ex.high, ey.high, ez.high),
low: new THREE.Vector3(ex.low, ey.low, ez.low),
};
}
頂点シェーダ(GLSL)
attribute vec3 positiond3dHigh;
attribute vec3 position3dLow;
uniform vec3 cameraHigh;
uniform vec3 cameraLow;
void main() {
vec3 highDiff = positiond3dHigh - cameraHigh;
vec3 lowDiff = position3dLow - cameraLow;
vec3 posEye = highDiff + lowDiff;
gl_Position = projectionMatrix * modelViewMatrix * vec4(posEye, 1.0);
}
カメラ更新
const { high, low } = encodeCameraToHighLow(camera);
material.uniforms.cameraHigh.value.set(high.x, high.y, high.z);
material.uniforms.cameraLow.value.set(low.x, low.y, low.z);
メリット
- カメラに対して常に小さな座標で計算でき精度が高い
- オブジェクトを自由に配置できる
デメリット
- high/low の 2 つの頂点を必要とするのでメモリ使用量が増える
- 毎フレーム カメラの high/low を更新する必要がある
- シェーダとマテリアルの実装が複雑
まとめ
ここまで、WebGLのような最大精度が f32 の環境で、高精度にレンダリングする方法について紹介しました。
現在開発中の地図エンジンでも同様の問題があり、動画のように改善できました
Eukaryaでは様々な職種で積極的にエンジニア採用を行っています!OSSにコントリビュートしていただける皆様からの応募をお待ちしております!
Eukarya is hiring for various positions! We are looking forward to your application from everyone who can contribute to OSS!
Eukaryaは、Re:Earthと呼ばれるWebGISのSaaSの開発運営・研究開発を行っています。Web上で3Dを含むGIS(地図アプリの公開、データ管理、データ変換等)に関するあらゆる業務を完結できることを目指しています。ソースコードはほとんどOSSとしてGitHubで公開されています。
➔ Eukarya Webサイト / ➔ note / ➔ GitHub
Eukarya is developing and operating a WebGIS SaaS called Re:Earth. We aim to complete all GIS-related tasks including 3D (such as publishing map applications, data management, and data conversion) on the web. Most of the source code is published on GitHub as OSS.
➔ Eukarya Official Page / ➔ Medium / ➔ GitHub