We have seen in the previous article how to handle position of object and camera in very large scene. But this still won’t solve the shaky problem caused by matrix accumulation. We will here see that only a few place needs to use double precision, and not all your rendering pipeline.
Reminder about space changes
We have a point P expressed in Model space, and we want to express it in View space knowing Model matrix and View matrix:
Model is a shortcut for ModelToWorld matrix. With this matrix, you can express P coordinates in World space knowing P coordinates in Model space. On the other hand, View matrix is a shortcut for WorldToView matrix with which we can express P coordinate in View space knowing P coordinate in World space. In the end:
P_view = WorldToView * P_world = WorldToView * ModelToWorld * P_model
The (WoldToView * ModelToWorld) is also known as the ModelView matrix. Legacy OpenGL only used the ModelView matrix for performance reason and didn’t split it into 2 Model and View matrix, but separate World and View are much more convenient, especially for beginners. And in todays computer, multiplying 2 matrices together is really not a bottleneck anymore.
What is P precision in view space ?
As we saw, precision issues occurs when point of interest is faw aways from its reference space. Both Camera and P are far away from World origin. We saw in previous article how to handle that. But when Camera and P are really close to each other, why P would be shaky ?
And once again, precision comes into play…
Problems are located in 3 place: the WorldToView matrix, the ModelToWorld and the WorldToView * ModelToWorld computation. Imagine P and Camera located in Saturn with an Heliocentric world space. You will do a +1 billion km to express P in World space and -1 billion to go back to View space. Now you slightly move your camera of 1 meter to the left and your P of 1 meter to the right. Using a proper model space and tripod position as described before, you can do it. You do 1 000 000 000 001 – 999 999 999 999. Cast these values to Float to generate corresponding matrices and your meter has been lost on both side. So should we accumulate everything in double precision ?
Well, not exactly. Only a few thing should be done in double precision. Most of other space transformations can remains in single precision.
Managing model matrix
Render engines often have a stack of several transformation before reaching the final object. To render a tire on car on a track, you have the main car position, then translation from car origin to tire origin, then rotation of tire around its axis. For complex models, you may have a dozen of transformation like this before reaching the actual point. But not all transformation should be done in Double. Car may be far far away from its origin, like Elon Musk’s Tesla Roadster, but once on the car, object are relatively close to each other. So only the first big translation should be accumulate in double:
If we represent this scene with a scene graph, we would have 3 Translation node T1, T2 and T3. T2 and T3 can use float precision and matrix accumulation can be done in float. Lets call T’=T2*T3.
However, even though T1 translation value can be stored in float, the computation T”=T1 * T’ should be done and stored in double precision and final Model matrix result stored in double precision. In the end, this scene graph should look like this:
A root node, immediately followed by a “Double translation” which generate a Double precision Model matrix, followed by classical scene graph: translations working in single Float precision and final object with single precision coordinated.
Managing View matrix
Same story for the view matrix. You should first locate the camera “Tripod” using a double precision translation and then locate your camera as usual:
So finale scene graph look like this:
And what about ModelView ?
In the end, we end up with a double precision Model matrix and a double precision View matrix. With them, we can have the final double precision ModelView matrix. And you will tell me “Yeah, but GPU don’t work with double precision, they need single precision ModelView matrix to compute all the vertices, lighting and so on”.
Yes, you are right. We cannot send double precision ModelView matrix directly to GPU. But suddenly, magic comes into play…
Remember, ModelView express coordinates of point relative to Camera. In our case, both point and Camera are somewhere around Saturn, they are both close to each other and thus, we don’t need a lot of precision to locate a point relative to camera. We don’t care anymore about World space. Remember this diagram:
This means, once we have computed ModelView = View * Model in double precision, we can cast back ModelView matrix to single precision and send it to GPU. ModelView matrix no more have large magnitude coefficient and precision won’t be an issue anymore.
Difficulty is how to define the initial double precision translation. Unfortunately, it depend on your use case. This big translation can be seen as a local origin for your scene, so for wide open world, this can be the tile position where you currently are. For fly simulator, you can update this translation with current plane position every hundred of km for examples. I had a use case where user wanted to render different factories in a Google earth like background. Off course, each factories were very detailed and you can literally see the pipes and ladders when zooming it. In this case, local origin was the position of currently selected factory.