如何执行确定性的物理模拟?


43

我正在创建一个涉及刚性物体的物理游戏,玩家可以在其中移动零件和零件来解决难题/地图。游戏中一个非常重要的方面是,当玩家开始进行仿真时,无论其操作系统,处理器等如何,它在任何地方都可以运行。

存在很多复杂性的空间,并且模拟可能会运行很长时间,因此,对于浮点运算,物理引擎必须完全确定,这一点很重要,否则解决方案似乎可以在一个玩家的机器上“解决”并在另一个上“失败”。

如何在游戏中获得这种确定性?我愿意使用各种框架和语言,包括Javascript,C ++,Java,Python和C#。

Box2D(C ++)以及其他语言的等效语言吸引了我,因为它似乎可以满足我的需求,但是它缺乏浮点确定性,尤其是对于三角函数。

到目前为止,我所看到的最好的选择是Box2D的Java等效项(JBox2D)。它似乎通过使用StrictMath而不是Math用于许多操作来尝试浮点确定性,但是由于我尚未构建游戏,因此尚不清楚此引擎是否可以保证我需要的一切。

是否可以使用或修改现有引擎以满足我的需求?还是我需要自行构建引擎?

编辑:如果您不关心为什么有人需要这样的精度,请跳过本文的其余部分。发表评论和回答的人似乎都认为我正在寻找不该做的事情,因此,我将进一步解释游戏的工作原理。

给玩家一个难题或关卡,其中包含障碍物和目标。最初,模拟未运行。然后,他们可以使用提供给他们的零件或工具来构建机器。一旦按下开始,模拟就会开始,并且他们不再可以编辑其机器。如果机器解决了地图问题,则玩家已经击败了关卡。否则,他们将必须按Stop(停止)按钮,更换机器,然后重试。

一切都需要确定性的原因是因为我计划通过记录每台机器的位置,大小和旋转,来生成将每台机器(一组试图解决一个关卡的零件和工具)映射到xml或json文件的代码。这样,玩家就可以共享解决方案(由这些文件表示),以便他们可以验证解决方案,相互学习,举行比赛,合作等等。当然,大多数解决方案,尤其是简单或快速的解决方案不会因为缺乏确定性而受到影响。但是解决真正困难水平的缓慢或复杂的设计可能会出现,而那些可能会是最有趣且值得分享的设计。


评论不作进一步讨论;此对话已转移至聊天。如果您认为您有解决问题的方法,请考虑将其发布为答案而不是评论-这样可以更轻松地进行编辑,通过接受的投票/标记进行评估,或者直接对建议本身进行评论以获取反馈而不是编织回复到更长的会话线程中。
DMGregory

Answers:


45

以确定性方式处理浮点数

浮点是确定性的。好吧,应该如此。这很复杂。

关于浮点数的文献很多:

以及它们的问题所在:

对于抽象。至少在单个线程上,以相同顺序发生的具有相同数据的相同操作应该是确定性的。因此,我们可以从担心输入和重新排序开始。


导致问题的此类输入之一是时间。

首先,您应该始终计算相同的时间步长。我并不是说不测量时间,而是要您将时间浪费在物理模拟中,因为时间的变化是模拟中的噪声源。

如果不将时间传递给物理模拟,为什么还要测量时间呢?您想测量经过的时间,以知道何时应调用模拟步骤,以及-假设您正在使用睡眠-睡眠了多少时间。

从而:

  • 测量时间:是
  • 模拟中的使用时间:否

现在,指令重新排序。

编译器可以确定f * a + b与相同b + f * a,但是结果可能不同。它也可以编译为fmadd,或者可以决定采取多行一起发生的情况,并使用SIMD或其他我现在想不到的优化来编写它们。记住,我们希望相同的操作以相同的顺序发生,这是因为我们想要控制发生的操作。

不,使用double不会保存您。

您需要担心编译器及其配置,尤其是要在网络上同步浮点数。您需要使构建版本同意做相同的事情。

可以说,编写汇编将是理想的。这样,您可以决定要执行的操作。但是,这可能是支持多个平台的问题。

从而:


定点数字的情况

由于浮点数在内存中的表示方式,较大的值将失去精度。原因是保持较小的值(钳位)可以缓解问题。因此,没有巨大的速度,也没有很大的房间。这也意味着您可以使用离散物理,因为隧道风险较小。

另一方面,小错误会累积。所以,截断。我的意思是,缩放并转换为整数类型。这样一来,您什么都不会建立。您可以使用整数类型进行一些操作。当需要返回浮点时,可以投射并撤消缩放。

