如何优化距离功能?


23

在开发一款相当简单的类似RTS的游戏时,我注意到我的距离计算正在影响性能。

在任何时候,都会进行距离检查,以了解一个单位是否在其目标范围内,射弹是否已达到其目标,玩家是否跑过了皮卡,常规碰撞等。列表继续进行,并检查两点之间的距离经常使用。

我的问题就是这个。我想知道除了常规的sqrt(x * x + y * y)方法以外,游戏开发人员还有哪些替代方法可以检查距离,如果我们每帧执行数千次,这将非常耗时。

我想指出的是,我了解了曼哈顿的距离和平方的距离比较(通过跳过sqrt瓶颈)。还要别的吗?



如果您有不希望移动的对象(例如,建筑物),则最好采用距离函数的2D泰勒级数,在平方项处截断,然后将结果函数存储为该特定建筑物的距离函数。这样可以将一些艰巨的工作重新定位到初始化过程,并可以加快处理速度。
亚历山大·格鲁伯

Answers:


26

TL; DR; 您的问题不在于执行距离功能。您的问题是多次执行距离功能。换句话说,您需要一种算法优化而不是数学优化。

[编辑]我正在删除答案的第一部分,因为人们讨厌它。问题标题是在编辑之前要求替代距离功能。

您正在使用距离函数,每次都在计算平方根。但是,您可以简单地替换它而根本不使用平方根,而是计算平方的距离。这将为您节省很多宝贵的周期。

距离^ 2 = x * x + y * y;

这实际上是一个常见的把戏。但是您需要相应地调整计算。在计算实际距离之前,也可以将其用作初始检查。 因此,例如,代替计算相交测试中两个点/球之间的实际距离,我们可以计算距离平方,然后与半径平方而不是半径进行比较。

编辑,就在@ Byte56指出我没有阅读问题之后,并且您知道平方距离优化了。

在您的情况下,很不幸,我们在计算机图形学中几乎只处理欧几里德空间,并且距离的定义与Sqrt of Vector dot itself欧几里德空间中的一样。

距离平方是您要获得的最佳近似值(就性能而言),我看不到有什么能击败2个乘法,一个加法和一个赋值。

所以你说我不能优化距离函数,我该怎么办?

您的问题不在于执行距离功能。您的问题是多次执行距离功能。换句话说,您需要一种算法优化而不是数学优化。

关键是,而不是检查玩家与场景中每个对象的交集,而是每个帧。您可以轻松利用空间连贯性来发挥自己的优势,只检查播放器附近的物体(最有可能发生碰撞/相交的物体)。

通过将这些空间信息实际存储在空间分区数据结构中,可以轻松完成此操作。对于一个简单的游戏,我建议使用Grid,因为它基本上易于实现并且很好地适合动态场景。

每个单元格/框都包含一个对象的列表,该对象的列表包含在网格的边界框中。而且很容易跟踪玩家在这些单元格中的位置。对于距离计算,您只需要检查同一个或相邻像元内的那些对象而不是场景中所有物体的玩家距离。

一种更复杂的方法是使用BSP或Octrees。


2
我相信问题的最后一句话说,OP正在寻找其他替代方案(他们知道使用平方距离)。
MichaelHouse

@ Byte56是的,您是正确的,我没有读过。
concept3d 2014年

无论如何,谢谢您的回答。您是否添加一句话来确认,即使该方法没有给我们欧几里得距离,但在比较中它还是非常准确的?我认为这会给来自搜索引擎的人增加一些帮助。
Grimshaw 2014年

@Grimshaw我编辑了答案以解决原始问题。
concept3d 2014年

@ Byte56感谢您指出。我编辑了答案。
concept3d 2014年

29

如果您需要某种东西在任何距离上都保持线性(不像distance^2),但看起来模糊地是圆形的(不像方形的切比雪夫和类似曼哈顿的菱形距离),则可以对后两种技术求平均值,以获得八边形的距离近似值:

dx = abs(x1 - x0)
dy = abs(y1 - y0)

dist = 0.5 * (dx + dy + max(dx, dy))

