Skip to main content
GameDev.net gamedev.net
🔒 Locked

float or double for my vertices / normals?

Started by codingJoe Feb 21, 2019 at 7:35 AM 25 replies 8.3k views
Original Post
codingJoe
codingJoe

Hello,

I have my 3D application use floats for almost everything. Now I realize that I lack precision (typically for transformation matrices, that are cumulative with a hierarchial arrangement of objects). So I want to switch to double for matrix/vector calculation and most other things.

My question is: what should I do with my vertices and normal vectors? From a storage point of view, going from float to double will almost double my file sizes, which is not really problematic. But passing doubles to a graphic card is another story I think, since twice that many data need to be passed, which very probably represents a bottleneck. I am using pure old openGl, but want to switch to vtk or something similar. What are people normally doing in this situation?

Thanks for any insight

_Silence_
_Silence_
13 minutes ago, codingJoe said:

But passing doubles to a graphic card is another story I think, since twice that many data need to be passed, which very probably represents a bottleneck.

Not only due to memory, but mainly for computation. Unless you have very specific uses and constraints, use float to store your vertices, normals and other attributes.

pcmaster
pcmaster

Multiplying pretty many rotational matrices over each other doesn't introduce too much error. The error can pop up with positions, if they're really big. Do you have really big positions or scales in your matrices? What for?

Gnollrunner
Gnollrunner


I'm not sure if this is really addressing your problem but I'll give you my experiences with double and float.

I'm building worlds with a very large range of numbers. It requires doubles or alternatively some likely error prone system of coordinate translation. My current strategy is to calculate everything in double on the CPU side. Although I'm sure you can mix and match in places, I think for initial development keeping everything double will save you some heartache. Later you can go back an optimize things if you feel the need.

The GPU is a whole different story since typically many cards aren't really optimized for double (apparently many intentionally). The trick is when going from CPU to GPU to translate everything so your camera is near (0.0,0.0,0.0) and as you do so simultaneously convert to float.

Since things in the distance need far less precision this works fairly well. For instance if a far away hill that is 100 meters height is now 99.5 meters high due to precision loss, a player will never notice it. As you approach the hill you will have to send it's vertexes down again anyway to get the required detail with you terrain and and that point the precision will fix itself since the data will be progressively closer to the camera.

This does however add one more requirement to your system....... You need a very robust LOD (Level of Detail) system. Detailed objects at distance are going to be a problem be they will induce Z-fighting since the precision simply isn't there. How you handle this has a lot to do with your basic graphics system. But generally you are not displaying small objects at distance anyway, so hopefully a lot of his should take care of itself.

Again I'm not sure if this really answers you question, but maybe you can glean something from it.

codingJoe
codingJoe

Thanks for the replies.

I do not have large positions, but I have sometimes a chain of 20+ objects built on top of each other. Each object stores its local transformation matrix. If you want to modify the absolute orientation of the object at the end of the chain, e.g. rotate it absolutely by 1 degree around a specific axis, then you first compute 20X matrix multiplications to get the absolute matrix, then you rotate that matrix, and compute another 19X matrix multiplications, inverse it, and multiply it with the modified matrix, in order to obtain the new local matrix for that object.

I am aware that above operation can be performed in a more simple way, but there are many such similar cases where a float just isn't enough anymore. Another example is to read and set the same absolute matrix of that object that is at the end of the chain: the object can very slowly drift.

(just for precision: I am not really using matrices, but position vectors and quaternions. But the problem is similar)

TeaTreeTim
TeaTreeTim

Try and find the operations that accumulate errors and post them, there may be a better way to do them. For example:

