【问题标题】:optimizing a grid-based particle system优化基于网格的粒子系统
【发布时间】:2017-08-30 02:07:48
【问题描述】:

我在 Java 中实现了一个有点类似于 this one 的游戏,目前发现我的粒子数量达到了大约 80k 的上限。我的游戏板是对“粒子”对象的引用的二维数组,每个对象都必须每帧更新。不同种类的“粒子”具有不同的行为,可能会根据风或相邻粒子等环境条件移动或改变其状态。

一些可能生效的“规则”:

  • 如果熔岩类型的粒子与水类型的粒子相邻,则两者都会消失,并且熔岩会被黑曜石取代
  • 如果气体粒子与熔岩、火、余烬等粒子相邻,则会点燃,并产生火焰和烟雾
  • 如果足够数量的尘埃颗粒相互堆叠,那些处于较低水平的尘埃颗粒,就像在压力下一样,可能会变成沉积岩

我四处搜索,但找不到任何似乎特别适合加快任务速度的算法或数据结构。似乎某种记忆可能有用?四叉树在这里有用吗?我已经看到它们在与Hashlife 算法有些相似的康威生命游戏中使用。或者,是不是我不能做太多的事情来提高速度?

【问题讨论】:

  • 这听起来像是 GPU 非常适合解决的问题。我对 GPU 编程知之甚少,但mikeinnes.github.io/2017/08/24/cudanative.html 建议它可能比你想象的更容易进入。
  • Hashlife 依赖于计算的局部性,而您几乎没有告诉我们您的规则。
  • @maaartinus 我添加了一些关于规则的信息
  • @paleto-fuera-de-madrid 我猜,hashlife 与前两个规则兼容(仅限本地交互),但与最后一个规则不兼容。我也对使用 memoization 持怀疑态度,因为可能性要大得多。如果您可以在CR 上发布整个代码,您可以在那里获得相当多的帮助(如果您愿意,请给我留言)。即使是微小的改进也可以为您带来不错的速度因素。
  • @maaartinus 好的。我会去做的。我该如何“给你留个便条”?

标签: java algorithm optimization data-structures cellular-automata


【解决方案1】:

Hashlife 原则上会起作用,但有两个原因可能会导致您无法像 Conway Life 那样从中获益。

首先,它依赖于重复出现的模式。您拥有的单元格状态越多,平面结构越少,您遇到的缓存命中就越少,您使用蛮力的工作就越多。

其次,正如另一张海报指出的那样,涉及非局部效果的规则要么意味着您的基元(在 Conway Life 4x4 中)需要更大,因此您将放弃分而治之,例如 8x8 或 16x16 或任何尺寸保证您可以在 n/2 时间内正确计算中间部分。 州的多样性使情况变得更糟。在 Conway Life 中,预先计算所有 4x4 网格或至少将几乎所有相关的网格都放在缓存中是很常见的。 2 个州只有 65536 个 4x4 网格(现代平台上的花生),但只有 3 个州有 43046721。 如果您必须拥有 8x8 基元,它会很快变得非常大并且超出任何实际存储。

因此,基元越大,您拥有的状态就越多,这很快就会变得不切实际。

解决原始大小的一种方法是让岩石规则传播压力。因此,如果一个 Rock+n(n 代表压力)在其上具有 Rock+m,其中 m>=n,则它在下一代中变为 Rock+(n+1)。达到某个阈值 k 时,它会变成沉积岩。

这意味着细胞仍然只依赖于它们的直接邻居,但再次增加了状态的数量。

如果您在给出的示例中具有像“鸟”这样的细胞类型,并且您的速度没有保持在最低限度(比如任一方向的 -1,0,1),您将完全崩溃记忆。即便如此,此类规则的混乱性质也可能使这些区域的缓存命中率变得微乎其微。

如果您的规则不能像 Conway Life 那样导致稳定状态(或重复循环),那么记忆的回报将是有限的,除非您的飞机大部分是空的。

【讨论】:

  • 由于行为差异很大,Hashlife 在这种情况下似乎没有多大用处,你能想到我可能研究的其他相关算法吗?或者你认为我最好的办法是想出聪明的方法来减少每粒子的工作?
  • @paleto-fuera-de-madrid 你仍然可以通过某种空间树获得一些东西。显而易见的优化是忽略死空间,而 Hashlife 非常擅长 - 忽略记忆。您需要一个易于遍历非死细胞并获得它们的近邻进行交互的数据结构。另一种方法是使用几个哈希图 (x,y)->state。迭代“旧”地图并填写“新”地图,以识别您可以轻松访问邻居。或者某种带有跳过死区的链接的稀疏矩阵。
  • 我之前尝试过哈希图,发现它们对于我正在处理的粒子数量来说太慢了。我还尝试使用一些整数来跟踪最大和最小占用坐标,以最大限度地减少对数组的迭代,但是当我测试它时,这似乎也很慢。一个普通的旧数组可能是最快的数据结构是否可行?
【解决方案2】:

