Rendering Models with High Precision in Global Scenes

2025-10-28

Rendering Models with High Precision in Global Scenes

Hello, this is Sasaki from Eukarya.

We’re currently developing a new 3D map engine. A map engine is a system that displays a globe in 3D space—similar to Google Earth—and shows more detailed images as you zoom in. Beyond its use as a map like Google Earth, it can also be used for visualizing geospatial data. Examples of existing map engines include CesiumJS and Maplibre GL JS.

Map engines handle extremely large scenes, roughly 6000 km in scale. When working with such large values on the GPU, precision can be insufficient, leading to unstable rendering.

Precision Issues on the GPU

When rendering a 3D world at a global scale, you'll encounter issues like “jittery vertices,” “polygon flickering (Z-fighting),” and “slightly misaligned positions” whenever the camera moves. These occur due to the limited precision of 32-bit floating-point numbers (float32).

In WebGL, shader precision can be specified using lowp/mediump/highp, with the maximum being 32-bit. As a result, using float32 for large values leads to precision loss and rendering errors. For more detailed information, refer to WebGL Precision Issues - WebGL Fundamentals or AGI’s “Precisions, Precisions”.

While some native APIs allow the GPU to use 64-bit floats, WebGL does not support float64. WebGPU also doesn’t officially support f64 yet, though extensions may be added in the future, and discussion is ongoing (WGSL f64 issue).

Solutions

This article introduces two approaches to achieve high-precision rendering using float32 at a global scale:

  • RTC (Relative-To-Center): Define a local origin (center) for each mesh (tile), and store vertex positions relative to this center.
  • RTE (Relative-To-Eye): Split each vertex into two float32 values (high/low) and calculate its position relative to the camera within the shader.

Both approaches aim to retain precision by keeping the values small through splitting float32 data. Each method has its pros and cons, so they should be used accordingly.

When rendering tile-based layers like Cesium 3D Tiles, MVT, or raster tiles, RTC is a better fit. Cesium 3D Tiles use a glTF extension called CESIUM_RTC and are designed with RTC in mind. MVT and raster tiles can also structure vertices relative to tile centers, making RTC a natural choice.

For other data like GeoJSON or single models, RTE is more straightforward. Vertices can be used as-is and simply split into high/low values, avoiding the need to recalculate relative coordinates.

RTC Implementation

Calculate the tile’s center C (in world coordinates like ECEF), and convert each vertex P to p_local = P - C before sending to the GPU. The mesh’s model matrix (mesh.position) holds the center C, and the shader processes it with the standard modelViewMatrix. This ensures vertices are small and maintain enough precision with float32.

Processing flow:

  1. Calculate the tile center
  2. Subtract the center to localize vertex positions
  3. Send only local coordinates to the GPU and embed the center in the model matrix translation

In Three.js, this can be implemented as follows—very simply:

// Construct vertices relative to the center and set the center in the model matrix
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: Set center C in the model matrix

Pros

  • No need to modify shaders—easier to maintain
  • No special logic required

Cons

  • Requires management of tile centers
  • Doesn’t work well with moving models

RTE Implementation

Split each vertex P into high/low float32 values from a high-precision float64, and do the same for the camera position E. In the vertex shader, compute (P_high - E_high) + (P_low - E_low) to get the position relative to the camera. This allows computations with smaller values each frame.

High/Low splitting example:

// Approximate split of f64 (Number) into f32 high/low
function encodeHighLow(v: number) {
  const high = Math.fround(v);
  const low  = Math.fround(v - high);
  return { high, low };
}

// Do the same for the camera
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),
  };
}

Vertex Shader (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);
}

Camera Update

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);

Pros

  • Always calculates coordinates near the camera, ensuring high precision
  • Allows free placement of objects

Cons

  • Requires both high and low vertices, increasing memory usage
  • Camera high/low values must be updated every frame
  • Shader and material implementations become more complex

Summary

This article introduced ways to achieve high-precision rendering in environments like WebGL, where float32 is the maximum precision.

We’re facing similar challenges in our current map engine development and have seen improvements as shown in the video examples.

English

Eukaryaでは様々な職種で積極的にエンジニア採用を行っています!OSSにコントリビュートしていただける皆様からの応募をお待ちしております!

Eukarya 採用ページ

Eukarya is hiring for various positions! We are looking forward to your application from everyone who can contribute to OSS!

Eukarya Careers

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