感谢Wolfram Alpha,这是该函数的可视化(轮廓图):

等高线图

这是与欧几里得距离(弧度,仅第一象限)相比时其误差函数的图:

误差图

如您所见,误差范围从轴上的0%到叶瓣的大约+ 12%。通过稍微修改系数,我们可以将其降低到+/- 4%:

dist = 0.4 * (dx + dy) + 0.56 * max(dx, dy)

在此处输入图片说明

更新资料

使用上述系数,最大误差将在+/- 4%之内,但平均误差仍将为+ 1.3%。针对零平均误差进行了优化,可以使用:

dist = 0.394 * (dx + dy) + 0.554 * max(dx, dy)

误差在-5%到+ 3%之间,平均误差为+ 0.043%


在网上搜索该算法的名称时,我发现了类似的八边形近似值

dist = 1007/1024 * max(dx, dy) + 441/1024 * min(dx, dy)

请注意,这实际上是等效的(尽管指数有所不同-这些指数提供-1.5%至7.5%的误差,但可以将其按摩到+/- 4%),因为max(dx, dy) + min(dx, dy) == dx + dy。使用此表格,可以将minand max调用排除在外,从而支持:

if (dy > dx)
    swap(dx, dy)

dist = 1007/1024 * dx + 441/1024 * dy

这会比我的版本快吗?谁知道...取决于编译器以及编译器如何针对目标平台进行优化。我的猜测是很难看到任何区别。


3
有趣,以前没看过!它是否有名称,或者只是“切比雪夫和曼哈顿的平均值”?
congusbongus

@congusbongus它可能有一个名字,但我不知道它是什么。如果不是,也许有一天它将被称为“克里斯特距离”(哈...也许不是)
bcrist 2014年

1
请注意,浮点乘法不是很有效。这就是为什么其他近似值使用1007/1024(将被实现为整数乘法后跟位移)的原因。
MSalters 2014年

@MSalters是的,浮点运算通常比整数运算要慢,但这无关紧要-0.4和0.56可以很容易地转换为使用整数运算。此外,在现代x86硬件,最浮点运算(除FDIVFSQRT以及其他超越函数)成本基本上是一样的它们的整数版本:每一指令1或2个周期。
bcrist 2014年

1
这看起来非常类似于Alpha max + Beta Min:en.wikipedia.org/wiki/Alpha_max_plus_beta_min_algorithm
drake7707,2016年

21

有时会出现此问题的原因并非是执行距离计算的成本,而是由于进行计算的次数

在有许多演员的大型游戏世界中,不断检查一个演员与所有其他演员之间的距离是不可扩展的。随着越来越多的玩家,NPC或抛射进入世界,需要告知的比较的数量将增长二次使用。O(N^2)

减少增长的一种方法是使用良好的数据结构来快速从计算中丢弃不需要的参与者。

我们正在寻找一种方法来有效地迭代所有的演员可能在范围之内,同时排除了大多数演员它们是绝对超出范围

如果您的演员相当均匀地分布在世界空间中,那么一个网格桶应该是一个合适的结构(如接受的答案建议)。通过在粗略的网格中保留对actor的引用,您只需检查附近的几个存储桶即可覆盖可能在范围内的所有actor,而忽略其余的。当演员移动时,您可能需要将他从旧桶中移到新桶中。

对于分布较不均匀的演员,四叉树可能对二维世界更好,或者八叉树更适合于三维世界。这些是更通用的结构,可以有效地分隔大空间的空白区域和包含大量参与者的小区域。对于静态参与者,有二进制空间分区(BSP),它的搜索速度非常快,但实时更新成本太高。BSP使用平面将空间分开,以将其重复切成两半,并且可以应用于任何尺寸。

当然,保持演员的这种结构会有开销,特别是当他们在分区之间移动时。但是在一个有许多参与者但兴趣范围很小的大世界中,代价应该远远低于对所有物体进行天真的比较所产生的代价。

考虑算法在接收更多数据时的开销如何增长对于可伸缩软件设计至关重要。有时仅选择正确的数据结构就足够了。成本通常使用Big O表示法进行描述。