我不清楚您的问题,但我认为 cuda 或 OpenGL(GPU 编程)可以轻松处理您的参考链接:https://dan-ball.jp/en/javagame/dust/

【讨论】:

  • 还有哪些信息可以让您更清楚我的问题?另外,你能提供更多的解释吗?
【解决方案3】:

我会为此使用固定的 NxN 网格,主要是因为有太多的点在每一帧周围移动,无法从四叉树的递归细分特性中受益。在这种情况下,一个简单的数据结构以及适当调整的数据表示和内存布局可以让世界变得与众不同。

我在这里为 Java 做的主要事情实际上是避免将每个粒子建模为一个对象。它应该是原始数据,就像一些普通的旧数据,如浮点数或整数。您希望能够通过顺序处理处理空间局部性的连续性保证,而不需要支付填充成本和每个类实例的 8 字节开销。将冷场与热场分开。

例如,您不一定需要知道粒子的颜色来移动它并应用物理。因此,您不希望此处的 AoS 表示必须在顺序物理传递期间将粒子的颜色加载到缓存行中,只是为了驱逐它而不使用它。将尽可能多的相关内存一起使用到缓存行中,方法是将其与特定通道的不相关内存分开。

网格中的每个单元格应该只将一个索引存储到一个粒子中,每个粒子存储一个指向单元格中下一个粒子的索引(单链表,但是一个不需要分配节点并且只使用索引的侵入式列表成数组)。 -1 可用于指示列表的结尾以及空单元格。

要查找感兴趣的粒子之间的碰撞,请查看与您正在测试的粒子相同的单元格,您可以并行执行此操作,其中每个线程处理一个或多个单元格的粒子。

考虑到每帧可以拥有的大量移动粒子,NxN 网格应该非常精细。使用您创建的单元格数量来找到最适合您的输入大小的单元格。你甚至可能有多个网格。如果某些粒子不相互作用,不要将它们放在同一个网格中。这里不用担心网格的内存使用情况。如果每个网格单元仅存储单元中第一个粒子的 32 位索引,则 200x200 网格只需 160 KB,每个粒子的 32 位 next 索引开销。

几年前,我在 C 语言中使用上述技术(但没有演示游戏那么多有趣的粒子交互)制作了类似的东西,它可以处理大约 1000 万个粒子,然后它开始低于 30 FPS 或更早只有 2 个内核的硬件。它确实使用了 C 以及 SIMD 和多线程,但我认为如果您执行上述操作,您可以在 Java 中获得一个非常快速的解决方案,一次处理大量粒子。

数据结构:

当粒子从一个单元格移动到另一个单元格时,您只需操纵几个整数将它们从一个单元格移动到另一个单元格。单元不“拥有内存”或分配任何内存。它们只是 32 位索引。

要确定粒子占据哪个单元格,只需执行以下操作:

cell_x = (int)(particle_x[particle_index] / cell_size)
cell_y = (int)(particle_y[particle_index] / cell_size)
cell_index = cell_y * num_cols + cell_x

... 比遍历树结构并在粒子四处移动时必须重新平衡它要便宜得多。

【讨论】:

  • 您能详细说明一下碰撞检查机制吗?我不明白看着一个细胞如何告诉你一个粒子的所有邻居。似乎某些粒子的邻居可能在相邻的单元格中。
  • 如果所有粒子具有相同的大小,那么您可以通过检查粒子重叠的单元格(与粒子的圆或 AABB 相交的所有单元格,例如。)。如果粒子有不同的大小,那就有点棘手了。您可以执行以下两种操作之一:1) 将与多个单元格重叠的粒子插入到每个单元格中。 2) 使细胞松散并膨胀/收缩它们的AABB以适应内部的颗粒。我在这里对第二种方法写了相当长的文章:stackoverflow.com/a/48384354/4842163
  • 基本上,如果所有粒子都有统一的大小,那么您可以将它们视为插入点,并将每个粒子插入到一个单元格中。但是,对于碰撞检测,您可以查询 area(例如圆形或 AABB)以确定哪些粒子可能与给定粒子发生碰撞。如果粒子没有统一的大小,那么您要么将粒子插入到它重叠的所有单元格中,要么插入到单个单元格中,但其 AABB 可以增长/收缩。然后你在查询一个区域时做同样的事情并检查粒子重叠的所有单元格。
  • 存储在列表中的粒子对象的大小是否对这种方法的性能有显着影响?另外,您有我们可以查看的参考实现吗?
  • 不是绝对意义上的,而是相对于数据结构的单元格大小而言。例如,如果您使用很小的细胞大小,它们只是粒子大小的一小部分,并且粒子的大小变化很大,那么大粒子最终会插入到许多细胞中,区域搜索最终需要检查大量细胞,并且性能会受到影响。然而,这在一定程度上也适用于四叉树(和空间散列),但松散变体除外。一般来说,空间索引往往需要对所存储的内容进行某种程度的调整。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-09
  • 2011-12-04
  • 2018-05-26
  • 2012-06-28
相关资源
最近更新 更多