A Tale of another Bug
Following on the footsteps of this post describing a “fun” debugging session, Popov72 had the luck… or not… to face another similar worth sharing situation recently.
It all started there: GitHub Issue
Issue Summary
Most of the time in our Babylon.js scenes, the world origin is placed in the center of the scene with 1 scene unit representing 1 meter in real life. This is perfect to create human scaled world such as First Person Shooters. All positions / measures would be in a ball park of 0.01m to 100m. In this case everything is good and our 32 bits floating point values are more than enough precise to handle every cases smoothly.
Now let’s say you want to use a geocentric coordinates system, the world origin would be placed at the earth center. To create your scene and zoom in the same area covered by our previous FPS game, the earth radius being about 6,371,000 meters the positions of every meshes in the scene will now be in a fully different range: a least one of the axis will be in the 10 millions unit range.
Those are huge numbers knowing the camera movement per frame might be of a couple centimeters in the range of 0.01 meter per frame (we will describe more on this a bit later). Unfortunately, the precision of numbers in computers are limited by their representation in memory which needs to be as small as possible. A common way to represent decimal numbers is to rely on floating point values.
Single Precision Floating Point value
Let’s pretend the game takes place around the north pole so around the position (0, 6371000, 0). We now want to animate a moving character starting from the origin of the game. The average walking speed being 5km/hours, this is equivalent to a translation of 0.02 meters per frame (at 60 FPS). If the player moves up, on the next frame the player position would be at (0, 6371000.02, 0). Is this something we can represent accurately knowing our current usage of FP32 numbers ?
W0000t we are basically completely losing those 2 centimeters :-/
So how long would it take to get to a new point of view ?
Basically, our smallest possible represented step would be 50 centimeters representing 25 frames at 2 cm / frame (e.g. only one new position refresh per second) which is definitely way less than enough to get a smooth movement.
What solutions could be used ?
First thing we thought about, was increasing the precision of the data. This is actually the default in JavaScript as numbers are represented with 64 bits precision:
And we could even have precision to the nanometer which is more than plenty for our use case.
The issue is that despite this precision, our shader use at max FP32 value so the data would anyway be lost on transfer to the GPU :-(
Turning on 64 bits computation in Babylon is achieved by enabling it through the engine options useHighPrecisionMatrix flag:
Matrices wizardry
An aspect we are not accounted for here is that in 3D rendering we are not relying directly on world space positions, but most of the time we are using matrices to project points from local space to world space then to view space and finally to projected space : http://rbwhitaker.wikidot.com/basic-matrices
Basically, all the information inside our meshes are defined in the mesh space so for a body the range would be from 0 to 2 m with a required 0.1 millimeter precision which is more than fine in FP32 \o/
Those are then first projected to world space and boom 6371000.0001 again not being supported and then in camera space which if we see it big enough on screen might be in 100 units magnitude values and 100.0001 would be ok with FP32 representation despite a tiny loss.
So, we can see that our values are fine in model and view space but not in world space on the GPU… Let’s disregard the projection part which would be totally fine in this case.
What about mixing together world and view on the CPU where we have 64 bits precision and in the shader directly go from model space to view space in 32 bit precision ???
A simple trick here is to pre-multiply world and view on the JavaScript side and use it directly in the shader so instead of the usual projection * view * world * position
, we would use worldViewProject * position
.
The node material editor from Babylon is a fantastic tool. It makes it super simple to try this out. Our default material would be:
You can experience the issue in this Playground. Try moving the camera to see how jittery it feels.
Let’s replace our world the viewProjection multiplication by a fully precomputed matrix:
You can try the fixed none jittery version on this Playground.
THIS IS IT !!!!
Feel free to over-abuse this technique in your code requiring large scale.
A big thanks to Popov72 for sorting this one out and reminds us that there are only solutions :-)
Sebavan— https://twitter.com/sebavanjs