使用定点算术以极高的精度和大数对向量进行归一化

Normalizing a vector with great accuracy and big numbers using fixed-point arithmetic

提问人:Mark 提问时间:1/26/2023 最后编辑:Mark 更新时间:1/28/2023 访问量:266

问:

我为什么需要这个?

我正在制作一个关于太空的游戏。为了让太空游戏发挥作用,它需要大而准确的数字。浮点数对于这样的应用来说并不是很好,因为如果你在世界上走得很远,精度会更差,所以物理特性不会是一样的,依此类推。

怎么了

在太空中,通常倾向于行星。因此,要创建一个,我必须生成球体(行星)网格。问题是,如果我想要一颗像木星这样的行星(半径为 ~69911km),当我归一化一个点并将其乘以行星的半径,从而将其“放置”在行星表面时,我没有足够的精度(网格不完全是圆形的,误差约为 10-15m)。这是该错误的一些镜头(行星的闪烁是由于视频压缩造成的,网格中最小正方形的边长约为 ~10m)。问题不在于数字,而在于我用来规范化点的方法。

在视频中,我使用的是精度为 20 位的 64 位定点数字。我只是通过将点与快速平方反比根相乘来对点进行归一化(我只使用牛顿方法的一次迭代,两次更好,但仍然不够)。其中,我将定点数转换为双精度浮点数,仅用于快速平方反比根。

我的想法和尝试

我认为获得如此精确的向量归一化的唯一解决方案是使用迭代方法:

  1. 正常计算点(将其归一化并乘以行星半径)
  2. 将它推得越来越接近半径,直到误差足够小(我希望它是 0.01m)

这 2.步骤是困难的一步。我没有数学技能,也没有计算机科学领域的任何经验来解决这个问题。我们可以迭代地增加或减少一个向量,但是多少量,以及我们如何知道我们不会超过它。我想再次尝试牛顿方法,但使用的是实际坐标,而不是长度的平方反比根。其中包括 128 位除法以保持所需的精度,这是无法有效完成的(请记住,对于行星网格的每个顶点,我必须这样做一百万次)

对此有什么想法吗?

而且尺度还不止于此,如果我想做一颗恒星怎么办,太阳的半径是木星的10倍。还有更大的明星。

我可能不是第一个思考这个问题的人,因为许多人都尝试过以真实的比例制造地球(请记住,地球半径比木星小 10 倍)。而且我可能不会是最后一个尝试这个的人,所以迟早会有答案。

更新 1,经过一些评论

  1. 是的,我使用具有 64 位数字的 3D 向量,精度为 20 位作为数字。而且我显然正在使用行星的局部坐标系来创建点(我需要根据需要进行归一化)。
  2. 使用浮点数是我最后的希望。它们实际上不是为这种应用而设计的,因为不同尺度的精度存在差异。
  3. 我想为天体(恒星、行星)制作的尺度至少是太阳的大小。对于对象的位置,我使用了两个 64 位整数(实际上是一个 128 位整数),因此如果我想制作它,游戏中的世界可以比我们实际的可观察宇宙更大(多亏了定点数字)。但是我不需要 128 位整数来表示行星顶点的位置,64 位就足够了(尽管我可以在此过程中使用 128 位整数来计算值)。正如我所提到的,网格顶点的误差最好小于 0.01 米,其中 1 个坐标单位为 1 米(想象一下在行星表面行走,任何大于 0.01m 的东西都可能很明显)。

我之所以如此推定点数,是因为由于物理学的原因,我不能将物体的位置用作浮点数。假设我们有一个大行星,比木星还大。因为我需要计算相对于木星位置的顶点,然后在着色器中从它们中减去相机位置(主要限于 32 位浮点数),所以误差只会加起来,而且会很明显。有一些解决方法,但核心问题总是会出现在使用浮点数时。

TL;博士

我需要一种方法来计算半径可以大到 1000000000 = 10^9 的球体上的点,但误差小于 0.01,给定该点需要静止的角度。而且这种方法需要相对高效。

数学 精度 归一化 定点

评论