(我意识到这不是问题的直接答案,但对某些读者可能有用。如果我浪费您的时间,我深表歉意!)


7
这是最好的答案。距离函数没有什么要优化的;一个人只需要很少使用它。
sam hocevar

3
可接受的答案还包括空间划分,否则您的答案实际上是最佳的。谢谢。
Grimshaw 2014年

我的时间花在阅读您的答案上。谢谢,乔伊。
Patrick M

1
这是最好的答案,也是唯一关注实际问题而不是距离函数性能的红鲱鱼。公认的答案也很可能涵盖空间划分,但这只是一个问题。它专注于距离计算。距离计算不是这里的主要问题;优化距离计算是无法扩展的蛮力解决方案。
Maximus Minimus 2014年

您能否解释一下比较次数为何成指数关系?虽然是二次方的,但在每个时间范围内将每个演员彼此进行比较。
PetrPudlák14年

4

切比雪夫距离怎么样?对于点p,q,定义如下:

距离

因此,对于点(2,4)和(8,5),切比雪夫距离为6,为| 2-8 |。> | 4-5 |。

此外,令E为欧几里得距离,C为切比雪夫距离。然后:

距离2

上限可能没多大用处,因为您必须计算平方根,但是下限可能会有所帮助-每当Chebyshev距离足够大而超出范围时,Euclidean距离也必须太大,以节省您的时间从不必计算它。

当然,要权衡的是,如果切比雪夫距离在范围内,则无论如何都要计算欧几里得距离,这会浪费时间。只有一种方法可以确定这是否将是净赢!


1
您也可以使用曼哈顿距离作为上限。
congusbongus 2014年

1
足够真实。我认为从那里开始,只有跳跃,跳跃和跳跃到bcrist所建议的“切比雪夫和曼哈顿平均水平”。
Tetrinity,2014年

2

一个非常简单的局部优化是先简单地检查一个维度。

那是 :

distance ( x1, y1 , x1, y2) > fabs (x2 - x1)

所以只是检查 fabs (x2 - x1)作为第一个过滤器进行可能会带来可观的收益。多少取决于世界的大小和相关范围。

此外,您可以将其用作空间分区数据结构的替代方法。

如果所有相关对象按x坐标顺序排列在列表中,则附近的对象必须在列表中。即使由于对象移动时未完全维护而导致列表混乱,但在已知的速度范围内,您仍然可以减少要搜索附近对象的列表部分。


2

过去一直在努力优化sqrt。尽管它不再适用于当今的机器,但这是Quake源代码中的一个示例,该示例使用了幻数 0x5f3759df

float Q_rsqrt( float number )
{
  long i;
  float x2, y;
  const float threehalfs = 1.5F;

  x2 = number * 0.5F;
  y  = number;
  i  = * ( long * ) &y;  // evil floating point bit level hacking
  i  = 0x5f3759df - ( i >> 1 ); // what the hell?
  y  = * ( float * ) &i;
  y  = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
  // y  = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration (optional)
  // ...
  return y;
}

一个详细的解释这是怎么回事就可以在维基百科上找到。

简而言之,它是牛顿方法(一种迭代改进估计的数值算法)的几次迭代,其中使用了幻数来提供合理的初始估计。

正如Travis指出的那样,这种优化对现代体系结构不再有用。即使是这样,它也只能为您的瓶颈提供恒定的速率加速,而算法重新设计可能会获得更好的结果。


2
这不再是一个值得的优化。如今,您几乎可以购买的所有消费类PC架构都具有经过硬件优化的sqrt指令,这些指令在一个时钟周期或更短的时间内执行平方根。如果您确实需要最快的sqrt,请使用x86 simd浮点sqrt指令:en.wikipedia.org/wiki/…对于GPU上的着色器之类的调用,调用sqrt将自动产生这样的指令。在CPU上,我假设许多编译器(如果可用)通过SIMD sqrt实现sqrt。
TravisG 2014年

@TravisG是的,值得一提,所以我更新了答案。提供此答案仅出于娱乐和历史兴趣!
joeytwiddle 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.