注意我说的是规模。这个想法是1个单位实际上将表示为2的幂(例如16384)。不管它是什么,请使其恒定并使用。基本上,您将其用作定点数。实际上,如果您可以使用一些可靠库中的正确定点数会更好。

我是说截断。关于舍入问题,这意味着您不能信任转换后获得的任何值的最后一点。因此,在投射比例超出所需数量之前,然后将其截断。

从而:

  • 保持较小的价值:是
  • 仔细取整:是
  • 定点数(如果可能):是

等一下,为什么需要浮点数?您不能仅使用整数类型吗?啊对。三角和根。您可以计算三角和辐射的表格,并将其烘焙到源中。或者,您可以使用浮点数来实现用于计算它们的算法,但改用定点数除外。是的,您需要平衡内存,性能和精度。但是,您可以远离浮点数,并保持确定性。

您知道他们为原始PlayStation做过类似的事情吗?请见我的狗,补丁

顺便说一句,我不是说不要对图形使用浮点数。仅用于物理。我的意思是,当然,这些职位将取决于物理学。但是,如您所知,对撞机不必匹配模型。我们不想看到模型被截断的结果。

因此:使用固定点号。


要明确的是,如果您可以使用编译器来指定浮点的工作方式,而这足以满足您的要求,那么您可以这样做。这并不总是一种选择。此外,我们这样做是为了确定性。定点数字并不意味着没有错误,毕竟它们的精度有限。

我不认为“定点数很难”是不使用它们的充分理由。而且,如果您有充分的理由使用它们,那就是确定性,尤其是跨平台的确定性。


也可以看看:


附录:我建议保持世界的规模很小。话虽如此,两个OP ans和Jibb Smart都提出了偏离原点浮动的精度较低的观点。这将对物理学产生影响,物理学的影响要早于世界边缘。定点数具有固定的精度,它们在任何地方都将同样好(或坏,如果您愿意)。如果我们要确定性,那很好。我还想提到,我们通常做物理学的方式具有放大微小变化的特性。请参阅不可思议的机器和捆绑器中的蝴蝶效应-确定性物理学


做物理学的另一种方法

我一直在思考,浮点数精度的小误差放大的原因是因为我们正在对这些数字进行迭代。在每个模拟步骤中,我们都取最后一个模拟步骤的结果,并对它们进行处理。在错误之上累积错误。那就是你的蝴蝶效应。

我认为我们不会在同一台机器上看到使用单个线程的单个构建通过相同的输入产生不同的输出。但是,可以在另一台计算机上,也可以在其他版本上。

有一个论点在那里进行测试。如果我们确切地决定事情的工作方式,并且可以在目标硬件上进行测试,则不应发布行为不同的构建。


但是,还有一个论点就是不进行大量修改会累积大量错误。也许这是一个以不同方式进行物理学的机会。

如您所知,存在连续的和离散的物理学,两者都取决于每个对象在时间步长上前进多少。但是,连续物理学具有解决碰撞瞬间的方法,而不是探测不同的可能瞬间以查看是否发生了碰撞。

因此,我提出以下建议:使用连续物理学的技术找出每个对象的下一次碰撞何时发生,而且步长很大,比单个模拟步骤要大得多。然后,您获取最近的碰撞瞬间,并找出该瞬间的所有情况。

是的,单个模拟步骤需要做很多工作。这意味着模拟不会立即开始...

...但是,您可以模拟接下来的几个模拟步骤,而无需每次都检查碰撞,因为您已经知道下一次碰撞的发生时间(或者在较长的时间步中没有碰撞发生)。此外,该模拟中累积的误差是无关紧要的,因为一旦模拟达到较大的时间步长,我们就只需放置预先计算出的位置即可。

现在,我们可以在每个模拟步骤中使用检查碰撞的时间预算来计算发现的下一个碰撞。也就是说,我们可以使用较大的时间步长进行提前模拟。假设世界范围有限(这不适用于大型游戏),那么应该有一排将来的状态用于模拟,然后将每个帧从最后一个状态插值到下一个状态。


我会建议插值。但是,考虑到存在加速度,我们不能简单地以相同的方式进行插值。相反,我们需要考虑每个对象的加速度进行插值。为此,我们可以像在较大时间步长上那样以相同的方式更新位置(这也意味着它不太容易出错,因为我们不会对同一移动使用两种不同的实现方式)。