0赞 Sven Nilsson 1/26/2023
也许使用 128 位整数作为全局尺度(宇宙)位置,然后相对于全局坐标 64 位浮点数对于局部内容(行星)来说应该足够了。
0赞 Mark 1/27/2023
问题在于计算球体上点的精度。我实际上确实使用 128 位数字作为宇宙中的位置,但我真的不能用它们(除法)做所有的算术运算,所以除非你有一个不使用除法的方法,否则请分享它。64 位浮点数对于我在问题中提到的比例来说是不够的。
0赞 Sven Nilsson 1/27/2023
也许你不需要球体是完美的?在行星网格中达到一定数量的三角形后,您可以通过使用线性插值对现有三角形进行细分来创建其他三角形。这些区域将是平坦的,但在这样的规模下很难注意到。快速平方根反比确实很快,但缺乏精度,不适合您正在做的事情。即使是常规的平方根也可能不够精确,精度为 20 位。有一些库可以提高精度,但计算速度可能非常慢:github.com/fahickman/r128
0赞 Mark 1/27/2023
球体在某种程度上必须是完美的。在当前误差的情况下,在地形生成/加载后,它实际上会非常明显。您提供的回购看起来很有希望,请看一看。谢谢!
0赞 Brett Hale 1/27/2023
一个好的折衷方案可能是双倍算术。这不是一个 float128 四精度浮点数 - 而是一个带有算术的“高低”拆分,可以利用现有的双精度浮点指令,同时(通常)产生 ~ (107) 位的精度。这需要一些研究,但有现有的库和论文描述了基本操作。(即使是浮子-浮子可能就足够了,而且速度要快得多)

答:

-1赞 Pascal Getreuer 1/27/2023 #1

我的理解是你是

  1. 制作一个 3D 向量,以米为单位表示为 64 位定点数,精度为 20 位。
  2. 将向量归一化为单位长度,通过快速平方根反比近似进行缩放。
  3. 按木星半径 69911 公里缩放矢量。

挑战在于,当按行星半径缩放时,归一化向量中来自 inv sqrt 近似或舍入的任何误差都会被放大。为了达到 0.01 m 的精度,单位矢量的长度需要精确且误差小于

0.01 米 / 69911 公里 = 1.43×10-10.

为此,inv sqrt 需要精确到 11 位或更高,单位向量至少需要 32 个小数位(舍入误差为 2-33 = 1.16×10-10)。

我建议在局部坐标系中计算表面点,原点位于行星的中心,用 64 位浮点数表示,然后将这些点转换为宇宙坐标系。64 位浮点数精确到 ~16 位,足以获得所需的精度。这似乎是最简单、最有效的解决方案,假设 64 位浮点数是一种选择。

或者,对于迭代的“轻推”方法,您可以这样做:

R = planet radius
x, y, z = surface point with correct angle but possibly wrong length
for 1, 2, ...
  scale = 0.5 * (R² / (x² + y² + z²) + 1)
  x *= scale
  y *= scale
  z *= scale

这是一个快速收敛的定点迭代。 运行示例,从非常不准确的曲面点 x, y, z = [6991.1, 55928.8, -6991.1] 开始:

#  x        y          z        Radius
-------------------------------------------------
0  6991.10  55928.80  -6991.10  56795.96489065047
1  8791.84  70334.70  -8791.84  71425.22857460588
2  8607.42  68859.40  -8607.42  69927.05096841766
3  8605.45  68843.60  -8605.45  69911.00184215968

进一步的想法:

请记住,对于行星网格的每个顶点,我必须这样做一百万次

从视频中可以看出,您已经在应用细节级别方案来减少远处的顶点数。当接近表面时,也可以利用分层网格来减少顶点:当它们的父级离开屏幕时,根据视锥剔除跳过更精细的顶点。

评论

0赞 Mark 1/27/2023
我想完全避免使用浮点数。当涉及到大距离时,你对精度的控制要少得多,而且它们不能很好地适应物理系统。如果我是正确的,你提到的“助推”方法是牛顿方法,你必须在其中计算向量的平方长度(x²+y²+z²),这没关系,它将占用 128 位(因为将 2 个 64 位数字相乘,你会得到一个 128 位数字,如果你不打算失去精度)。那里没有问题,但你需要除以两个 128 位数字,这是一个问题
2赞 Mad Physicist 1/27/2023
@Mark。“花车”不是魔术。它们是 64 位整数,精度为 53 位。并且通常具有良好的 CPU 支持
0赞 Mark 1/28/2023
@MadPhysicist。它具有 53 位精度,但并非所有值的精度都相同。整数肯定有更好的支持。
1赞 Mad Physicist 1/28/2023
@Mark。取决于您如何使用它们:)
3赞 Pascal Getreuer 1/28/2023
@Mark,我同意减去宇宙位置对于浮点数是有问题的(en.wikipedia.org/wiki/Catastrophic_cancellation 的情况)。为了澄清起见,我建议仅在局部坐标系中使用 64 位浮点数:(1) 在此系统中将曲面点计算为浮点数。(2) 将结果转换为 128 位表示形式。(3)平移到宇宙坐标系。在有用的地方使用每个数字表示形式。