Instead of asin(x) try 2 * atan(x / (1 + sqrt(1 - x * x))
Instead of pow(a,b) try exp(log(a) * b)

I would say the basic premise of expecting hundreds of operations to be reversed to return the same float might need a basic design rethink but you didn't really say what you're doing or why.

lawnjelly
lawnjelly
34 minutes ago, codingJoe said:

I am aware that above operation can be performed in a more simple way, but there are many such similar cases where a float just isn't enough anymore.

Then why use this example? If you can provide examples where this is a problem you will be more likely to get solutions.

51 minutes ago, codingJoe said:

Another example is to read and set the same absolute matrix of that object that is at the end of the chain: the object can very slowly drift.

Can you elaborate on this? And what are the actual numerical values of the transform where you are getting drift? With quaternions could it be something like you are not normalizing them?

pcmaster
pcmaster

@TeaTreeTimpow(a,b) actually is implemented as exp(log(a) * b) on modern architectures. There's no power intrinsic function (neither in x86. x87, amd64, SSE, SSE2, ..., nor on the GPUs) :)

TeaTreeTim
TeaTreeTim
1 minute ago, pcmaster said:

@TeaTreeTimpow(a,b) actually is implemented as exp(log(a) * b) on modern architectures. There's no power intrinsic function (neither in x86. x87, amd64, SSE, SSE2, ..., nor on the GPUs) :)

I tested these examples about 10 years ago (so probably DX 11 on a card circa 2010 ish) to reduce floating point rounding errors in HLSL shader operations. They were just supposed to be an example of modified functions reducing the risk of rounding errors.

Puffin
Puffin

GPUs can be very slow with doubles so there's usually a better way, but it depends on the case. Either find a way to change your calculations so that floats are sufficient, or use doubles only on the CPU. For example in the large-world case, you can use double matrices on the CPU for model-to-world-space and world-to-view-space transformations, and then combine them into a model-to-view-space matrix and convert it to floats for the GPU, so the GPU can transform vertices directly from model-space to view-space and never has to calculate with large floats (for objects close to the camera at least). Additionally, if you can't easily transform everything to view-space and would prefer doing some GPU calculation in world-space (e.g. some world-space light maps that are difficult to rotate), then instead of using the actual world-space where coordinates near the camera can be too large for floats, you can use a camera-centered-world-space that is centered around the camera but oriented according to world axes, sending a model-to-camera-centered-world-space matrix to the GPU.



turanszkij
turanszkij

For normals or any other normalized vectors, you likely don't need even float precision, so a half (16-bit float) should be plenty per component, so a half3 for example. But most renderers I think would even go for less precision in the vertex buffer, for example storing all xyz components of normals in a 32 bit uint (8 bit per component, like a signed byte, so 24 bits, you have an extra 8 bits left over, for something else). I specifically am a fan of storing normals in model files as float3, just so that it is backwards compatible any time you need to update the renderer to support a different kind of compression. Then I pack the normals into 24 bits inside the vertex buffer, and use the remaining 8 bits for eg. material index.

For positions, usually float precision should be enough. However, it is completely feasible to have your position components as halfs in the vertex buffer, and only have full 32 bit precision for the instance world matrix. The vertex shader can read the vertex buffer with half the bandwidth, but after loading, it will store it in 32 bit float register, so after this all computations with world matrices will be 32 bit precision. As for double precision, it will probably be very slow on the GPU (if even supported). In case you are expecting precision errors when shading, you can shade in view space instead of world space, so precision will be relative to the distance to the camera, not the world origin.

_Silence_
_Silence_
2 hours ago, codingJoe said:

I am aware that above operation can be performed in a more simple way, but there are many such similar cases where a float just isn't enough anymore

If you are convinced that float isn't enough for you, then why not moving to double ? In a previous work we had a simulator capable to show very tiny (molecular) up to very huge (galaxies and even larger) without any seams. We were using doubles in all the chain. So this is feasible. But doing so has a cost: everything will be far slower than on full-float systems (and not just slightly slower).

This all depends on what you want to do. If people here cannot know what you are trying to achieve, you'll get answers, but tons of guesses, of this can be done, or you might be doing something wrong, or you might be able to do something in a better way. But all these throws are just thrown as guesses. If you want (and it seems you are looking to find the best answer) to know if you should move to double and where (data, CPU, GPU...), then give people concrete explanation of what you are trying to do. ie. Are you doing a space simulator ? Are you doing a city visualizer ? Are you doing something more on the science side ? Or is it that your game map can be very huge and you lack LOD ? All these scenarios might end-up with different solutions to resolve inaccuracies.

Hodgman
Hodgman

If your objects are less than around 10km in diameter and have details around 1mm in size. Then use floats for your vertex data.

If your objects are larger then 10km in size, split them up into sub-objects and still use floats for your vertex data.

If your objects are bigger than 10km, have details smaller than 1mm, and can't feasibly be split up... are you sure? Well, go ahead and use 32bit integers for your vertex data.

If your objects are bigger than 1000km, have details smaller than 1mm, and can't feasibly be split up... you're shitting me? Well, go ahead and use doubles for your vertex data... But this is very odd.

If your objects are far, far smaller than 1km, consider using 16bit fixed point for your vertex data.


If your world is bigger than 10km, then go ahead and think about fixed point, hierarchical (with high precision intermediate results) or 64bit transforms on the CPU side, but there's still no need for doubles on the GPU. Once you've computed your model-to-view transforms in high precision, you can safely truncate them to 32bit precision before sending them to the GPU, as the final matrix will take relatively small model-space values as input and produce relatively small view-space outputs. Just never use raw global-origin world-space values on the GPU.

fleabay
fleabay
2 hours ago, Hodgman said:

If your objects are less than around 10km in diameter and have details around 1mm in size. Then use floats for your vertex data.

Should objects be measured on the CPU, GPU or on the screen? :) You can't assume what size the units represent and it does matter when using your guidelines listed.

