在开发一款相当简单的类似RTS的游戏时,我注意到我的距离计算正在影响性能。
在任何时候,都会进行距离检查,以了解一个单位是否在其目标范围内,射弹是否已达到其目标,玩家是否跑过了皮卡,常规碰撞等。列表继续进行,并检查两点之间的距离经常使用。
我的问题就是这个。我想知道除了常规的sqrt(x * x + y * y)方法以外,游戏开发人员还有哪些替代方法可以检查距离,如果我们每帧执行数千次,这将非常耗时。
我想指出的是,我了解了曼哈顿的距离和平方的距离比较(通过跳过sqrt瓶颈)。还要别的吗?
在开发一款相当简单的类似RTS的游戏时,我注意到我的距离计算正在影响性能。
在任何时候,都会进行距离检查,以了解一个单位是否在其目标范围内,射弹是否已达到其目标,玩家是否跑过了皮卡,常规碰撞等。列表继续进行,并检查两点之间的距离经常使用。
我的问题就是这个。我想知道除了常规的sqrt(x * x + y * y)方法以外,游戏开发人员还有哪些替代方法可以检查距离,如果我们每帧执行数千次,这将非常耗时。
我想指出的是,我了解了曼哈顿的距离和平方的距离比较(通过跳过sqrt瓶颈)。还要别的吗?
Answers:
TL; DR; 您的问题不在于执行距离功能。您的问题是多次执行距离功能。换句话说,您需要一种算法优化而不是数学优化。
[编辑]我正在删除答案的第一部分,因为人们讨厌它。问题标题是在编辑之前要求替代距离功能。
您正在使用距离函数,每次都在计算平方根。但是,您可以简单地替换它而根本不使用平方根,而是计算平方的距离。这将为您节省很多宝贵的周期。
距离^ 2 = x * x + y * y;
这实际上是一个常见的把戏。但是您需要相应地调整计算。在计算实际距离之前,也可以将其用作初始检查。
因此,例如,代替计算相交测试中两个点/球之间的实际距离,我们可以计算距离平方,然后与半径平方而不是半径进行比较。
编辑,就在@ Byte56指出我没有阅读问题之后,并且您知道平方距离优化了。
在您的情况下,很不幸,我们在计算机图形学中几乎只处理欧几里德空间,并且距离的定义与Sqrt of Vector dot itself欧几里德空间中的一样。
距离平方是您要获得的最佳近似值(就性能而言),我看不到有什么能击败2个乘法,一个加法和一个赋值。
所以你说我不能优化距离函数,我该怎么办?
您的问题不在于执行距离功能。您的问题是多次执行距离功能。换句话说,您需要一种算法优化而不是数学优化。
关键是,而不是检查玩家与场景中每个对象的交集,而是每个帧。您可以轻松利用空间连贯性来发挥自己的优势,只检查播放器附近的物体(最有可能发生碰撞/相交的物体)。
通过将这些空间信息实际存储在空间分区数据结构中,可以轻松完成此操作。对于一个简单的游戏,我建议使用Grid,因为它基本上易于实现并且很好地适合动态场景。
每个单元格/框都包含一个对象的列表,该对象的列表包含在网格的边界框中。而且很容易跟踪玩家在这些单元格中的位置。对于距离计算,您只需要检查同一个或相邻像元内的那些对象而不是场景中所有物体的玩家距离。
一种更复杂的方法是使用BSP或Octrees。
如果您需要某种东西在任何距离上都保持线性(不像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
这会比我的版本快吗?谁知道...取决于编译器以及编译器如何针对目标平台进行优化。我的猜测是很难看到任何区别。
FDIV,FSQRT以及其他超越函数)成本基本上是一样的它们的整数版本:每一指令1或2个周期。
有时会出现此问题的原因并非是执行距离计算的成本,而是由于进行计算的次数。
在有许多演员的大型游戏世界中,不断检查一个演员与所有其他演员之间的距离是不可扩展的。随着越来越多的玩家,NPC或抛射进入世界,需要告知的比较的数量将增长二次使用。O(N^2)
减少增长的一种方法是使用良好的数据结构来快速从计算中丢弃不需要的参与者。
我们正在寻找一种方法来有效地迭代所有的演员可能在范围之内,同时排除了大多数演员它们是绝对超出范围。
如果您的演员相当均匀地分布在世界空间中,那么一个网格桶应该是一个合适的结构(如接受的答案建议)。通过在粗略的网格中保留对actor的引用,您只需检查附近的几个存储桶即可覆盖可能在范围内的所有actor,而忽略其余的。当演员移动时,您可能需要将他从旧桶中移到新桶中。
对于分布较不均匀的演员,四叉树可能对二维世界更好,或者八叉树更适合于三维世界。这些是更通用的结构,可以有效地分隔大空间的空白区域和包含大量参与者的小区域。对于静态参与者,有二进制空间分区(BSP),它的搜索速度非常快,但实时更新成本太高。BSP使用平面将空间分开,以将其重复切成两半,并且可以应用于任何尺寸。
当然,保持演员的这种结构会有开销,特别是当他们在分区之间移动时。但是在一个有许多参与者但兴趣范围很小的大世界中,代价应该远远低于对所有物体进行天真的比较所产生的代价。
考虑算法在接收更多数据时的开销如何增长对于可伸缩软件设计至关重要。有时仅选择正确的数据结构就足够了。成本通常使用Big O表示法进行描述。
(我意识到这不是问题的直接答案,但对某些读者可能有用。如果我浪费您的时间,我深表歉意!)
切比雪夫距离怎么样?对于点p,q,定义如下:

因此,对于点(2,4)和(8,5),切比雪夫距离为6,为| 2-8 |。> | 4-5 |。
此外,令E为欧几里得距离,C为切比雪夫距离。然后:
上限可能没多大用处,因为您必须计算平方根,但是下限可能会有所帮助-每当Chebyshev距离足够大而超出范围时,Euclidean距离也必须太大,以节省您的时间从不必计算它。
当然,要权衡的是,如果切比雪夫距离在范围内,则无论如何都要计算欧几里得距离,这会浪费时间。只有一种方法可以确定这是否将是净赢!
过去一直在努力优化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指出的那样,这种优化对现代体系结构不再有用。即使是这样,它也只能为您的瓶颈提供恒定的速率加速,而算法重新设计可能会获得更好的结果。