注意:如果我们正在执行浮点数计算,则此方法无法解决对象离原点距离越远的行为问题。但是,虽然精度确实会随着您离原点的距离的增加而丢失,但这仍然是确定性的。实际上,这就是为什么原本没有提出来的原因。


附录

从OP中进行评论

这个想法是,玩家将能够以某种格式(例如xml或json)保存他们的机器,以便记录每个棋子的位置和旋转。然后,该xml或json文件将用于在另一台播放器的计算机上重现该机器。

所以,没有二进制格式,对吗?这意味着我们还需要担心恢复的浮点数与原始数是否匹配。请参阅:浮点精度再访:九位数浮点可移植性


评论不作进一步讨论;此对话已转移至聊天
Vaillancourt

2
好答案!支持定点的还有2个点:1.浮点的行为与原点的距离更近或更远(如果您在不同的地方有相同的谜题),但不算定点;2.固定点实际上有更多比浮点其大部分范围的精度-您可以通过使用定点阱增益精度
Jibb智能

可以使用base64元素在XML和JSON中对二进制数据进行编码。这不是表示大量此类数据的有效方法,但是暗示它们阻止使用二进制表示形式是不正确的。
皮卡列克

1
@Pikalek我知道,OP在评论中问我有关问题,我提到base64是一个选项,其中包括十六进制,将演员重新解释为int以及使用protobuf格式,因为无论如何也不会理解这些文件,所以它们不是(未经培训的) )人类可读。然后-我认为-一个mod删除了评论(不,它不在上面链接的聊天中)。那会再次发生吗?我应该从答案中删除吗?我应该延长吗?
Theraot

@Theraot啊,我可以看到自删除评论以来,我可能会有不同的理解。(FWIW,我确实阅读了有关此答案和问题的聊天记录)。即使有一种本机的,有​​效的数据编码方式,仍然有更大的问题要确保跨平台的含义相同。考虑到客户流失,也许最好保持原样。感谢您的澄清!
皮卡列克

6

我在一家生产某些知名的实时战略游戏的公司工作,我可以告诉你浮点确定性是可能的。

使用不同的编译器,或者使用具有不同设置的同一编译器,甚至使用同一编译器的不同版本,都可能破坏确定性。

如果您需要平台或游戏版本之间的交叉游戏,那么我认为您需要固定点-我知道的唯一具有浮点的交叉游戏是在PC和XBox1之间,但这非常疯狂。

您将需要找到一个完全确定性的物理引擎,或者采用开源引擎使其具有确定性,或者滚动您自己的引擎。我不由自主地感觉到万物的统一性增加了确定性的物理引擎,但是我不确定它是在同一台机器上确定性还是在所有机器上确定性。

