Double-precision large world coordinates and rendering
Dawn Engine provides engine-level large world coordinates (double-precision world positions). Absolute world state is stored in double precision on the CPU; before GPU submission, positions are converted to single-precision coordinates relative to a stable render origin. GPU shaders continue to use float by default, so you do not pay a default GPU fp64 cost for large worlds.
Maturity: Delivered. Enabled by default; small-world projects can turn it off.
What problem this solves
Most real-time engines, including open source O3DE, store world coordinates as single-precision floats. Far from the origin, mantissa bits are consumed by the large magnitude: around 100 km, position error can reach centimeters. Symptoms include mesh and camera jitter, unstable shadows and TAA, physics drift, and inaccurate editor picking and placement.
Large world coordinates address numeric range, not every GPU calculation. Depth fighting and poorly chosen near/far planes are separate issues and are not fixed by this feature alone.
How it works
Dawn Engine uses high-precision world state with origin-relative rendering:
- CPU absolute world: authoritative entity translation, camera position, bounds, and editor interaction data use double types such as
Vector3dandTransformD. - Scene-level render origin: each frame, the main camera position is quantized onto a grid of cells (default edge length 1024 m) to produce a stable
renderOriginD. - GPU relative submit: object matrices, camera, and visibility data subtract the current render origin before upload, so the GPU sees single-precision relative coordinates.
- History consistency: TAA, motion vectors, and similar paths track both the current and previous render origin so origin switches do not flash the frame.
This is not per-frame camera-relative rendering. Dawn Engine uses a cell-quantized render origin with hysteresis so the origin does not chatter at cell boundaries.
Enable and disable
Default settings ship in Registry/LargeWorld.setreg under /O3DE/Atom/LargeWorld/.
| Key | Default | Description |
|---|---|---|
Enabled | true | Master switch. When false, the render origin stays at (0,0,0) and behavior matches the pre-migration small-world path |
OriginCellSize | 1024.0 | Render-origin cell edge length in meters |
OriginSwitchDistance | 0.0 | Hysteresis distance before an origin switch; 0 means 0.6 × OriginCellSize |
PhysxOriginShiftEnabled | true | On origin switch, translate PhysX actors so internal poses stay within a safe float range |
MultiplayerUseTranslationD | true | Replicate an extra double translation field translationD over the network |
LegacyFloatInputPolicy | Silent | Policy for legacy float APIs used without an explicit space declaration in large-world mode: Silent / Warn / Error |
Example project override (disable large-world path):
{
"O3DE": {
"Atom": {
"LargeWorld": {
"Enabled": false
}
}
}
}
Disable Enabled only for small-world projects that need bit-identical pre-migration behavior. Keep it enabled for open worlds, digital twins, or any content that moves far from the origin.
Authoring and scripting at large coordinates
Double precision is an engine-level capability; you do not enable a separate Gem. Prefer the high-precision path when authoring and scripting:
- Editor: use the double-precision translation inspector controls and the D-aware entity create / Prefab instantiate paths. Placement, picking, and grid snap far from the origin use the double path.
- Scripts and gameplay: prefer
HighPrecisionTransformBusand*Dtransform get/set APIs (for example world translation*Devents). LegacyTransformBus/AZ::Vector3APIs remain available but can silently lose precision far from the origin. - Migrating old content: “upgrading” an old Prefab float translation to double does not restore precision that was already lost. Re-place content at large coordinates, or re-import from a source that still has high-precision data.
While porting custom code, temporarily set LegacyFloatInputPolicy to Warn or Error to surface call sites that still pass absolute-world floats.
API reference
HighPrecisionTransformBus (absolute entity world transform)
Header AzCore/Component/HighPrecisionTransformBus.h, addressed by EntityId. In large-world mode every entity serves two transform buses with a clear split of duties:
| Bus | Payload | Purpose |
|---|---|---|
TransformBus (legacy) | Render-origin-relative single-precision AZ::Transform | Compatibility layer; the only required interface for small-world projects |
HighPrecisionTransformBus | Absolute-world double-precision AZ::TransformD | Everything that needs absolute-world precision: gameplay, picking, physics adapters, feature processors |
Interface events:
| Event | Description |
|---|---|
GetWorldTransformD / SetWorldTransformD | Absolute world transform (TransformD) |
GetLocalTransformD / SetLocalTransformD | Local transform relative to the parent entity |
GetWorldTranslationD / SetWorldTranslationD | World translation (Vector3d); the most common gizmo / inspector write-back path |
GetLocalTranslationD / SetLocalTranslationD | Local translation |
Change notifications arrive on HighPrecisionTransformNotificationBus via OnWorldTransformDChanged(localD, worldD). The legacy TransformNotificationBus still fires, but its payload in large-world mode is render-origin-relative single precision — listeners that need absolute world coordinates must switch to the high-precision notification.
AZ::LargeWorld precision-boundary helpers (custom C++ code)
Header AzCore/Math/LargeWorld.h. Every “absolute double → relative float” demotion should go through these helpers rather than a hand-written subtraction:
| API | Description |
|---|---|
ToRelativeFloat(posD, renderOrigin) | Subtract the render origin and demote to Vector3; the primary entry point at the Atom submit boundary |
ToRelativeTransform / ToRelativeMatrix3x4 / ToRelativeAabb | The TransformD / matrix / bounds variants of the same |
ToFloatChecked(v) | Demote without subtracting an origin; asserts on NaN / Inf / out-of-warning-range values in debug builds |
SplitOriginHighLow(origin, outHigh, outLow) | Exact split of a render origin into a (high, low) float pair matching the wire format of the shader helper AbsoluteWorldPositionDemote.azsli — use it when a custom material consumes absolute world positions |
LargeWorld::GetLegacyFloatInputPolicy() / ReportLegacyFloatInput(symbol) | Migration policy and reporting for legacy float entry points (backed by the LegacyFloatInputPolicy registry key) |
The double-precision math types (Vector3d, TransformD, AabbD, FrustumD, and others) live under AzCore/Math/.
RenderOriginService (scene-level render origin, Atom RPI)
Header Atom/RPI.Public/LargeWorld/RenderOriginService.h. It is not a component; it is a service on the Atom scene driven once per frame by Scene::PrepareRender:
| API | Description |
|---|---|
GetCurrentRenderOriginD() / GetPreviousRenderOriginD() | Current / previous frame render origin (history buffers are generated against the previous origin) |
DidOriginSwitchThisFrame() | Whether the origin switched this frame |
ConnectRenderOriginChangedHandler(handler) | Subscribe to the RenderOriginChanged event (fires exactly once per switch); handlers must mark all origin-relative GPU data dirty for re-upload |
GetStats() | Diagnostics: max view / object distance from origin, switch count, and more |
Custom feature processors that upload origin-relative data should listen to this event; ordinary gameplay code never needs to touch the render origin — use the absolute coordinates from HighPrecisionTransformBus instead. Render-origin switches also do not change World Streaming cell desired states (streaming resolves on absolute world distance).
Script Canvas
The math library provides double-precision nodes such as TransformD. When interacting with entities, prefer the high-precision transform events (world translation Get/Set *D) so values never pass through Vector3 and silently lose precision far from the origin.
Runtime behavior
When enabled, the Render Origin Service:
- derives a cell index from the main camera:
cellIndex ≈ round(camera / OriginCellSize), with origin candidatecellIndex × OriginCellSize; - switches origin only when the camera is farther from the current origin than the hysteresis threshold;
- shares one render origin across views that use the same scene object buffer;
- notifies migrated subsystems (view matrices, object transforms, lights and shadows, ray-tracing TLAS, PhysX origin shift, and others) when the origin changes.
Inside a default 1024 m cell, relative-coordinate float ULP typically stays at sub-millimeter scale. At about 100 km from the origin, migrated paths keep sub-millimeter position precision; the absolute single-precision path at the same location is about 1.2 cm ULP.
Content and material caveats
- Custom materials / shaders that treat
worldPositionas absolute geographic space need an explicit migration (for example a render-origin high/low split, a feature origin, or CPU-side precomputation). The default shader contract is render-origin-relative world, not absolute world. - A single oversized object (for example one planet-scale mesh) can still exceed the relative-coordinate safety budget; tile it or use a specialized pass.
- Far reflection or tool views: views beyond the view-to-origin safety thresholds may be unsupported or use a deferred / split pipeline. Do not assume each viewport has its own render origin.
- Depth and near/far: double precision does not replace sensible near/far settings; an oversized far plane still hurts depth precision.
Limits and related features
Safe to rely on:
- CPU double world state plus render-origin-relative GPU submission;
- migrated editor placement / picking, core rendering, culling, PhysX origin shift, and multiplayer
translationD; - no default GPU fp64; with
Enabled = false, small-world behavior matches the pre-migration engine.
Do not assume:
- GPU
double/ fp64 materials and post by default; - drop-in compatibility with another engine’s large-world coordinate API;
- that every third-party Gem or custom shader is large-world safe without audit.
Geospatial and water capabilities built on this coordinate contract ship separately; see Cesium 3D Tiles and Water.
World Streaming addresses load and memory scale for large worlds; double precision addresses accuracy at large coordinates. Open-world projects usually need both.
Troubleshooting
| Symptom | Likely cause | What to try |
|---|---|---|
| Objects jitter far from origin | Still on an absolute-world float path, or Enabled is off | Confirm /O3DE/Atom/LargeWorld/Enabled is true; switch scripts to *D APIs; set LegacyFloatInputPolicy to Warn while porting |
| Brief flash on origin switch | Custom post that ignores previous render origin | Audit history-dependent materials / post for current and previous origin |
| Physics tunnels or unstable contacts at large coords | PhysX origin shift disabled | Confirm PhysxOriginShiftEnabled is true when Enabled is true |
| Multiplayer clients drift in position | translationD not replicated | Confirm MultiplayerUseTranslationD is true and clients understand the D field |
| Depth flicker / z-fighting | near/far too large, not coordinate precision | Tighten camera near/far; do not rely on double precision alone |
Next steps
- For Dawn Engine capabilities and how they differ from O3DE, see Introduction.
- For large-world load and memory scale, see World Streaming; plan it together with double-precision coordinates for open-world projects.