优化重力计算


23

我有一堆大小和速度各异的物体,它们相互吸引。每次更新时,我都必须遍历每个对象并合计由于每个其他对象的重力而产生的力。它的伸缩性不是很好,是我在游戏中发现的两个主要瓶颈之一,我不确定该如何提高性能。

感觉就像我应该可以提高性能。在任何给定时间,系统中大约99%的对象对对象的影响都可以忽略不计。我当然不能按质量对物体进行排序,而只能考虑前10个最大的物体或某些物体,因为力随距离的变化比随质量的变化大(方程沿的线force = mass1 * mass2 / distance^2)。我认为,最好的办法是考虑最大的物体最接近的物体,而忽略世界另一端可能无法影响任何事物的数百个微小的岩石碎片,但要找出哪些物体是最近我有遍历所有的对象,他们的立场正在发生变化不断,所以这不是我只能做一次。

目前我正在做这样的事情:

private void UpdateBodies(List<GravitatingObject> bodies, GameTime gameTime)
{
    for (int i = 0; i < bodies.Count; i++)
    {
        bodies[i].Update(i);
    }
}

//...

public virtual void Update(int systemIndex)
{
    for (int i = systemIndex + 1; i < system.MassiveBodies.Count; i++)
    {
        GravitatingObject body = system.MassiveBodies[i];

        Vector2 force = Gravity.ForceUnderGravity(body, this);
        ForceOfGravity += force;
        body.ForceOfGravity += -force;
    }

    Vector2 acceleration = Motion.Acceleration(ForceOfGravity, Mass);
    ForceOfGravity = Vector2.Zero;

    Velocity += Motion.Velocity(acceleration, elapsedTime);
    Position += Motion.Position(Velocity, elapsedTime);
}

(请注意,我删除了很多代码-例如碰撞测试,我没有第二次遍历对象以检测碰撞)。

因此,我并不总是遍历整个列表-我只对第一个对象执行此操作,并且每次对象找到它对另一个对象的感觉力时,另一个对象都感觉到相同的作用力,因此它只会更新两个对象它们-然后在其余的更新中不必再次考虑第一个对象。

Gravity.ForceUnderGravity(...)Motion.Velocity(...)等功能,只需用一点XNA的内置矢量数学。

当两个物体碰撞时,它们会产生无质量的碎片。它被保存在一个单独的列表中,并且作为速度计算的一部分,块状对象不会在碎片上进行迭代,但是每块碎片都必须在块状粒子上进行迭代。

这不必扩展到令人难以置信的极限。这个世界不是无限的,它包含一个边界,该边界可以摧毁穿过它的物体-我希望能够处理大约一千个物体,目前游戏开始cho绕200个左右。

关于如何改善这一点有什么想法吗?我可以使用一些启发式方法将循环长度从几百个减少到几个吗?我执行的某些代码少于每次更新的执行频率?我是否应该对其多线程化直到它足够快以允许一个体面的世界?我应该尝试将速度计算卸载到GPU吗?如果是这样,我将如何设计?我可以在GPU上保留静态共享数据吗?我可以在GPU上创建HLSL函数并任意调用它们(使用XNA),还是必须将它们作为绘制过程的一部分?


1
只需注意一下,您就说过:“作为速度计算的一部分,块状对象不会在碎片上进行迭代,但是每个碎片都必须在块状粒子上进行迭代。” 我从中得到的印象是,您认为它更有效。但是,每次迭代100次碎片对象10次,仍然与迭代10次大型物体100次相同。也许,在大对象循环中迭代每个碎片对象是一个好主意,这样您就不必第二次这样做了。
理查德·马斯克

您需要多精确的模拟?您真的需要一切都朝着彼此加速吗?您真的需要使用真实的引力计算吗?还是您可以偏离这些约定以达成自己的目标?
chaosTechnician 2011年

@Drackir我认为你是对的。它们分离的部分原因是因为无质量对象的数学不同,部分原因是它们最初根本不服从重力,因此不包含它们会更有效。所以这是个遗迹
卡森·迈尔斯,

@chaosTechnician并不一定要非常准确-实际上,如果仅考虑少数最主要的力量,则系统会更稳定,这是理想的。但它正在找出我遇到麻烦的有效方式中最主要的力量。引力计算也已经被近似,它只是G * m1 * m2 / r^2,其中G只是用来调整行为。(尽管我不能随便他们走,因为用户可能会打扰系统)
Carson Myers

如果每块碎片都是无质量的,为什么每个碎片都必须遍历它们呢?碰撞?
sam hocevar 2011年

Answers:


26

这听起来像是网格的工作。将游戏空间划分为一个网格,并为每个网格单元保留当前其中的对象的列表。当对象在单元格边界上移动时,更新它们所在的列表。在更新对象并搜索其他对象进行交互时,您可以仅查看当前的网格单元格和一些相邻的网格单元格。您可以调整网格的大小以获得最佳性能(通过平衡搜索网格对象的成本来增加网格单元的更新成本(如果网格单元太小则成本会增加)。大)。

