Floating-Floating origin - Corwin McKnight

Floating-Floating origin

Today I successfully implemented a performant version of floating origin in the Unity Engine for the game I'm writing, Cosmonopoly.

In this post, I'll walk you through the journey of developing this system, as well as explaining exactly what an origin is, why it's floating, and why that is important for making big games look good.

So... what's an origin?

It's the center of something. In 2D games (and a graph) it's the coordinate (0,0), and in 3D games it's (0,0,0).

Everything is relative to this origin. A box may be positioned at (2,3), but it's more correct to say "The box is (2, 3) units away from the origin."

When does this become a problem?

The (X,Y,Z) coordinates are represented by 3 floating point numbers. They're very fast and usually good enough, but a float's 32-bits of precision is lower then required for some games.

When these floating point numbers get really big, things get really messed up. Things start shaking, wobbling, distoring, and generally looking more PlayStation 1 the further you get away from the origin. This is... not desirable in a game.

Additionally, things that far away actually have less precise positions. If something is a million meters away from the origin, you might not even be able to represent half a meter anymore!

A very good video on this topic, using Super Mario 64 as an example, can be found here:

So, in big games like American/Euro Truck Simulator, Kerbal Space Program, and MMOs, this becomes a problem. Those games are huge and have big worlds, and you need some way to:

  1. Render the world correctly
  2. Keep track of objects far away from the origin.

What can we do?

There's many different ways to solve both problems of rendering the world correctly, and keeping track of objects.

Some games simply just... move all objects closer to the camera when a threshold is reached. This works for fixing problem #1, but not problem #2.

A better way is to change up your coordinate system. By making a distinction between world position and rendered position, you can make a system that allows you fix both problems.

By making the World Position more precise, and then transforming that into a rendered position, you can keep rendered positions closer to the origin.

This technique is called a floating origin.

Sounds cool, how do we do that?

I'll walk through a couple of ways, and I'll specifically highlight my solution.

Chunks

This is the most common way to do a floating origin. By seperating the world into "chunks" of data that you stream in, you simply can just move all chunks a fixed offset towards the origin, move the player, move the camera, and the player will hopefully not notice that anything happened!

This is one of the best ways to do it, if you can. You would represent positions as (chunkX, chunkY) - (x,y,z) within chunk. Your origin would simply be a (chunkX, chunkY) pair.

Making the Camera the Origin

Picture of Futurama's Planet Express Ship

In Futurama, the ship moves the universe, not the other way around.

One thing I tried was making the Camera the origin, with the camera never moving, and all objects moving around it. This had the added benefit that every position of an object was its distance to the camera, and a side effect of the calculation to move an object was calculating the actual euclidian distance. This also allows some perspective-warping effects.

However, this has a major performance problem: UpdateRendererBoundingVolumes. UpdateRendererBoundingVolumes is an internal Unity function that updates object positions for the rendering. Normally, this is not a huge deal, but when you are moving every object, every frame, it takes up a significant amount of time (4.2ms w/20,000 objects).

While this can work with a low amount of objects, or for a different need, it does not work for my game.

Floating Floating Origin

My game has the scale of an entire galaxy, and I also allow the player to dynamically change the scale of the world (so they can view the whole galaxy). Objects within the world need a consistant position system, as they go from moving around in star systems, to jumping between them.

My camera can be at any scale or position within the galaxy, looking at a planet, or the whole galaxy at once. This is a unique challenge a chunk based system cannot solve.

So, my solution is to have the floating origin... float!

All positions in my game are two pairs of Vector3, for global (galactic), and local(solar) positioning. I call this a StellarPosition.

The floating origin is a StellarPosition, which means the origin of the world can be any arbitrary point in the world, instead of being quantized. This makes rendering very simple.

During the game, you let the camera move in render space. When it gets too far from the origin, you simply set the new origin point to where the Camera is, and set the camera back to the origin. All objects are transformed accordingly to keep things consistant during the origin-shift.

You might've noticed I'm using the word transformed instead of moved or shifted. This is because we need to scale positions. This is a linear transformation based on the camera's zoom level, and this has to be done at the origin to be consistant with the camera. This also allows me to scale objects based on scale, but this is something I have not implemented yet.

This does making zooming more expensive, but this not a common operation.

Conclusion

In doing research and trying many different techniques, it really shows that one size does not fit all in Programming. There also is usually many different ways to tackle the same subject. All these solutions worked for me, but some were at 40fps, and my current solution works at 900 fps (40ms vs 0.5ms).

I actually don't believe that many people will find a floating-floating origin useful, as the concept of a floating origin is not used outside of many games at all. However, it's a useful thing to have that allows you to make the assumption of the camera being the center of the universe for the purposes of transformations.

© Corwin McKnight 2023