原文有35页,容我慢慢翻译,第一部分翻译了10页
reference:《Real-Time Rendering》
目录
17 前言 17.1 和射线的碰撞检测 17.2 使用BSP树的动态碰撞检测 17.3 一般层次的碰撞检测 17.3.1 分层的构建 17.3.2 不同层之间的碰撞检测 17.3.3 代价函数 17.4 OBB树 17.5 多重物体碰撞检测系统 17.5.1 广阶段的碰撞检测 17.5.2 总结 17.6 更多样的话题 17.6.1 及时碰撞检测 17.6.2 距离查询 17.6.3 可变形的模型 17.6.4 碰撞响应 17.6.5 基于GPU的碰撞检测 |
把东西击倒,尤其是在一些很突出的角度上,是一件极大的乐事。
——乔治·桑塔耶拿
碰撞检测是许多计算机图形应用基础而又重要的部分。它在虚拟操控,CAD/CAM,计算机动画,物理建模,游戏,飞行和驾驶模拟,机器自动,路径和运动规划(公差验证),装配(??),以及几乎所有虚拟现实的模拟中扮演着重要的角色。由于它广泛的使用,碰撞检测仍然是人们不断扩展研究的课题。
碰撞检测是适用于碰撞处理的一部分,碰撞处理可以分为三个部分:碰撞检测,碰撞计算,碰撞响应。碰撞检测的结果通常是一个布尔量,它告知了是否存在两个或者更多的物体相撞;而碰撞计算求出物体间碰撞的实际交点;最终,碰撞响应决定针对两个物体的碰撞,该采取什么样的行动。
在第一部分我们讨论简单而又相当快的碰撞检测技术。主要的想法是用线的集合来近似一个复杂的物体。这些线条将和场景中的基本体检测交点。这一技术在游戏中非常常见。另外一个近似的方法将会在第二部分介绍,在这里我们使用了BSP树来表达场景,一个角色可能是由圆柱体来表达的。然而,所有的物体不总是能够被近似表达为直线或者圆柱体,有一些应用也许需要更精确的检测。
想象一下,例如,我们想要决定一个三维的手撞上(拿起)一个三维的茶杯,两个物体都是由三角形网格表达的。这将如何有效地完成呢?当然,手的每个三角形都需要和茶杯的每个三角形进行相交检测。但是在这个例子里,如果两个物体隔得很远,这个算法应当快速地发现这一点,然后不再进行繁重的计算。哪怕物体已经靠的足够近了,大量的检测依然是不高效的。我们需要一个能够快速完成这个例子的算法。很容易想出碰撞检测更复杂的情节,下图显示了这样的一个复杂例子。
左图中,计算的是许多障碍物之间以及与环境的碰撞检测和响应;右图中,则更加混乱。(左图来自Crytek,右图来自Valve Corp)
第三部分描述了通用的,分层的包围盒碰撞检测算法。一个基于OBBs的特殊实现将在第四部分介绍。下面的特点应用于大多数碰撞检测系统:
○ 对于有大量多边形的模型,在相隔很远和距离很近时,它们都能完成交互速率(interactive rates???)
○ 它们能处理多边形集合。I.e,一般的没有凸面或有效邻接信息的约束的多边形模型。
○ 模型能进行刚体运动,i.e.旋转加上平移,甚至更一般类型的变形。
○ 它们提供有效的包围盒,试图把几何体集合包得更紧。小的包围盒提升了算法在判断两个物体是否相交时的表现。包围盒的创建速度也应当是较快的。
一个场景可能包含几十甚至上百个运动物体,一个好的碰撞检测系统将能很好地应对这一点。如果一个场景包含n个运动物体和m个静止物体,那么一个普通的方法将会在每一帧计算
个物体的检测。第一项对应着静止物体和运动物体碰撞的次数,下一项对应着运动物体之间的碰撞次数。最原始的方法随着m和n的增加,复杂度快速提升。在这种情况下我们需要更聪明的做法,这将在第五部分介绍。这样的一种方法通常使用这样的算法,首先检测所有可能存在的物体对物体的碰撞,然后再使用如第四部分介绍的OBB树算法来处理它们。
讨论完一系列碰撞检测之后,会有简短的部分介绍一些混杂的话题。及时的碰撞检测是一个在常数时间内进行近似碰撞检测的算法,它将在第七部分介绍。有时候我们期望得到两个物体的最短距离,这个话题将在研究了一些可变性物体后介绍。最终,碰撞响应将被简略地提及。
必须指出的是,碰撞检测的代价估计是非常困难的,因为算法是对实际碰撞场景敏感的,在所有的例子中,没有哪一种算法的表现是最好的。
和射线的碰撞检测
在这一章节,我们将会展示一个在特定情况下非常高效的技术。想象一辆在倾斜面上驾驶的汽车,我们想要用道路的信息(i.e.道路建造的基元)来引导车子向上。当然,这可以通过检测车轮所有基元与道路所有基元来完成,这个方法会在第三部分介绍。但是,对于游戏或者其它应用场合,我们不总是需要这种精细的碰撞检测。反之,我们可以用射线集合来近似一个运动物体。在车的例子里,我们可以在四个轮子中的每个轮子放置一条射线。这个近似在实际中的表现非常好,只要这四个轮子是车辆和外界(地面)接触的唯一位置。假设车辆一开始处在一个平面上,那么我们可以在每个车轮上放置一条射线,每条射线的原点处在车辆和环境接触的那个位置。轮子上的射线将会和环境进行相交测试。如果射线原点到环境的距离为0,那么这个轮子就恰在地面上。如果距离大于0,那么轮子和环境没有接触,一个负距离意味着轮子已经穿过了环境。这一应用中,可以使用距离来计算碰撞响应——一个负距离将会让车子(在那个轮子处)向上,而一个正距离将会让车子向下(除非车子在空中飞了短暂的时间)。注意这一类型的技术很难应用于更加复杂的场景。例如,如果车子在旋转运动中发生撞击,我们需要更多不同方向的射线。
为了加速这一相交检测,我们可以使用常用于计算机图形学中加速的一个技术——多层次表示法(hierarchical representation)。这个场景可以使用沿坐标轴的BSP来表达(它和K-D树一样)。BSP树在14章中有介绍,在那里它被用于视锥体裁剪。BSP树同样也可以用于加速相交检测。根据这个场景中使用了哪种类型的基元,我们使用不同的射线对象相交检测技术(看16章)。BSP树并不是唯一的用于快速找到相交的表达方式,一个包围盒层同样也是有用的。
和标准的光线追踪里我们要找到离光线最近的物体不同,我们实际上需要的是沿着射线最后的交点,有时距离可能是负的。为了避免处理射线时在两端搜寻,测试射线的原点将会后移,直到它处在环境中的包围盒外。实际上,这仅仅意味着,射线不再是从距离0处开始,而是从一个负距离开始,并且处在包围盒之外。为了处理更一般的情况,例如在隧道里开车并且和屋顶做碰撞检测,我们不得不从两个方向进行搜索。
使用BSP树的动态碰撞检测算法
在这里,将会介绍由Melax提出的碰撞检测。它通过BSP树来决定几何物体之间的碰撞,碰撞的物体可以是球体,圆柱体或者其他凸表面的物体。它同样也支持动态的碰撞检测。例如,这个算法可以检测到沿着点p0到p1的直线段上有无碰撞检测发生。这里演示的算法已经被用于一些商业游戏,在那里,一个角色的几何形体被近似表达为一个圆柱体。
标准的BSP树可以高效地用于线段的测试。一个线段可以表示为一个点(粒子)从p0移动到p1。这里也许会有一些相交,第一个交点(如果有的话)表达了点和BSP树中物体的相交。注意到,在这个例子中,BSP树是沿表面对齐的,而不是沿坐标系对齐的。这也就是说,树中的每个平面与场景中的墙、地板或者天花板是对应的。我们可以很容易把一个点扩展成一个从p0到p1半径为r的球体。我们不再测试线段与BSP树中结点对应的平面,而是将每个平面都沿着平面法线方向移动了距离r。这个平面调节后的排序在下图中有进一步阐述。这将针对每个碰撞查询动态产生,所以一个BSP树可以被用于任意大小的球体。假设一个平面为那么调节后的平面为其中r的符号取决于你选择了平面的哪一侧来继续碰撞检测的搜索。假设角色在平面正半空间一侧,i.e.当时,我们从d中减去半径r。注意这时负的半空间被认为是实心(solid)的,一个角色无法立足的地方。
对于左边,从上方看有一些几何体(蓝色),它的BSP如中间图所示。为了检测这个树与圆心在p的圆形,BSP树将向外生长圆的半径的长度,然后就可以改为检测点p和生长后的bsp树。这在右图中展示。注意角落应当是圆滑的,所以这是这个算法的一个近似。
一个球体无法很好地近似游戏中的角色。角色顶点的凸包或者包围着角色的圆柱体会有更好的效果。为了使用这些不同的包围盒,平面方程中的d需要修改成不同的样子。为了检测对BSP树移动的点集合的凸包S,我们把下面这个方程的标量加到平面方程中的d值上。
再一次指出,这个减法符号意味着角色处在平面的正半空间。点p0可以是任意适合作为参照点的点。对于球体而言,我们通常选择球体的中心。对于一个角色来说,我们通常使用接近脚的一个点,或者也许是肚脐上的一个点。有时候这个选择可以简化方程(正如选在球体中心那样)。我们和BSP树检测的就是点P0,。对于动态查询,也就是说,在一帧内角色从一个点走向了另一个点,点p0将被作为这个线段的起始点。假设角色在一帧内行走了向量w,那么这个线段的终点就是p1 = p0 + w。
圆柱体也许是更有用的,因为它检测起来更快,而且依然能够很好地近似游戏中的角色。但是,这将会涉及到更多平面方程的调整值的衍生。在这个算法中,我们一般做的是把BSP树的包围盒(球体,凸包,圆柱体,在这个例子中)的测试改为调整的BSP树中的点P0的测试。这基本上和闵可夫斯基和(Minkowski Sum)是一样的。然后,为了把这个扩展到运动物体上,我们把点p0换为从p0到目的点p1的线段的检测。
我们对一个圆柱体扩展这样一个测试,它的性质在下图中显示。参照点p0处在圆柱底面的中心。图b显示了我们想要解决的:检测圆柱体和平面。在图c中,我们移动平面使得它刚好与圆柱体接触。然后我们计算从接触点到点p0的距离e。距离e将用于图d中将平面移动到新的位置。e的值是在每一帧对每个平面动态计算的。事实上,首选被计算的是从p0到点t的一个向量,t也就是平面和圆柱的交点。这在图c中显示。接下来,这样计算e:
现在,还剩下t需要计算。t的z分量(i.e.圆柱坐标方向)非常简单:如果nz > 0 , 那么, i.e.p0的z分量。否则,。tz的值对应着圆柱体的底部和顶部。 如果nx和ny的值都是0(e.g.对于地面或者天花板),那么我们使用圆柱盖上的任一顶点。一个自然的选择是(tx,ty)
= (px,py),也就是圆柱盖的中心。否则,对于一个不垂直的n,下面的选择将会给出圆柱盖边缘上的一点:
也就是说,我们把平面法线放置在x-y平面上,标准化它,然后放大r倍使其放置在圆柱体的边缘。
在这种方法中可能会有误差。下图显示了这样一个例子。正如看见的那样,我们可以通过使用一些额外的斜平面来解决这一问题。实际上,我们计算出了两个相邻平面的“外”角,如果这个角度大于90,我们会插入一个额外的平面。这一方法是为了提高本该是圆角的近似度。在下图中,可以看到普通的BSP树和用斜面扩展的BSP树的区别。斜平面当然可以提高准确度,但是它不会减少任何错误。
在左图中,右边的球体正确地碰撞,而左边的球体则过早地检测到了碰撞;在右图中,这通过增加了一个额外的斜平面来解决,它事实上不对应着任何真实的几何体。正如我们所见,碰撞检测在使用了这样的一个平面后已经正确了。
下面给出了这个碰撞检测算法的伪代码。它调用了BSP树的根N,它有N个正的孩子和N个负的孩子,线段由p0和p1定义。注意撞击的点(如果存在的话)将会返回一个叫做的全局变量:
HitCheckBSP(N,p0,p1) returns ( {TRUE, FALSE} ); if(not isSolidCell(N)) return FALSE; else if ( isSolidCell(N)) pimpact = p0 return true; end hit = FALSE; if(clipLineInside(N shift out, p0, p1, &w0, &w1)) hit = hitCheckBSP(N.negativechild, w0, w1); if(hit) p1 = pimpact end if(clipLineOutside ( N shift in, p0, p1, &w0, &w1)) hit = hitcheckBSP(N.posivtivechild, w0, w1); end return hit;
如果到达了叶子节点,函数isSolidCell就返回真,这时候我们处在实心的位置(与空心相对)。下图中解释了空心和实心状态。如果线段的部分(由运动路径v0和v1决定)处在结点转换平面的内部,函数clipLineInside返回真,也就是说,它处在负半空间。它同样裁剪了处在结点转换平面之外的线段,并且返回在w0和w1之间的线段结果。函数clipLineOutside是类似的。注意函数clipLineInside和clipLineOutside返回的线段是相互覆盖的。第9行设置v1 =,这仅仅是一个优化。如果碰撞发生了的话,那么一个潜在的碰撞点,,除了这个点之外不需要再测试其它点了,因为我们想要的就是碰撞的第一个点。在第7和第11行,N从“外部”转换到了“内部”。这种转化是指来自球体,凸包,以及圆柱体的调节后的平面方程。
对于左图,由p0p1定义的线段被由法线n定义的平面裁剪。平面的动态调整由虚线显示。函数clipLineInside和clipLineOutside返回由w0和w1定义的线段。注意到三条线段都本应该有相同的y坐标值,但是为了更清晰的表达它们被显示成这样。对于右图,显示了一个例子,它解释了为什么直线应当被裁剪成左边的样子。BSP树中的结点A同时属于左右两个三角形。所以,有必要移走它两个方向的平面。
Melax已显示这个算法相比起不使用动态调整平面的算法要昂贵2.5到3.5倍。这是因为在找合适的调整时有过多的计算。哪怕这样的慢速问题可能看起来很严肃,Melax指出这在环境中表现得并不那么差。一帧通常花费33,00us(30fps)来计算,而它最复杂的碰撞查询花费66us.这一策划的有点在于我们只需要单一的BSP树就能检测所有角色和对象。而其替代算法则需要为每个不同的半径以及对象类型存储不同的BSP树。