19
\$\begingroup\$

My OpenGL scene has objects that are positioned at ridiculously far distances away from the origin. When I view these objects, and pan/rotate/zoom a camera around them, they 'jitter'. That is, the vertices comprising the objects seem to snap around an imaginary 3d grid of points. I've read this is a common problem because of the amount of information that can be stored using floating point precision (which OpenGL, and pretty much everything else uses). I don't get why this happens though.

When searching for a solution, I came across the very simple 'floating origin' fix, and it seems to work. I just transform everything so my objects are in the same relative positions but whatever my camera's looking at is close to the origin. I found an explanation here: http://floatingorigin.com/, but I couldn't follow it.

So...Could someone explain why positioning my scene very far away (say 10 million units) from the origin results in the erratic behaviour I observed? And also why moving it close to the origin fixes the problem?

\$\endgroup\$
2
  • 4
    \$\begingroup\$ Because if they didn't, they'd be fixed -point numbers. Tautological question, this. \$\endgroup\$
    – MSalters
    Commented Feb 14, 2012 at 13:10
  • 1
    \$\begingroup\$ True, but only when you understand what 'floating point' truly means. \$\endgroup\$
    – Kylotan
    Commented Feb 14, 2012 at 15:13

7 Answers 7

26
\$\begingroup\$

This is ALL due to the way floating points are represented in computers.

Integers are stored pretty straightforward; each unit is exactly "one" apart from the "previous" just as you'd expect with countable numbers.

With floating point numbers this is not exactly the case. Instead, several bits indicate the EXPONENT, and the rest indicate what's known as the mantissa, or fractional part that is then MULTIPLIED by the exponent part (implicitly 2^exp) to give the final result.

Look here for a visual explanation of the bits.

It is precisely because of this exponent being an actual part of the bits that precision begins to WANE once numbers grow larger.

To see this in action, let's do a faux-floating point representation without getting into the nitty-gritty: take a small exponent like 2 and do some fractional parts to test:

2 * 2^2 = 8

3 * 2^2 = 12

4 * 2^2 = 16

...etc.

These numbers don't grow very far apart at only exponent 2. But now let's try exponent 38:

2 * 2^38 = 549755813888

3 * 2^38 = 824633720832

4 * 2^38 = 1099511627776

Whoa, huge difference now!

The example, while not specifically going to the VERY NEXT COUNTABLE (that would be the very next fractional part depending on how many bits it is), is there to demonstrate the precision loss once numbers grow larger. The "next countable" unit in floats is very small with small exponents and VERY large with larger exponents, whereas in integers it's ALWAYS 1.

The reason the float origin method works is because it's scaling all these potentially large-exponent floating point numbers DOWN TO small-exponent so that the "next countables" (precision) can be very small and happy.

\$\endgroup\$
4
  • \$\begingroup\$ The examples you gave were really illustrative, thanks :) \$\endgroup\$
    – Pris
    Commented Feb 14, 2012 at 6:39
  • 3
    \$\begingroup\$ On the right track, but I wish you had used examples that are closer to the way floating point really works. It doesn't raise the mantissa to the exponent; it's mantissa * 2^exponent. \$\endgroup\$ Commented Feb 14, 2012 at 6:58
  • 3
    \$\begingroup\$ You're right, I knew that; I don't know what I was thinking. Edited my answer. \$\endgroup\$
    – user12197
    Commented Feb 14, 2012 at 7:02
  • 1
    \$\begingroup\$ @ScottW Nice edit! +1 \$\endgroup\$ Commented Feb 14, 2012 at 17:14
17
\$\begingroup\$

Because floating point numbers are represented as fraction + exponent + sign, and you only have a fixed amount of bits for the fraction part.

http://en.wikipedia.org/wiki/Single_precision

As you get larger and larger numbers, you simply do not have the bits to represent the smaller portions.

\$\endgroup\$
8
\$\begingroup\$

The classic in the field must be brought up: What every computer scientist should know about floating point numbers.

But the gist of it has to do with how single (double) precision floating point numbers are just a 32 bit (64-bit) binary number with 1 bit representing the sign, an 8-bit (11-bit) exponent of base 2, and a 23bit (52-bit) significand (the parentheses are the values for doubles).

That means the smallest positive number you can represent in single precision is 0.0000000000000000000001 x 2-127 = 2-22 x 2-127 = 2-149 ~ 1.40 x 10-45.

The next positive number is double that: 0.0000000000000000000010 x 2-127 = 2-148 ~ 2.80 x 10-45, and then the next number is the sum of the previous two 0.0000000000000000000011 x 2-127 = 3 x 2-149 ~ 4.2-45.

This continues increasing by the same constant difference until: 0.1111111111111111111111 x 2-127 = 2-126 - 2149 ~ 1.17549435 x 10-38 - 0.00000014 x 10-38 = 1.17549421 x 10-38