如果您打算尝试自己制作东西,则可以采取一些措施:

  • 如果您没有做过任何富有成果的工作,则IEE754浮点数是确定性的(有关“覆盖范围”或“未覆盖”的更多信息,请使用Google“ IEE754确定性”
  • 您需要确保每个客户端的舍入模式和精度都设置相同(使用controlfp进行设置)
  • 舍入模式和精度可以通过某些数学库进行更改,因此,如果您使用任何关闭的库,则可能需要在调用后检查它们(再次使用controlfp进行检查)
  • 一些SIMD指令是确定性的,很多不是,请小心
  • 如上所述,要确保确定性,您还需要相同的平台,相同的编译器的相同确切版本,相同的配置,相同的编译器设置
  • 内置一些工具来检测状态不同步,并帮助诊断它们-例如,每帧CRC对游戏状态进行CRC检测,以检测何时发生不同步,然后使用详细的日志记录模式,您可以启用将对游戏状态所做的修改手动记录到文件中的功能,然后从彼此不同步的模拟中提取2个文件,并在差异工具中进行比较以查看错误之处
  • 在游戏状态下初始化所有变量,这是不同步的主要来源
  • 整个游戏模拟每次都需要以完全相同的顺序进行,以避免出现失步,这很容易出错,建议以最小化这种方式构造游戏模拟。我确实不是软件设计模式的人,但是在这种情况下,这可能是个好主意-您可能会考虑某种模式,其中游戏模拟就像一个安全盒,并且改变游戏状态的唯一方法是插入“消息”或“命令”,仅const访问权限提供给游戏状态以外的任何内容(例如,渲染,联网等)。因此,将多人游戏的模拟联网是通过网络发送这些命令的情况,或者重放相同的模拟是第一次记录命令流的情况,

1
Unity确实已经通过其面向数据的技术堆栈的新Unity Physics系统朝着跨平台确定性的目标努力,但是据我所知,它仍在进行中,尚未完成或可以立即使用。
DMGregory

非确定性SIMD指令的示例是什么?你在想大概的像rsqrtps
Ruslan

@DMGregory则必须在预览中,因为您已经可以使用它-但正如您所说的,它可能尚未完成。

@Ruslan是rsqrtps / rcpps结果取决于实现情况
Joe

5

我不确定这是否是您要寻找的答案类型,但是另一种方法可能是在中央服务器上运行计算。让客户端将配置发送到您的服务器,让其执行模拟(或检索缓存的),然后将结果发送回,然后由客户端解释并处理为图形。

当然,这会关闭您可能需要在脱机模式下运行客户端的所有计划,并且取决于模拟的计算强度,您可能需要功能强大的服务器。一个或多个,但是至少您可以选择确保它们具有相同的硬件和软件配置。实时模拟可能很困难,但并非不可能(想想实时视频流-它们可以工作,但会稍有延迟)。


1
我完全同意。这是您保证与所有用户共享体验的方式。 gamedev.stackexchange.com/questions/6645/…讨论了类似的话题,比较了客户端和服务器端物理之间的区别。
Tim Holt

1

我将提出一个违反直觉的建议,即尽管不是100%可靠,但在大多数情况下应该可以正常工作并且非常容易实现。

降低精度。

使用预定的恒定时间步长,以标准双精度浮点数在每个时间步上执行物理操作,然后在每个步骤之后将所有变量的分辨率量化为单精度(甚至更差)。然后,浮点重新排序可能引起的大多数可能偏差(与同一程序的参考运行相比)将被消除,因为这些偏差发生在数字中,而这些数字甚至都不以降低的精度存在。因此,这种偏差不会使Lyapunov积累(蝴蝶效应)的机会最终变得明显。

当然,模拟的准确性将比实际准确性稍差(与真实物理学相比),但是只要您所有程序的运行方式都不相同,这并不是真正值得注意的。

现在,从技术上来讲,重新排序当然可能会导致偏差达到较高的有效位数,但是,如果偏差实际上仅是由浮动引起的,并且您的值表示连续的物理量,则这种可能性就很小。请注意,double任何两个值之间都有十亿个值single,因此大多数模拟中的大多数时间步长在模拟运行之间可以期望完全相同。偏差确实通过量化得以实现的少数情况将有望出现在运行时间不长的仿真中(至少不会出现混沌动力学)。


我还建议您考虑与您所询问的问题完全相反的方法:拥抱不确定性!如果行为是不确定的,则实际上更接近实际的物理实验。那么,为什么不故意将每次模拟运行的起始参数随机化,并要求模拟在多个试验中始终如一地成功呢?这将教会更多有关物理学的知识,以及如何使机器设计得足够健壮/线性,而不是仅在模拟中才是现实的超脆弱机器。


四舍五入将无济于事,因为如果高精度结果不确定,那么最终结果将是在一个系统上舍入一种方法,而在另一系统上舍入另一种方法。例如,您始终可以四舍五入为最接近的整数,但是一台系统计算机1.0和另一台系统计算机计算0.9999999999999999999999999999999999999999,它们的舍入方式不同。
yoyo

是的,这是可能的,正如我在回答中已经说过的那样。但这很少会发生,就像游戏物理中的其他故障一样。因此,舍入确实有帮助。(不过,我不会四舍五入;为了避免偏差
四舍五入

0

创建自己的用于存储数字的类!

如果您确切知道如何执行计算,则可以强制执行确定性行为。例如,如果您处理的唯一运算是乘法,除法,加法和减法,那么将所有数字表示为有理数就足够了。为此,一个简单的Rational类就可以了。

但是,如果您想处理更复杂的计算(例如三角函数),则必须自己编写此类函数。如果希望能够采用数字的正弦值,则必须能够编写仅使用上述操作的近似于数字正弦的函数。这都是可行的,并且我认为在其他答案中可以绕开很多有毛的细节。折衷方案是您必须处理一些数学运算。


3
有理数运算对于任何类型的数值积分都是完全不可行的。即使每个时间步仅做一次* / + -,分母也会随着时间而变得越来越大。
大约在

我希望即使不考虑积分,这也不是一个好的解决方案,因为仅进行了两次乘法或除法运算后,代表分子和分母的数字就会溢出64位整数。
jvn91173

0

这里的术语有些混乱。物理系统可以是完全确定性的,但无法在一个有用的时间段内建模,因为它的行为对初始条件极为敏感,并且初始条件的无限小变化将产生完全不同的行为。

这是一个真实设备的视频,其行为是故意无法预测的,但从统计意义上讲除外:

https://www.youtube.com/watch?v=EvHiee7gs9Y

构造简单的数学系统(仅使用加法和乘法)很容易,其中N步后的结果取决于起始条件的第N个小数位。在用户可能拥有的任何计算机硬件和软件上编写一致的模型的软件几乎是不可能的-即使您有足够大的预算在每种可能的硬件和软件组合上测试应用程序。

解决此问题的最佳方法是从根本上解决问题:使游戏的物理性具有确定性,以便获得可再现的结果。

另一种方法是通过调整计算机软件来建模不是物理学指定的东西,从而使其具有确定性。问题是,与显式更改物理方法相比,您已经在系统中引入了更多的复杂性层。

作为一个具体示例,假设您的游戏涉及刚体的碰撞。即使您忽略摩擦,实际上也不可能精确建模任意形状的对象之间的碰撞,这些对象可能随着运动而旋转。但是,如果您改变这种情况以使唯一的对象是不旋转的矩形砖,那么生活就会变得非常简单。如果游戏中的对象看起来不像砖块,请使用一些“非物理”图形隐藏该事实-例如,将碰撞瞬间隐藏在某些烟雾或火焰或卡通文字泡沫“ Ouch”之后管他呢。

玩家必须通过玩游戏来发现游戏的物理原理。只要它是自洽的,并且与常识性经验足够相近就可以,那么它是否不是“完全现实的”也没关系。

如果使物理学本身具有稳定的行为,则其计算机模型也可以产生稳定的结果,至少在某种意义上来说舍入误差是无关紧要的。


1
我没有看到术语上的任何混淆。OP希望从潜在的混乱系统中获得确定性的行为。那完全是可行的。
标记

使用更简单的形状(例如圆形和矩形)根本不会改变问题。您仍然需要许多三角函数,sqrt等...
jvn91173,

-1

使用双浮点精度,而不是单浮点精度。尽管不是十全十美,但它足够准确,可以被认为是确定性的。您可以将火箭以双浮点精度而不是单浮点精度发送到月球。

如果您确实需要完美的确定性,请使用定点数学。这会降低精度(假设您使用相同的位数),但结果不确定。我不知道有任何使用定点数学的物理引擎,因此如果您想走这条路线,可能需要编写自己的引擎。(我建议您不要这样做。)


11
双精度方法与蝴蝶效应不符。在动态系统(例如物理模拟)中,即使是初始条件中的微小偏差也可以通过反馈来放大,滚雪球直至可察觉的误差。所有多余的数字都会使延迟时间更长-迫使雪球在它变得足够大以至于引起问题之前滚动得更远。
DMGregory

2
一次出现两个错误:1)双浮点遭受相同的问题,通常只会将问题推迟到更难调试的将来。2)没有规则规定固定点的精度必须小于浮点精度。根据规模和手头的问题,或每个固定点号准备使用的内存,它们的精度可能较低,相同或更高。说“它们不够精确”是没有意义的。
菲涅尔

@phresnel作为定点精度的示例,IBM 1400系列使用了任意精度定点十进制数学。为每个数字指定624位数字,您已经超出了双精度浮点数的范围精度。
标记

@phresnel(2)好点。我更新了答案以假定位数相同。
Evorlor

-2

使用纪念图案

在初始运行中,保存每一帧的位置数据或所需的任何基准。如果那太不好了,则仅每n帧执行一次。

然后,当您重现模拟时,请遵循任意物理原理,但每n帧更新一次位置数据。

过度简化的伪代码:

function Update():
    if(firstRun) then (SaveData(frame, position));
    else if(reproducedRun) then (this.position = GetData(frame));

9
我认为这不适用于OP的情况。假设您和我都在不同的系统上玩游戏。我们每个人都以相同的方式放置拼图碎片-开发人员未预先预测的解决方案。当您单击“开始”时,您的PC将模拟物理过程,从而使解决方案成功。当我执行相同操作时,模拟中的一些小差异导致我的(相同)解决方案未获得成功的评分。在这里,我没有机会从成功的运行中查阅备忘录,因为备忘录是在您的计算机上发生的,而不是在开发时发生的。
DMGregory

@DMGregory是的。谢谢。
jvn91173
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.