当然,这将导致比几个网格单元更远的物体根本不相互作用,这可能是一个问题,因为应聚集大量的质量(一个大物体或许多小物体簇)正如您提到的那样,影响范围更大。

您可以做的一件事是跟踪每个网格单元中的总质量,并将整个单元视为一个对象,以进行更远的交互。也就是说:当您计算物体上的力时,请计算附近几个网格单元中对象的直接对象间加速度,然后为每个更远的网格单元添加单元间加速度(或也许只是其中的质量不可忽略的那些)。所谓的细胞间加速度,是指使用两个细胞的总质量及其中心之间的距离计算出的向量。这应该可以合理地估算出该网格单元中所有对象的总重力,但要便宜得多。

如果游戏世界很大,您甚至可以使用分层网格(例如四叉树(2D)或八叉树(3D))并应用类似的原理。更长距离的交互将对应于层次结构的更高级别。


1
+1为网格创意。我建议也跟踪网格的质心,以保持计算更加纯净(如有必要)。
chaosTechnician 2011年

我非常喜欢这个主意。我曾考虑过将对象包含在单元格中,但是在考虑两个附近的对象时,放弃了这些对象,这些对象在技术上位于不同的单元格中,但是我并没有费心去考虑几个相邻的单元格,也没有考虑其他对象的总质量细胞。我认为,如果我做对的话,这应该会很好地工作。
卡森·迈尔斯,

8
本质上,这是Barnes-Hut算法:en.wikipedia.org/wiki/Barnes –Hut_simulation
Russell Borogove 2011年

这甚至听起来与重力在物理学中的工作原理类似-时空的弯曲。
Zan Lynx

我喜欢这个主意-但老实说,它需要进一步完善-如果两个对象彼此非常靠近但在单独的单元格中会发生什么?我可以看到您的第三段会有所帮助-但是为什么不对交互对象做一个圆形剔除呢?
乔纳森·迪金森

16

Barnes-Hut算法是解决这一问题的方法。在超级计算机仿真中已使用它来解决您的确切问题。编写代码并不难,而且非常有效。实际上,不久前我写了一个Java applet来解决这个问题。

访问http://mathandcode.com/programs/javagrav/,然后按“开始”和“显示四叉树”。

在“选项”选项卡中,您可以看到粒子数可以一直达到200,000。在我的计算机上,计算大约需要2秒钟才能完成(200,000个点的绘制大约需要1秒钟,但是计算在单独的线程上运行)。

这是我的applet的工作方式:

  1. 创建具有随机质量,位置和起始速度的随机粒子列表。
  2. 从这些粒子构造一个四叉树。每个四叉树节点包含每个子节点的质心。基本上,对于每个节点,您都有三个值:massx,massy和mass。每次将粒子添加到给定的节点时,分别通过mass.x * particle.mass和particle.y * particle.mass增加massx和massy。位置(质量/质量,质量/质量)将最终成为节点的质心。
  3. 对于每个粒子,计算力(在此完整描述)。这是通过从顶部节点开始并遍历四叉树的每个子节点来完成的,直到给定的子节点足够小为止。停止递归后,可以计算粒子到节点的质心的距离,然后可以使用节点的质量和粒子的质量来计算力。

您的游戏应该能够轻松处理上千个相互吸引的对象。如果每个对象都是“哑巴”(例如我的applet中的准粒子),则应该能够获得8000到9000个粒子,也许更多。这是假设单线程的。使用多线程或并行计算应用程序,您可以获得比实时更新更多的粒子。

另请参见:http : //www.youtube.com/watch?v= XAlzniN6L94,以获取此内容的大图


第一个链接已死。小程序是否托管在其他地方?
Anko

1
固定!抱歉,忘记支付该域名的租金,有人自动购买了该信息:\另外,对于1.3年的8D帖子,响应时间是3分钟

我应该补充:我没有源代码。如果您正在寻找一些源代码,请查看part-nd(用c编写)。我敢肯定那里也有其他人。

3

内森·里德(Nathan Reed)有一个很好的答案。它的简短版本是使用适合您的仿真拓扑的广相技术,并且仅对将对彼此产生显着影响的对象对运行重力计算。实际上,这与您对常规碰撞检测广泛阶段所做的操作没有什么不同。

尽管如此,另一种可能性是仅间歇地更新对象。基本上,每个时间步(帧)仅更新所有对象的一部分,而其他对象的速度(或加速度,取决于您的偏好)保持相同。只要间隔不太长,用户就不太可能注意到更新的任何延迟。这将使您线性提高算法速度,因此也请务必参考Nathan建议的广相技术,如果您有大量对象,则可以大大提高速度。尽管一点儿都没有建模,但这有点像“重力波”。:)