🙂🙂🙂🙂🙂<←The tone posse, ready for action.
_Silence_
_Silence_
39 minutes ago, fleabay said:

Should objects be measured on the CPU, GPU or on the screen? :) You can't assume what size the units represent and it does matter when using your guidelines listed.

No. It's just a proportion. The order of magnitude is the same whether you talk about meters, kilometers, yards or whatever. So if you have a map of 10 km with the minimum detail is about 1mm, it is the same if you have a 10 meters map and a detail about 1e-3mm and so on.

Or did I understand wrongly what you told ?

fleabay
fleabay
56 minutes ago, _Silence_ said:

Or did I understand wrongly what you told ?

I think you did.

You can put a whole planet in 1 unit or you can fit a grain of sand in 1 unit. It depends on the game. There is no such thing as kilometers or millimeters inside a computer. It matters with scale when exporting/importing meshes from DCC tools to game engine but also with floating point accuracy inside the game itself. Without defining a base unit size you can make no assumptions.

A unit is not a size. Unit math is what causes floating point accuracy errors. You must define the size of a base unit to make real world comparisons thus my observation to Hodgman's assumed unit size...

"You can't assume what size the units represent and it does matter when using your guidelines listed. "

🙂🙂🙂🙂🙂<←The tone posse, ready for action.
Irusan, son of Arusan
Irusan, son of Arusan
25 minutes ago, fleabay said:

A unit is not a size. Unit math is what causes floating point accuracy errors. You must define the size of a base unit to make real world comparisons thus my observation to Hodgman's assumed unit size...

"You can't assume what size the units represent and it does matter when using your guidelines listed. "

I think you're getting hung up on the units, when @Hodgman is really just using them as an example to talk about magnitudes of scale. He's saying that if you're covering around 7 orders of magnitude or less (i.e 10km to 1mm, or 10m to 1 micron, or 1AU to 10 miles, or whatever) then you're fine with a float.

fleabay
fleabay
14 minutes ago, Irusan, son of Arusan said:

I think you're getting hung up on the units, when @Hodgman is really just using them as an example to talk about magnitudes of scale.

If I try and sell you something for 100 but say I will take 10, then you ask me if I am talking about dollars, I would say "don't get hung up on the units, I'm talking about magnitude". :D

🙂🙂🙂🙂🙂<←The tone posse, ready for action.
Hodgman
Hodgman
7 hours ago, fleabay said:

Should objects be measured on the CPU, GPU or on the screen? :) You can't assume what size the units represent and it does matter when using your guidelines listed.

There's a reason I quoted two numbers in there instead of 1. You can multiply the whole thing by 10 if you like to get a safe "10km to 1cm detail" range.

Fixed point 32bit was mentioned where obviously you can't use int(1) = 1m and still have millimetre details... So it holds that the same arbitrary unit techniques can obviously be used with float as well.

Almost everyone works in meters and the other scientific units by default though, so I quoted my rules of thumb using those units and human scales.

Did you really just assume I was telling you to store vertex position as meters in an integer and that will somehow preserve sub-milimeter details? And then repeatedly try to point out that this isn't true?

_WeirdCat_
_WeirdCat_

Once i was writing earth sim, i used double precision to define vertices and do the collisoon math, i used 1 unit as one meter so even when earth center was at 0,0,0 i had to deal with numbers bigger than

const double earth_radius = (6371.0 * 1000.0); didnt work well for.collision and had.many precision issues, was wondering for about a year on this, answer was use integrals, and 'pivots'

So lets assume theres an earth radius aas this double thing

Now to define a vertex position you define a pivot point (reference point)

All that matters to use the less precision: 60000-59999 equals to 1 right?

But what about 60-59? Aint that easier to compute?

Unfortunatetly i didnt code that = waste of time.

One thing is to store a int number and denominator int to recalculate floating point but even long double could output here 0.999999 not 1

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.