5
\$\begingroup\$

I'm working on an open-world game. I noticed glitches when I move far away from the origin. The objects seem to be shaking or wiggle. Especially the z-fighting seems to wiggle. And the objects that move. If I understand correctly this is caused by floating point precision problems. Currently I'm using floats for everything on CPU side and in shaders, I don't use doubles.

Before I try to come up with a solution I want to understand how does float imprecision lead to objects shaking on the screen. (Why do they shake instead of disappearing or stopping or something?) And where does the error come from: the calculations on CPU side (camera movement / physics calculations of objects) or calculations on GPU side (transform in shaders / depth-buffer not being precise enough)? Why do objects not shake when they are far away from the camera?

(I asked this before but realized my question was very broad, I wanted to be more specific.)

\$\endgroup\$
  • \$\begingroup\$ Note that you can also edit questions to refine their focus, rather than deleting & re-creating. (Particularly if they don't have answers yet responding to the old framing) We can clean up comments that are no longer relevant to the refined version. \$\endgroup\$ – DMGregory Sep 23 '18 at 20:04
  • \$\begingroup\$ @DMGregory considering it says "new contributor" it is likely that they made an account a long while ago to ask that question and then either lost access or forgot the username. I don't think that tag stays on the second question. \$\endgroup\$ – The Great Duck Sep 25 '18 at 12:29
  • \$\begingroup\$ @TheGreatDuck I'm not commenting out of speculation - this user and I conversed on that earlier question that was asked two days ago. ;) \$\endgroup\$ – DMGregory Sep 25 '18 at 12:32
  • \$\begingroup\$ @DMGregory the question is referenced in their post so I know it isn't out of speculation per se. I was just saying that my experience says the question may have been from an alt account. I could be wrong. :-) \$\endgroup\$ – The Great Duck Sep 25 '18 at 23:02
15
\$\begingroup\$

Quick recap (this is probably not news to you): a floating point number is broken into...

  • A sign bit to distinguish positives & negatives
  • Some exponent bits which tell us what broad range of numbers we're talking about (eg. 0.25-0.5, 0.5-1, 1-2, 2-4, 4-8...)

    Note that the ranges double in size each time we take another step further from zero

  • Some mantissa bits which address a specific number in that range.

    00000000000000000000000 = lowest number in the range
    11111111111111111111111 = last number before the start of the next range

For a 32-bit "single-precision" floating point number, we have 24 (23 stored + 1 implicit) of these mantissa bits to work with. That means in the range \$[2^n, 2^{n+1})\$ we have \$2^{23}\$ representable numbers, spaced \$2^{n-23}\$ apart.

If we try to store a number that falls between two representable numbers, it gets rounded to the nearest one, making an error of up to \$2^{n-24}\$. That could, for example, nudge an object or vertex or camera position a little to the left or to the right of where it's supposed to be. This answer includes a table of what relative precision we have at various ranges from the origin.

Once we lose that precision in a calculation, we don't get it back if we later bring the result back closer to the origin - there are no bits left to encode that lost high-precision information, so it's not visible to any calculations downstream. This precision loss can snowball through repeated calculations, if we round a number, then use the rounded result to make a new number that also gets rounded...

There's also a phenomenon called "catastrophic cancellation" which can happen if we try to subtract two large numbers that are reasonably similar (like finding the offset between a camera 10 km from the origin, and an object 1 m away from the camera). Most of the bits in each object's position are talking about what they have in common (their 10 km offset from the origin) and fewer are left to talk about how they differ (the 1 m offset from the camera to the object). When we subtract them, the bits in common all cancel out and we're left with only a few significant digits in the result.

Where exactly this precision loss hits your game will depend on a lot of details of your setup and the exact order in which you apply your operations. A worst case would be transforming all your vertices to world space (so vertices far from the origin get rounded to a coarser grid of representable positions) before then transforming those worldspace vertices into view/screen space. If you combine these operations into a single matrix, you might be able to save some intermediate rounding and/or take advantage of higher precision available for intermediate results in processor registers. But even algorithms tuned for numerical stability will start showing rounding errors if you push them far enough.

It also depends on how you're looking at the content in question:

Why do objects not shake when they are far away from the camera

They do, in the sense that their rendered position gets rounded away from their "true" position we might calculate if we had infinite precision available. But here perspective cameras save us: things very far away get drawn very small. That includes shrinking the size of the errors. And we have only a finite precision output to present: the number of pixels on the screen. At a long distance away, even a substantial error due to floating point precision might project to a difference much smaller than a single screen pixel, so we don't see any jitter or error in the rendered result.

But when the camera itself is a long way from the origin, then we can expect those rounding errors to occur for nearby objects as well, where a much smaller error projects to a pixel difference or more.

So that's the basic idea: a long way from the origin, the vectors we can represent with floating point numbers get sparser. When we want some specific vector (like the position of a vertex relative to the camera), we might be able to compute only a rounded result close to that desired vector.

As the object or the camera moves, this desired vector will change, and it might move from being close to one representable value to being close to another. At that point, the rounded result will snap to the new closest representable value, which can be visible as a discrete "pop." When our desired value hovers close to the threshold between two representable results (eg. when you have two very similar surfaces z-fighting), it might pop back and forth between one rounded value and another on consecutive frames, so it appears to jitter, vibrate, or dance in a distracting way.

Animated example of loss of float precision See a high-res version of this visualization rendered in WebGL here

In this example, I'm visualizing the travel of a blue dot along a 2D plane in floating point coordinates. To make it easier to see, I'm using only 4 mantissa bits, so there are only 16 representable values within each exponent band. The grid lines represent representable x & y values, and the black dot tracks the closest representable point to the blue dot's true location.

Close to the origin, the two dots look like one, but as we go further out, the black representable point strays further and further from the blue source position, bouncing back & forth around it.

These effects can also creep in through the game's physics, limiting the precision with which the physics engine can apply impulses or integrate velocities, so what would have looked like a smooth diagonal movement close to the origin takes on a stairstep quality as an object's path snaps to the nearest representable positions. Or making an offset that would have correctly separated two penetrating objects close to the origin get rounded to a result that keeps them overlapping, leading to more corrections next frame that can make the objects appear to shake or swim through each other.

\$\endgroup\$
  • \$\begingroup\$ Thank you for the detailed answer! That gif also helps to understand whats going on. I tested with objects that move along a world axis instead of random direction and also when they are in the center of the screen and the shaking is reduced greatly. I also noticed that even if the objects don't visibly shake, the z-fighting still shakes. The depth-calculations seem to be very sensitive for this. \$\endgroup\$ – Newline Sep 24 '18 at 5:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.