同样,您可以在一次通过中生成一个重力场,然后在第二次通过中更新对象。首先,您基本上是利用每个对象的重力影响填充网格(或更复杂的空间数据结构)。现在的结果是一个重力场,您甚至可以渲染(看起来很酷)以查看在任何给定位置将对对象施加的加速度。然后,您遍历对象,然后将重力场的效果应用于该对象。更酷的是,您可以在GPU上执行此操作,方法是将对象作为圆/球形渲染到纹理上,然后读取纹理(或在GPU上使用另一个变换反馈通道)以修改对象的速度。


将过程分成多个阶段是一个好主意,因为(据我所知)更新间隔很小的一秒。引力场的纹理很棒,但现在也许有点超出我的能力范围。
卡森·迈尔斯,

1
不要忘记将作用力乘以自上次更新以来跳过的时间片数。
Zan Lynx

@seanmiddlemitch:您能再详细说明一下重力场纹理吗?抱歉,我不是图形程序员,但这听起来真的很有趣。我只是不明白它应该如何工作。和/或也许您有指向该技术描述的链接?
Felix Dombek

@FelixDombek:将对象渲染为表示影响区域的圆圈。片段着色器写入一个指向对象中心并具有适当大小(基于距中心和对象质量的距离)的向量。硬件混合可以以加法方式处理这些向量的求和。结果将不是精确的重力场,但几乎可以肯定足以满足游戏的需求。作为使用GPU的另一种方法,请参见这种基于CUDA的N体重力模拟技术:http.developer.nvidia.com/GPUGems3/gpugems3_ch31.html
Sean Middleditch


0

只是一小部分(可能是幼稚的)输入。我不做游戏编程,但是我的感觉是,您的基本瓶颈是重力计算。无需遍历每个对象X并从每个对象Y中找到引力效果并将其相加,您可以采用每对X,Y并找到它们之间的力。那应该减少O(n ^ 2)的重力计算次数。然后,您将进行很多加法运算(O(n ^ 2)),但这通常较便宜。

同样,在这一点上,您可以执行一些规则,例如“如果由于这些物体太小,引力将小于\ epsilon,则将力设置为零”。将该结构用于其他目的(包括碰撞检测)也可能是有利的。


从根本上讲,这就是我在做什么。在获得所有涉及X的对之后,我不再遍历X。我找到了X和Y,X和Z等之间的力,并将该力应用于该对中的两个对象。循环完成后,ForceOfGravity向量是所有力的总和,然后将其转换为速度和新位置。我不确定重力计算是否特别昂贵,并且首先检查它是否超过阈值并不会节省大量时间,我认为
Carson Myers

0

在扩展seanmiddleditch的答案时,我认为我可能会对重力场的想法有所启发(讽刺?)。

首先,不要将其视为纹理,而是可以修改的离散值字段(实际上是二维数组);模拟的后续准确性可能是该字段的分辨率。

将物体引入田野时,可以计算所有周围值的引力。从而在野外产生重力

但是,在变得越来越多或像以前一样无效之前,您应该计算出多少点呢?可能不是很多,甚至32x32都是要为每个对象进行迭代的重要字段。因此,将整个过程分成多个阶段;每个都有不同的分辨率(或精度)。

即,第一遍可以计算以4x4网格表示的物体重力,每个像元值表示空间中的2D坐标。给出O(n * 4 * 4)小计的总复杂度。

第二遍可能具有64x64分辨率的重力场,因此更准确,每个像元值代表空间中的2D坐标。但是,由于复杂度很高,因此可以限制受影响的周围单元的半径(也许仅更新周围的5x5单元)。

附加的第三遍可用于高精度计算,分辨率可能为1024x1024。请记住,您实际上从来没有执行过1024x1024的单独计算,而是仅对该字段的某些部分进行操作(可能是6x6的小节)。

这样,您的更新总体复杂度为O(n *(4 * 4 + 5 * 5 + 6 * 6))。

然后要计算每个对象的速度变化,对于每个重力场(4x4、64x64、1024x1024),只需将点质量位置映射到网格单元,然后将该网格单元的整体重力势矢量应用于新矢量;对每个“层”或“通过”重复;然后将它们添加在一起。这将为您提供一个良好的合成重力矢量。

因此,总体复杂度为:O(n *(4 * 4 + 5 * 5 + 6 * 6)+ n)。真正重要的(复杂性)是计算通道中的重力势时要更新的周围单元数,而不是重力场的整体分辨率。

低分辨率场(第一遍)的原因显然是要涵盖整个宇宙,并确保尽管有距离,但外围物体仍会吸引到更密集的区域。然后使用更高分辨率的场作为单独的图层,以提高相邻行星的精度。

我希望这是有道理的。


0

如何使用另一种方法:

根据物体的质量为其分配影响区域-它们太小而无法在该范围之外产生可测量的效果。

现在,将您的世界划分为一个网格,并将每个对象放入对其有影响的所有单元的列表中。

仅对附加到对象所在单元格的列表中的对象执行重力计算。

仅当对象移到新的网格单元中时才需要更新列表。

网格单元越小,每次更新将进行的计算就越少,但更新列表将进行的工作就越多。

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.