提问人:Mark 提问时间:1/26/2023 最后编辑:Mark 更新时间:1/28/2023 访问量:266
使用定点算术以极高的精度和大数对向量进行归一化
Normalizing a vector with great accuracy and big numbers using fixed-point arithmetic
问:
我为什么需要这个?
我正在制作一个关于太空的游戏。为了让太空游戏发挥作用,它需要大而准确的数字。浮点数对于这样的应用来说并不是很好,因为如果你在世界上走得很远,精度会更差,所以物理特性不会是一样的,依此类推。
怎么了
在太空中,通常倾向于行星。因此,要创建一个,我必须生成球体(行星)网格。问题是,如果我想要一颗像木星这样的行星(半径为 ~69911km),当我归一化一个点并将其乘以行星的半径,从而将其“放置”在行星表面时,我没有足够的精度(网格不完全是圆形的,误差约为 10-15m)。这是该错误的一些镜头(行星的闪烁是由于视频压缩造成的,网格中最小正方形的边长约为 ~10m)。问题不在于数字,而在于我用来规范化点的方法。
在视频中,我使用的是精度为 20 位的 64 位定点数字。我只是通过将点与快速平方反比根相乘来对点进行归一化(我只使用牛顿方法的一次迭代,两次更好,但仍然不够)。其中,我将定点数转换为双精度浮点数,仅用于快速平方反比根。
我的想法和尝试
我认为获得如此精确的向量归一化的唯一解决方案是使用迭代方法:
- 正常计算点(将其归一化并乘以行星半径)
- 将它推得越来越接近半径,直到误差足够小(我希望它是 0.01m)
这 2.步骤是困难的一步。我没有数学技能,也没有计算机科学领域的任何经验来解决这个问题。我们可以迭代地增加或减少一个向量,但是多少量,以及我们如何知道我们不会超过它。我想再次尝试牛顿方法,但使用的是实际坐标,而不是长度的平方反比根。其中包括 128 位除法以保持所需的精度,这是无法有效完成的(请记住,对于行星网格的每个顶点,我必须这样做一百万次)
对此有什么想法吗?
而且尺度还不止于此,如果我想做一颗恒星怎么办,太阳的半径是木星的10倍。还有更大的明星。
我可能不是第一个思考这个问题的人,因为许多人都尝试过以真实的比例制造地球(请记住,地球半径比木星小 10 倍)。而且我可能不会是最后一个尝试这个的人,所以迟早会有答案。
更新 1,经过一些评论
- 是的,我使用具有 64 位数字的 3D 向量,精度为 20 位作为数字。而且我显然正在使用行星的局部坐标系来创建点(我需要根据需要进行归一化)。
- 使用浮点数是我最后的希望。它们实际上不是为这种应用而设计的,因为不同尺度的精度存在差异。
- 我想为天体(恒星、行星)制作的尺度至少是太阳的大小。对于对象的位置,我使用了两个 64 位整数(实际上是一个 128 位整数),因此如果我想制作它,游戏中的世界可以比我们实际的可观察宇宙更大(多亏了定点数字)。但是我不需要 128 位整数来表示行星顶点的位置,64 位就足够了(尽管我可以在此过程中使用 128 位整数来计算值)。正如我所提到的,网格顶点的误差最好小于 0.01 米,其中 1 个坐标单位为 1 米(想象一下在行星表面行走,任何大于 0.01m 的东西都可能很明显)。
我之所以如此推定点数,是因为由于物理学的原因,我不能将物体的位置用作浮点数。假设我们有一个大行星,比木星还大。因为我需要计算相对于木星位置的顶点,然后在着色器中从它们中减去相机位置(主要限于 32 位浮点数),所以误差只会加起来,而且会很明显。有一些解决方法,但核心问题总是会出现在使用浮点数时。
TL;博士
我需要一种方法来计算半径可以大到 1000000000 = 10^9 的球体上的点,但误差小于 0.01,给定该点需要静止的角度。而且这种方法需要相对高效。
答:
我的理解是你是
- 制作一个 3D 向量,以米为单位表示为 64 位定点数,精度为 20 位。
- 将向量归一化为单位长度,通过快速平方根反比近似进行缩放。
- 按木星半径 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
进一步的想法:
请记住,对于行星网格的每个顶点,我必须这样做一百万次
从视频中可以看出,您已经在应用细节级别方案来减少远处的顶点数。当接近表面时,也可以利用分层网格来减少顶点:当它们的父级离开屏幕时,根据视锥剔除跳过更精细的顶点。
评论
双倍算
术。这不是一个 float128 四精度浮点数 - 而是一个带有算术的“高低”拆分,可以利用现有的双精度浮点指令,同时(通常)产生 ~ (107) 位的精度。这需要一些研究,但有现有的库和论文描述了基本操作。(即使是浮子-浮子可能就足够了,而且速度要快得多)