How to Handle Double Precision Coordinates in Unity ?
Tags: unity3d, gamedev
Published: 2023-09-08 09:41:10

Like most of 3D/Game engines, Unity works with single float precision coordinates. As it is not an issue for most use cases, this may be a problem when you deal with geographical coordinates or very large world. Let’s see how to handle this problem using the Unity’s High Precision Framework.

## The Unity High Precision Framework
[The Unity High Precision Framework](https://github.com/Unity-Technologies/com.unity.gis.high-precision-framework) is a package that add double precision support to Unity. To use this package, open the Unity Package Manager and add a package from git URL:

![Adding High Precision Framework package](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hpvtc6xx5wbij4rlyexc.png)

Then past the git url from Github repos:
https://github.com/Unity-Technologies/com.unity.gis.high-precision-framework.git

This package comes with a very good sample. It contains a huge plane of 100.000 km over 100.000 km and 3 small scenes located at random places.

![High Precision Framework sample](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gdyg26krfzavd8fosye9.png)

## Using The Unity High Precision Framework

High Precision Framework define two new components:

### The HPRoot component

Create an **HPRoot** **GameObject** and add an **HPRoot** component. Every objects should be under this **HPRoot**. Even the camera:


![HPRoot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s2pzlrxf1rdrw410trc0.png)

**HPRoot** can also contains a Local Coordinate System script to automatically update RootUniversePosition property depending on some logic. See Strategies to Update **HPRoot** to get some ideas.

### The HPTransform component

Add this component for **GameObject** that require double precision position. If possible, try to make “container” objects with **HPTransform**, and children **GameObject** with position relative to parent. If you build a map of a country, put an **HPTransform** for each city and put building under corresponding city, but avoid using an **HPTransform** for each objects.

![HPTransform](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ye1uh0mim920mf9r20r.png)

Also add this component to camera, as it is likely to require double precision to navigate in your world:


![HPTransform for Camera](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/90smkyidsazv5vam0mz3.png)

The most important thing is to update **HPRoot.RootUniversePosition** to a position near your current work area. In the sample, the position is update when camera go to another position. See LocalCoordinateSystem.cs:

```C#
private void LateUpdate()
{
    if (m_Origin != null && !m_LastPosition.Equals(m_Origin.UniversePosition))
    {
        m_LastPosition = m_Origin.UniversePosition;
        m_Root.RootUniversePosition = m_LastPosition;
    }
}
```

## Understanding the Problem of Float Precision

I wrote a [series](https://kissyagni.wordpress.com/2022/03/21/the-problem-of-floating-point-precision-in-opengl-vulkan-and-3d-in-general-part-1/) [of](https://kissyagni.wordpress.com/2022/03/28/the-problem-of-floating-point-precision-in-opengl-vulkan-and-3d-in-general-part-2/) [articles](https://kissyagni.wordpress.com/2022/04/04/the-problem-of-floating-point-precision-in-opengl-vulkan-and-3d-in-general-part-3/) that details the problem of float precision in 3D. Let summarize this and see how the HP framework handle it:

Floating point values have a limited number of digit. To simplify, it can store only 6 digits in decimal representation. This means that possible position for objects are aligned on a grid of representable values. If your game consists in walking around a garden near a house, your position will never be higher than a few dozen of meters, giving you a sub-millimeter precision. The grid has a spacing of less than a millimeter, you don’t see you character jumping from millimeter to millimeter.

However, imagine you develop an open world game with planet size map and origin in bottom left. Your grid will look like this:


![Loss of precision on huge map](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b8ev2yif8v5kr4o3qr4b.png)

A few meters aways from the origin, you will have a sub millimeter precision. But if you go 100 km away, you barely have 10 cm precision. And if you go even further, let say 1.000 km aways, you don’t even have 1 meter precision.

This means that, if you try to put a car on a road, it will “jump” from place to place, because its position can only be on the grid of representable floating point values.

Using double precision don’t completely fix the problem, it just push it farther away. With double, you would have ~15 digits precision. Meaning you will keep a millimeter precision even at the other end of the solar system. If you want to cross the galaxy… You will have to find another solution 😊. That’s the role of **HPTransform**: To define object’s position in double precision.

Using double precision to gives object's position in world space is just one part of the problem. GPU works in float precision. You **CAN NOT** have end to end double precision. You **MUST** go back to float precision at some point.

The good news is that floating precision issues are visible only for… visible objects, so only for object close to the camera. And the good news is that, in camera space, you can go back to float precision because object close to camera are… close to camera space origin 😊, so float precision is enough:


![Precision in Camera space](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fo4wfgyq8itff9lxr32l.png)

Now, the bad news 😭… Doing this “back to float precision” in camera space would require doing it at every frame (or at least every time camera moves). Which would kill performances. It can work for simple cases, but framerate will drop as soon as you will have many objects in your scene.

BUT, what we could do is to define a space which is “not far away from camera”, and update it only once from time to time. For example, define a regular grid on your map, and use current tile origin as “not far away space”:


![Precision near camera space](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/86896o9p94m2tys7lmsk.png)

That’s the role of the **HPRoot** component. Define a position near current position, and express all **GameObject** position relative to this position, using float precision.

## Strategies to Update **HPRoot**

The root of the problem is to properly update **HPRoot.RootUniversePositio**n. There are several strategies to do so:

- Attach it to camera position. It works, but kill performances.
- Define a regular grid, and update it when player go to another tile.
- Define a maximum distance, and update it to current player position when the distance is higher than a threshold.
- Update it regularly through a timer. By the way, this strategy was actually used in the old [VistaNav GPS](https://aviationweek.com/vistanav-16-includes-3-d-guidance).
- Update depending on “teleportation” logic, like in sample.

## Limitations

Unity’s HP Framework is not magic and currently have limitations (taken from [documentation](https://github.com/Unity-Technologies/com.unity.gis.high-precision-framework/tree/main/Documentation~)):

**Platform Support**

Currently, only 64 bit windows is fully tested. However, the framework is reported to work on other platforms.

**Physics**

Physics can only work in a static rebasing context due to the performance cost of moving colliders.

**Navigation Mesh**

Authored navigation meshes can only be used in a static rebasing context.

**Particle Systems**

World-space particle simulations may or may not behave as expected depending on the rebasing strategy that is used by the high precision rendering scheme. If dynamic rebasing updates the **HPRoot**, particles simulated in world space will appear to jump around as a function of the rebasing.

**Scripts**

Any script that reads world-space positions will likely behave strangely when using the High Precision Framework. These scripts would need to be written such that only relative values are used or so that they use the **HPTransform** component rather than the Transform component.

**Shaders**

Shaders that rely on world space coordinates, such as tri-planar shaders will behave oddly when used with the High Precision Framework as the world space is constantly being adjusted with dynamic rebasing schemes.

For the Shader part, I’ve used this package with complex shader based on [ray-casting](https://en.wikipedia.org/wiki/Ray_casting) and [signed distance field](https://en.wikipedia.org/wiki/Signed_distance_function) and didn’t have any trouble.

## Conclusion

It may be hard to adapt an existing Unity game to High Precision framework because of limitations described above. Script dealing with world coordinates might also require rework to use the **HPTransform** UniversePosition instead of **GameObject** transform position.

However, this package it is very well adapter to render static objects in very large scene like in GIS applications or flying simulation game.