Now you have reached the normal numbers (where the first digit in the significand is 1) specifically: 1.0000000000000000000000 x 2-126 = 2-126 = 1.17549435 x 10-38 and the next number is then 1.0000000000000000000001 x 2-126 = 2-126(1+2-22) = 1.17549435 x 1.00000023.

\$\endgroup\$
2
\$\begingroup\$

The reason why floating-point numbers become less precise further from the origin is because a floating-point number is supposed to be able to represent large numbers. The way that this is done lends it the term "floating point". It splits up the possible values it can take (which is determined by its bit-length) so that there is about the same number for each exponent: For a 32-bit float, 23 of the bits define the mantissa or significand. So it will be able to take the value of 2^23 different values in each exponent range. One of these exponent ranges is 1-2 [2^0 to 2^1], so splitting the range 1 to 2 into 2^23 different values allows for a lot of precision.

But splitting the range [2^10 to 2^11] into 2^23 different values means the space between each value is a lot bigger. If it wasn't, then 23 bits wouldn't be enough. The whole thing is a compromise: You need an infinite number of bits to represent any real number. If your application works in a way that lets you get away with lower precision for larger values, and you benefit from being able to actually represent large values, you use a floating point representation.

\$\endgroup\$
1
  • \$\begingroup\$ just making a note here upon further review 7 years later... my numbers in my examples aren't particularly well-chosen. But the overall points are valid. \$\endgroup\$
    – Steven Lu
    Commented Jan 16, 2019 at 4:44
1
\$\begingroup\$

It can be a bit difficult to offer specific examples of how floating-point precision works. To supplement the other answers, here’s one. Let’s say we have a decimal floating-point number, with three digits of mantissa and one digit of exponent:

mantissa × 10exponent

When the exponent is 0, every integer in the range 0–999 can be represented precisely. When it’s 1, you’re essentially multiplying every element of that range by 10, so you get the range 0–9990; but now, only multiples of 10 can be represented accurately, because you still only have three digits of precision. When the exponent is at its maximum of 9, the difference between each pair of representable integers is one billion. You are literally trading precision for range.

It works the same way with binary floating-point numbers: whenever the exponent goes up by one, the range doubles, but the number of representable values within that range is halved. This applies to fractional numbers as well, which is of course the source of your problem.

\$\endgroup\$
0
\$\begingroup\$

In general, the resolution gets worse because the resolution is multiplied by the exponent value (2**exponent part).

in acknowledgement of josh's comment: the above was just to put the answer into a succinct statement. Of course, as I have tried to indicate on http://floatingorigin.com/, this is just be beginning towards an overall solution and your program could have jitter from a number of places: in the precision pipeline or other parts of the code.

\$\endgroup\$
2
  • \$\begingroup\$ This doesn't add anything that isn't already present in other answers. \$\endgroup\$
    – user1430
    Commented Dec 9, 2013 at 16:35
  • \$\begingroup\$ True: I realised I could describe the answer in a single line and thought someone might find a succinct answer useful. \$\endgroup\$ Commented Mar 24, 2014 at 6:31
-1
\$\begingroup\$

OpenGL depth buffer is not linear. The furhter you go, the worse resolution it has. I recommend to read this. Something taken from there (12.070):

In summary, the perspective divide, by its nature, causes more Z precision close to the front of the view volume than near the back.

And another one (12.040):

You may have configured your zNear and zFar clipping planes in a way that severely limits your depth buffer precision. Generally, this is caused by a zNear clipping plane value that's too close to 0.0. As the zNear clipping plane is set increasingly closer to 0.0, the effective precision of the depth buffer decreases dramatically. Moving the zFar clipping plane further away from the eye always has a negative impact on depth buffer precision, but it's not one as dramatic as moving the zNear clipping plane.

So you should move your near clipping plane the furthest you can and your far plane the closest you can.

\$\endgroup\$
4
  • \$\begingroup\$ -1: the question is about floating-point precision, not the precision issues with non-linear depth buffer representation. \$\endgroup\$ Commented Feb 14, 2012 at 6:51
  • \$\begingroup\$ It's possible that what I'm seeing is because of depth buffering issues. I'm using a lib on top of OpenGL to view my scene, and I'm making the assumption its setting up the camera, view, and near and far clipping planes to account for the size and position of the geometry (since the viewer tool seems to automagically set an optimal view for the scene contents). But I guess this may not be the case -- I'll try playing around with the clipping planes leaving the original position intact and see what happens. \$\endgroup\$
    – Pris
    Commented Feb 14, 2012 at 6:54
  • \$\begingroup\$ 2Nathan Reed: Author wrote, that he has OpenGL scene, so I thought, it could be this issue, too. \$\endgroup\$
    – zacharmarz
    Commented Feb 14, 2012 at 8:28
  • \$\begingroup\$ This issue may seem similar or related, but the depth buffer values are most definitely NOT stored in a way compatible with floating point numbers. It is a fixed-point format. It is because of this that the answer may be misleading. \$\endgroup\$
    – Steven Lu
    Commented Feb 14, 2012 at 15:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .