目录
多重物体碰撞检测系统
广阶段的碰撞检测
Sweep-and-Prune
网格
总结
更多样的话题
时间临界的碰撞检测
距离查询
考虑一个有弹簧和齿轮的旧式时钟,我们用一个计算机中的细节三维模型来表达它。现在想象使用碰撞检测来模拟时钟的运动。模拟弹簧缠绕和钟的运动过程为碰撞检测和生成响应。这样的一个系统将会要求在上百对物体之间进行碰撞检测。而之前描述的算法检测的仅仅是一对物体的碰撞(两个物体的碰撞检测)。蛮力搜索所有可能的碰撞对是低效的,所以在这样的情况下不适用。
在这里,我们将会介绍一个两层次碰撞检测系统,它针对的是多个运动物体的大型场景系统的。第一层次找出了环境中所有可能碰撞的物体。这些结果随之被交到第二层次,在这里我们计算每一对物体实际的碰撞检测。17.4中介绍的OBB算法(或者其他可以对两个物体进行碰撞检测的算法)可以在这里使用。
碰撞检测系统中,第一层的目标是减小第二层调用的个数。这意味着我们需要给出尽可能少的潜在碰撞物体。在这一层次,碰撞检测在一个更高的层次进行(i.e.对象层次),因此它通常被称为广阶段的碰撞检测。
大多数这方面的算法从围住包围盒里的每个物体开始,然后它们采用一些方法来找到所有重叠的包围盒对。一个简单的方法是对每个物体使用轴向包围盒(AABB)。为了避免重复计算经历刚体运动的物体的AABB包围盒,我们调节AABB包围盒使其成为大得足够包含任意朝向的对象的固定立方体。这个固定立方体被用于快速判定在这些包围盒中哪些对物体完全相交。有时候,使用动态改变大小的AABB包围盒会更好。
我们可以使用球体而不是固定立方体。这是有道理的,因为球体是可以包裹住任意朝向物体的完美包围盒。Kim提出了一个球体的算法。还有另外一个方法,是使用凸包或者其他凸多面体,使用例如Lin-Canny算法或者V-clip算法而不是固定立方体。接下来,我们将介绍两个广阶段碰撞检测的算法:sweep-and-prune,以及使用网格。一个完全不一样的方法是使用14.1.3中介绍的松散八叉树(loose octree)。
我们假设每个物体都在AABB包围盒中。在sweep-and-prune技术中,时间相干性(the temporal coherence)——在经典应用中经常出现的东西,在这里也得到了应用。时间相干性意味着物体从一个帧到下一个帧之间在它的位置以及方向上只发生了相关的微小变化(因此也叫帧到帧相干)
Lin指出,三维重叠包围盒问题可以在时间内解决(k是成对重叠的次数),但是它可以利用相干性来提高效率,所以复杂度可以降为O(n+k)。但是,这基于动画有一定的时间相干性。
如果两个AABB包围盒重叠,那么在每个主要轴向的三个一维区间(由AABB包围盒的起始点组成)必定也是重叠的。在这里,我们将会描述在帧到帧相干性高时(这是我们在合理应用下可期望的),怎样能够高效地检测出所有一维区间的重叠。有了结果后,我们可以使用对三个主要轴分别应用一维算法来解决AABB包围盒的三维问题。
假设n个区间(沿着特定轴)用si和ei来表达,其中,并且。这些值按升序存储在链表中。然后我们从头到尾扫描链表。当遇到一个起始点si时,它所对应的区间被放入一个活动区间链表中。当遇到一个终止点时,对应的区间从活动链表中移除。现在,如果在活动链表中包含区间的时候遇到区间的起点,那么遇到的区间与链表中所有区间重叠。这在下图有显示。
在上方,当在活动链表中仅有一个区间时(I3)会遇到区间I4(a处),所以可以得出I4和I3重叠。当遇到I2时,I4仍然在活动链表里(因为还没有遇到e4),所以I2和I4也是重叠的。当遇到e4时,从活动链表中移走I4(在b处)。在下方,I2被移动到了右方,当插入排序找出s2和e4需要改变位置时,同样可以得出I2和I4不再重叠。
这一程序将会花O(nlogn)的时间排序所有区间,加上O(n)的扫描链表的时间以及O(k)反馈k个重叠区间的时间,最终得到了一个的算法。但是,由于时间相干性,这个链表在帧到帧之间的变化不会非常大,因此可以在第一趟后使用冒泡排序或者插入排序性能会提升。这个排序算法在期望时间O(n)内排序邻近排序链表。
插入排序是通过递增地构建排序序列来工作的。我们从链表中的第一个数字开始。如果我们只考虑这一个的话,那么这个链表已经排好序了。接下来,我们加入下一个数字。如果第二个数字比第一个小,那么我们交换这两个数字。否则,我们保持原状。我们继续增加,然后交换位置,直到这个链表排好序。这段过程将对所有我们想要排序的物体不断重复,结果是一个排好序的链表。
为了利用时间相干性的优势,我们为每一个区间对维护一个布尔量。对于大型模型而言,这个也许是不适用的,因为它要求的空间。如果这一对重叠,一个特定的布尔量为真,否则为假。在算法的第一步,也就是完成第一趟排序后,我们初始化布尔量的值。假设一对区间在前一帧重叠,那么它们的布尔量为真。现在,如果一个区间的起点与另外一个区间的终点交换位置,那么这个区间对的状态会反转,在这里意味着它们的布尔量为假,并且它们不再重叠。在另一个方向也是类似的,i,e.如果一个布尔量为假,那么当一个起点和终点交换位置时它将为真。这在上图中也有解释。
所以我们为三个主要轴向创建了一个区间的排序链表,并且使用了前面的算法为每个轴找到了重叠区间。如果一对的所有三个区间都重叠,它们的AABB包围盒(区间表达的)同样也重叠,否则,它们不重叠。期望时间是线性的,这就得到了O(n+k)的sweep-and-prune算法,在这里,再一次,k是重叠的对个数,这样就得到了快速的AABB包围盒重叠检测算法。注意到这个算法在最坏情况下复杂度达到排序的。这将在团聚的时候发生,一个普遍的例子是当大量的物体处在地面上。如果z坐标是地面法线方向,就有z轴上的拥塞。一个解决方法是完全省略z轴,仅仅考虑xhey轴。在很多例子中,这样运行得更好。
网格以及分层网格在光线追踪加速中是非常常用的数据结构,它们同样也能很好地应用到广阶段的碰撞检测。在其最简单的形式中,一个网格就是一个n维数组,它不重叠的网格单元覆盖了整个场景空间。因而每个单元就是一个盒子,所有的盒子都有着相同的尺寸。从高层次而言,基于网格的广阶段的碰撞检测是从插入场景中所有物体的包围盒到网格开始的。然后,如果两个物体处在同一个网格单元中,我们马上就能知道这两个物体很可能会碰撞。因此,我们进行一个简单的包围盒之间的重叠检测,如果它们碰撞了,我们可以就能进入碰撞检测的第二阶段。在下图的左边,显示了一个有四个物体的二维网格。
左图:包含四个物体的一个低分辨率的二维网格。注意由于椭圆和星星共享了一个网格,所以需要使用包围盒测试。在这一例子中,他们并没有碰撞。三角形和星星同样共享了一个网格单元,而它们确实碰撞了。右图:一个高分辨率的网格,正如我们所见,星星与很多网格单元都重叠了,这让这个程序运行起来非常耗费。同样也注意有很多小物体挤在一个网格单元,这是另外一个可能的低效源头。
为了得到好的性能,选择一个合适的网格大小是非常重要的。这一问题在上图中右边得到了解释。一个想法是找到场景中的最大物体,然后创建一个足够在所有可能朝向包围这个物体的网格单元。在这样的方式下,三维网格中,所有的物体最多和八个网格单元重叠。
存储一个大的网格是非常浪费的,尤其是在当相当一部分网格被闲置的时候,所以,一个建议是可以使用空间哈希。大体上,每个网格单元被映射到哈希表上的一个索引。所有与一个网格单元重叠的物体都被插入哈希表,可以像往常一样继续检测。如果没有空间哈希的话,我们需要在为网格分配内存前,决定包住整个场景的AABB包围盒。我们同样必须限制物体可以移动的地方,所以它们无法离开这些包围盒。这些问题在使用了空间哈希后可以一并解决,由于物体可以立即被插入哈希表,所以它们处在什么位置并没有什么关系。
正如上图所解释的,在整个网格网格中总是选择一样大小的网格单元不总是最好的。所以,我们可以反之考虑分层网格。在这个例子中,使用了不同大小的不同网格,我们插入一个物体当且仅当物体的包围盒比网格单元要小。如果网格单元大小与分层网格中的邻接层次的差距恰为2,这个结构和octree非常类似。关于在碰撞检测中应用网格以及分层网格还有很多值得知道的细节,我们可以参考Ericson提出的这一主题的好的解决方法。
两阶段碰撞检测系统的大纲在下面给出了总结。
首先,使用sweep-and-prune算法或者基于网格的技术,找出所有对包围盒重叠的物体,并存储在对链表中。
其次,物体对输入到实际的碰撞检测算法中,如OBB树
最后,碰撞检测的结果由应用接收,所以可以采取相应行动(碰撞响应)
碰撞检测系统的大纲。广阶段的碰撞检测第一层找到的重叠物体对报告到实际的碰撞检测系统,在这里,会轮流将真正发生了碰撞检测到的物体报告给系统中负责计算碰撞响应的部分。最终,所有物体进入模拟环节,它们执行各自的变换。
在这一部分,将会对一些不同话题进行简短介绍:时间临界的碰撞检测,距离查询,任意形变物体模型的碰撞检测,然后最终是碰撞响应。
假设一个特定的游戏引擎在观察者看向一面墙的时候,在7ms渲染一副场景,但是当观察者望向长的走廊时,渲染要花上15ms。很明显,这会导致不同的帧率,这会对使用者产生干扰。在14.7.3中介绍了一个完成了稳定帧率的渲染算法。在这里,有另外一个方法,叫做时间临界碰撞检测(time-critical collision detection),它可以在应用使用了碰撞检测时使用。我们称它为“时间临界”,是因为碰撞检测算法提供特定帧率,如果说在9ms中完成其任务,那么它一定会在这一时间内完成。另外一个在碰撞检测中使用这一算法的原因是预知因果的重要性。所以,回到我们的例子,假设我们的目标是最多花20ms渲染每一帧(包括碰撞检测),也就是说,fps达到50。在时间临界的碰撞检测算法下,我们可以看到渲染引擎的碰撞检测部分使用了还剩余的时间,直到20ms。例如,如果一帧要花15ms渲染,那么碰撞检测仅使用5ms。
接下来的算法由Hubbard介绍。主要想法用宽度优先搜索遍历包围盒层次。这意味着在树的同一层次上的所有结点将在访问下一层前被访问。这与深度优先搜索相对应,其访问的是到达叶节点的最短路径。这两个遍历在下图中介绍。使用广度优先搜索的原因是一个结点的左右子树都被访问了,这意味着一起包围住整个物体的包围盒都被访问了。使用深度优先搜索,我们可能仅仅访问了左子树,因为算法可能会超时。当我们不知道我们是否有足够时间遍历整个树的时候,最起码我们要多同时访问一些结点的两个子树。
深度优先(左)vs宽度优先(右)遍历。深度优先搜索经常使用在遍历包围盒层次的碰撞检测中,但是对于时间临街的碰撞检测,我们使用宽度优先搜索。
这一算法首先找到了包围盒重叠的所有对物体,使用例如17.5.1中的算法。这些对物体被放到一个队列里,叫做Q。下一阶段是从移除第一个包围盒对开始的。它们的孩子包围盒之间相互测试,如果它们重叠了,那么孩子对将被放到队列的尾部。然后继续检测队列里的下一个包围盒对,直到队列变空(在这里例子里,是树被完全遍历),或者超时。
另外一个相关的方法是给每个包围盒对一个优先级,然后在这个优先级下排序这个队列。优先级可以基于各种因素,例如可见性,离心率,距离等等。Dingliana and O’Sullivan描述了计算近似碰撞相应和碰撞接触测定的算法。时间临界的碰撞检测算法中需要这一点,因为可能会在遍历完树之前超时。Mendoza and O’Sullivan展现了一个可变性物体的时间临界碰撞检测算法。
在特定应用中我们可能想要测试一个物体和某一环境是否达到了一定的距离。例如,当设计新车的时候,我们需要为不同体型的乘客预留足够的空间,使得他们感到舒适。所以,不同体型的虚拟人类将和车座以及车进行测试,来看他是否可以在不撞上车的情况下坐到座位上。除此之外,乘客最好能在坐在座位上的同时离车内物件至少10cm远。这一类测试的方法叫做公差验证(tolerance verification),它可以用于道路规划(path planning),i,e,通过算法决定从一个点到另一个点无碰撞冲突的道路。给出一个对象的加速度以及速度,可使用最小距离来评估碰撞的时间下界。通过这种方法,可以在此时间之前避免碰撞检测。另外一个相关的查询是关于穿透深度的,也就是说,找出两个物体进入对方的深度。这一距离可以用于把这两个物体移回到它们恰好不穿透对方的位置,然后就能在那一处计算出合适的碰撞响应。
其中一个实际的方法是计算出凸多面体之间的最小距离,这叫做GJK,取自它的发明者:Gilbert,Johnson和Keerthi。这个算法的大致思路会在这一章给出。这个算法计算出了两个凸物体A和B的最小距离。为了做到这一点,A,B间不同的物体(有时称作物体和(sum object))记作:
这同样也被称为A和B的闵可夫斯基和(Minkowski sum)(可以参照16.18.3)。所有的x-y差被看做是一个点集合,它构成了一个凸物体。这样的差的一个例子在下图显示,他解释了怎样构造一个差对象。
左边,展示了两个凸物体A和B,为了构造A-B,首先移动A和B保证一个参考点处在原点(在左图中已经完成了)。然后B进行翻转,正如中间的图所示的,然后B的参考点被放置到A的表面,翻转后的B在A上扫过。这构造了右图中的A-B。最小的距离d在左右图中都有显示。
GJK的想法是,不再计算A和B之间的最小距离,而是计算A-B以及与原点的最小距离。这两个距离是等价的,这一算法在下图中有解释。注意到如果原点在A-B中,A与B重叠。
左上方:将计算出多边形到原点的最短距离。右上方:任意选择一个三角形作为算法的起始点,计算出了到该三角形的最短距离。顶点7是最近的。左下方:在下一步,所有顶点投影到原点到顶点7所处直线,最近的顶点取代了投影里三角形最远的顶点。因此顶点4取代了顶点8。因而找到了三角形中最近的顶点,它处在顶点4到7所在线段上。右下方:顶点投影到原点与前一步最近点所在直线上,然后顶点5取代了顶点1,它是投影中最远的点。顶点5是三角形中最近的点,当顶点投影到顶点5的直线上,我们发现5是最终的最近点。迭代结束了。在这时已经找到了三角形里的最近点,碰巧也是顶点5。返回这一点。
算法是从多面体中任一单形(simplex)开始的。一个单形就是在各自维度内最简单的基元,所以在二维中它是三角形,在三维中它是四面体。然后我们计算出单形中距离原点最近的点。Van den Bergen显示了怎样通过解线性方程组来完成这一点。所有多面体的顶点投影到一个向量中,选择到原点最小投影距离的向量,使其成为更新后的单形的一个新顶点。由单形中加入了新的顶点,单形中原有的一个顶点应当被移除。我们应当移除投影最远的点。在这一点上,计算出了更新后的单形的最小距离,我们反复迭代这个算法直到无法继续更新单形。算法对两个多面体会在有限步内结束。这个算法的性能可以通过使用很多技术提升,例如增量计算和缓存。
Van den Bergen描述了一个快速而稳定的GJK实现。GJK同样可以扩展到计算穿透深度。计算最小的算法不仅仅有GJK,还有很多其他的算法,例如Lin-Canny算法,V-Clip,PQP SWIFT,以及SWIFT++(同样也计算凹刚体之间的距离)。