概念页
碰撞检测系统
Collision Detection System
碰撞检测系统判断游戏世界里的物体是否接触,本质上是一个"高级几何相交测试器",同时返回接触的详细信息。每个可碰撞对象由形状 + 变换描述,全部存放在独立的碰撞世界里;形状尽量用球、胶囊、盒、凸体等简单图元。为了在大量物体中快速找到真正相交的对,引擎使用时间相干性、空间划分,以及宽相、中相、窄相三级筛选(GEA4 卷 II 14.3)。
可碰撞实体
- 碰撞表示是独立的数据结构,既不同于对象的玩法表示,也不同于视觉表示(三角网格、细分曲面、粒子等)。形状应尽量简单:石头用球,车引擎盖用盒,人体用一组相连的胶囊;只有简单表示不够用时才上复杂形状(卷 II 14.3.1)。
- Havok 称之为 collidable(
hkpCollidable),PhysX 称为 actor(NxActor)。两者都包含形状和变换。需要变换的三个理由:形状通常以原点为中心、按规范朝向定义,必须变换到世界;移动复杂形状时只改变换比逐个移动顶点便宜;复杂形状占内存,多个 collidable 可以共享一个形状(赛车游戏里很多车形状相同)。 - 一个游戏对象可以没有、有一个或有多个 collidable(关节机械臂的每个刚性部件一个)。
碰撞世界与物理世界
- 碰撞系统用一个单例数据结构管理全部 collidable,即碰撞世界(Havok
hkpWorld,PhysXNxScene,ODEdSpace是包含所有碰撞形状的几何体层级之根)。好处:只包含可能碰撞的对象,不用遍历无关数据;数据可以按最高效的方式组织(缓存一致性);也是良好的封装,利于可理解性、可维护性、可测试性和复用(卷 II 14.3.2)。 - 有刚体动力学时,它通常与碰撞系统共享这个"世界",每个刚体对应一个 collidable(反之不一定),物理系统驱动碰撞系统每个时间步至少运行一次碰撞测试,所以常称为碰撞/物理世界。Havok 中
hkpRigidBody恰好指向一个hkpCollidable;PhysX 的NxActor兼任两者(刚体物理属性另存在NxBodyDesc)。刚体可设为固定,只作碰撞体用。多数 SDK 仍尽量把碰撞库与动力学分开,以便单独使用碰撞,甚至替换碰撞系统(GEA 说实际上没那么容易)(卷 II 14.3.2.1)。 - 原书问题(API 年代):书中 PhysX 类名
NxActor、NxScene、NxBodyDesc属于 PhysX 2.x。PhysX 3 起Nx前缀改为Px,单一 actor 类拆成层级(PxRigidActor→PxRigidStatic/PxRigidDynamic等),多数描述符 (descriptor) 被创建函数的参数取代,对象由PxPhysics创建后再scene->addActor()(NVIDIA PhysX SDK 3.4 文档 "Migrating From PhysX SDK 2.x to 3.x")。概念(actor 兼任碰撞体与刚体、场景即物理世界)不变。
形状的概念
- 形状是由边界围成、有明确内外的空间区域:2D 有面积,边界是曲线或多边形;3D 有体积,边界是曲面或多面体。地形、河流、薄墙更适合用曲面(有正反面、无内外)。多数 SDK 把"形状"扩展到开放曲面,并允许给曲面一个挤出厚度,减少小而快的物体穿过无限薄曲面的"子弹穿纸"问题(卷 II 14.3.3)。
- 相交严格说是两个形状共有点的集合;游戏通常只想知道是否相交,以及接触信息:分离向量(沿它移动即可脱离碰撞)、哪两个 collidable、哪些子形状甚至哪些特征接触、投影到分离法线上的速度等。Havok 用
hkContactPoint返回(卷 II 14.3.3.1–14.3.3.2)。 - 凸性:从内部出发的射线不会穿出表面超过一次;直观判断是"用保鲜膜包起来没有气泡"。2D 中圆、矩形、三角形是凸的,吃豆人 (Pac-Man) 不是。凸形状之间的相交检测更简单、更便宜(卷 II 14.3.3.3)。
碰撞图元
| 图元 | 表示与特点 |
|---|---|
| 球 | 中心 + 半径,正好塞进一个四分量浮点向量,适合 SIMD;最高效 |
| 胶囊 | 圆柱 + 两个半球,两点 + 半径;可视为扫掠球(但静态胶囊与随时间扫出胶囊的球并不完全相同);比圆柱和盒更快,常用于人体四肢 |
| AABB | 最小点 + 最大点;只有相对某个特定坐标系才谈得上"轴对齐";与 AABB 相交测试极快,但物体旋转后要重算,且离轴旋转时贴合很差 |
| OBB | 三个半边长 + 变换;贴合任意朝向的物体,表示仍简单 |
| DOP(离散有向多面体) | 把若干平面从无穷远沿法线推到贴住物体;AABB 和 OBB 都是 6-DOP;常从 OBB 出发用 45° 平面倒角,倒掉 8 个角得 14-DOP |
| 任意凸体 | 美术在 Maya 中建模,离线工具检验凸性后转换为 k 个平面方程;可用 GJK |
| 多边形汤 (poly soup) | 任意非凸三角形集合,常用于地形和建筑;最贵,还要处理相邻三角形共享边上的虚假相交,所以尽量只给不参与动力学的物体用 |
| 复合形状 | 一组形状(椅子 = 靠背盒 + 座椅与四腿盒);两三个凸体往往胜过一个 poly soup;可先测整体的凸包围体,Havok 称为中相 (midphase) |
(卷 II 14.3.4)几何细节见 几何图元的表示 与 包围体构建。
poly soup 有没有内部:它可能只是开放曲面,信息不足以判断该往哪个方向把穿插的物体推出去。办法是让所有三角形绕序一致,整体就有正反面;再记录是否闭合(若离线工具能判定),闭合时把正反解释为外内。开放曲面(如地形)也可以约定"正面总背离地心",即正面就是外部,但通常需要定制碰撞引擎让它理解这一约定(卷 II 14.3.4.7)。
解析几何与相交测试
- 点-球:, 则在内。球-球:;两边平方可省去开方:(卷 II 14.3.5.1–14.3.5.2)。
- 分离轴定理:若存在一条轴,使两个凸形状在其上的投影区间 不重叠,则它们不相交;凸形状找不到这样的轴就一定相交(凹形状不成立,这也是偏好凸形状的原因之一)。区间不相交当且仅当 或 。两球即将接触时唯一的分离轴平行于球心连线,分开越远可选的轴越多(卷 II 14.3.5.3)。更多见 相交测试基础与分离轴。
- AABB-AABB:分离轴只可能是三条坐标轴,三个轴上的区间都重叠才相交(卷 II 14.3.5.4),见 包围体重叠测试。
- GJK(Gilbert、Johnson、Keerthi):适用于任意凸多面体。两个凸形状的闵可夫斯基差 包含原点当且仅当两者相交;差本身是凸的,只需关心其凸包。算法从凸包内任一点构成的单纯形开始,每次朝原点方向找凸包上的支撑顶点加入单纯形(点→线段→三角形→四面体);若单纯形包住原点则相交,若找不到比当前单纯形更接近原点的支撑点则不相交。GEA 推荐 Casey Muratori 的视频 "Implementing GJK",以及原论文、Christer Ericson 与 Gino van den Bergen 的 SIGGRAPH/GDC 讲稿(卷 II 14.3.5.5)。
- 原书问题(支撑顶点的定义):书中把支撑顶点说成"凸包上沿当前方向最接近原点的顶点"。标准定义正相反:方向 上的支撑点是 ,即沿 最远的点;对闵可夫斯基差有 (Ericson《Real-Time Collision Detection》9.5 节;Wikipedia "Gilbert–Johnson–Keerthi distance algorithm"、"Support function")。GJK 正是靠"最远点都越不过原点"来判定不相交。
- 书中解释闵可夫斯基差时一句写成"sphere A 和 sphere B 有公共点",应为一般形状 A、B(前后文都在讲任意形状)。另外"差包含原点 ⇔ 相交"其实对任意形状都成立,凸性真正的作用是让差也是凸的、可以用单纯形逼近。
flowchart TD
S["单纯形 = 闵可夫斯基差内任一点"] --> D["求单纯形指向原点的方向"]
D --> V["沿该方向找差的支撑顶点"]
V --> C{"支撑点比当前单纯形更接近原点?"}
C -- "否" --> N["不相交"]
C -- "是" --> A["加入单纯形,升一维"]
A --> O{"单纯形包住原点?"}
O -- "是" --> Y["相交"]
O -- "否" --> D上图按 GEA 对 GJK 的文字描述绘制;真实实现还会在每步丢弃不需要的单纯形顶点,书中未展开。
形状组合与双重分派
N 种形状需要 种两两测试,这是碰撞引擎复杂性的主要来源,所以引擎会限制图元种类;GJK 受欢迎也因为它用一个算法覆盖所有凸形状,只是支撑函数因形状而异。选择测试函数常用双重分派:按两个对象类型索引的二维函数表,或先按 A 的类型调一个虚函数、再在其中按 B 的类型调第二个虚函数。Havok 用碰撞代理(hkpSphereSphereAgent、hkpGskConvexConvexAgent 等)处理具体组合,由 hkpCollisionDispatcher 管理二维分派表(卷 II 14.3.5.6)。
运动物体与隧穿
- 最简单的做法是把每个时间步的世界当作静止快照做静态测试。只要物体相对自身尺寸不太快就很好用,Havok 默认如此(卷 II 14.3.5.7)。
- 小而快的物体一步移动距离大于自身尺寸时,前后两个快照之间有空隙,中间的物体会被完全漏掉,即"子弹穿纸"或隧穿 (tunneling)。
- 扫掠形状:形状从上一快照移到当前快照扫出的新形状(扫掠球 = 胶囊,扫掠三角形 = 三棱柱),相当于对运动线性插值。沿曲线扫掠的凸形状不再凸;旋转中的凸形状即使沿线段扫掠也不一定凸,用两快照极端特征线性外推可以得到凸形状,但未必反映真实运动。
- 连续碰撞检测 (CCD):求一个时间区间内两物体最早的冲击时间 (TOI)。保存每个 collidable 上一步与当前步的位置和朝向,分别线性插值得到中间时刻的近似变换,再沿路径搜索最早 TOI。常见方法:Brian Mirtich 的保守前进法、对闵可夫斯基和做射线投射、取各特征对的最小 TOI;Erwin Coumans 提出了保守前进的变体。
性能优化
- 碰撞检测吃 CPU 的两个原因:单次相交计算不简单;物体多时测试对数急剧增长,暴力两两测试是 。实际会用空间哈希、空间细分或层次包围体(卷 II 14.3.6)。
- 时间相干性(帧间相干性):速度合理时位置朝向帧间变化不大,可以跨时间步缓存结果。Havok 的碰撞代理通常跨帧持久,只要运动没让之前的计算失效就复用(卷 II 14.3.6.1)。
- 空间划分:八叉树、BSP 树、kd 树、球树等层次结构;沿一个分支向下时,该分支中的对象不可能与兄弟分支中的对象碰撞(卷 II 14.3.6.2)。见 空间数据结构 与 空间分区。
- 宽相、中相、窄相(Havok 的三级筛选):宽相用粗略 AABB 找可能相交的 collidable;中相测复合形状的粗包围体(复合形状可以嵌套,一般是一棵包围体层次);窄相测具体图元(卷 II 14.3.6.3)。
- 扫描剪除 (sweep and prune):GEA 说 Havok、ODE、PhysX 等主要引擎的宽相都用它。原书问题(以偏概全):SAP 很常见,但并非唯一或默认:ODE 提供
dSimpleSpace、dQuadTreeSpace、dHashSpace、dSAPSpace四种宽相空间(ODE Wiki "How Collision Detection Works");Bullet 同时提供btDbvtBroadphase(动态 AABB 树,插入删除比 SAP 快,适合大量运动物体)和btAxisSweep3(SAP)(Bullet 2.83 User Manual;btDbvtBroadphase.h注释);PhysX 4 起有多种宽相并加入 automatic multi-broad phase(NVIDIA "PhysX SDK 4.0 Available Now")。把各 AABB 在三条主轴上的最小、最大值排序,遍历有序列表找重叠。借助帧间相干性, 的排序可降到期望 ;物体旋转时更新 AABB 也能受益。
碰撞过滤
- 例:多数物体应能穿过水面(再用浮力模拟让它浮起或沉底)。按游戏规则接受或拒绝接触称为碰撞过滤(卷 II 14.3.8)。
- 层与掩码:Havok 中每个 collidable 属于且只属于一个碰撞层,默认过滤器
hkpGroupFilter为每层维护一个 32 位掩码,每一位表示能否与另一层碰撞。 - 回调:碰撞库检测到碰撞时调用回调决定接受或拒绝。Havok 在接触点首次加入时调用
contactPointAdded(),确认有效后(可能因找到更早的 TOI 接触而无效)调用contactPointConfirmed(),应用都可以在其中拒绝。 - 碰撞材质:类似渲染材质,给每个碰撞表面关联一组属性:撞击声音与粒子、恢复系数和摩擦系数、过滤信息等。简单图元整体一个材质;poly soup 可以逐三角形指定,所以绑定要紧凑,常用 8/16/32 位整数索引全局材质数组,或一个指针。
与 UE5 的对照
| GEA 概念 | UE5 Chaos | 差异 |
|---|---|---|
| 简单碰撞图元、复合形状 | 物理体由盒、球、胶囊或凸包组成(Physics Asset Editor / Physics Bodies) | GEA 建议 poly soup 只用于不参与动力学的物体 |
| 碰撞层 + 32 位掩码 | 对象类型 × 响应(Block / Overlap / Ignore),结果由双方共同决定(Collision Overview、Collision Response Reference) | Havok 是层间的二值允许表;UE5 是三态响应且双向取"更弱"者 |
| 碰撞回调 | Simulation Generates Hit Events、Generate Overlap Events | UE5 要求显式打开事件开关,Overlap 需要双方都打开(Collision Overview) |
| 碰撞材质 | 物理材质(GEA 在 14.5.3.2 提到 Unreal 的 physical material,见 物理引擎与游戏的集成) | 见 Chaos 物理与碰撞 |
出处
- Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
- Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 14 章 14.3.1–14.3.6、14.3.8 节
- Collision Overview
- Collision Response Reference
- Physics Asset Editor / Physics Bodies
相关页面
游戏开发碰撞查询Collision Queries碰撞系统除了检测真实物体的接触,还要回答假设性问题:子弹从枪口朝某方向飞,先打中什么?车能否从 A 开到 B 不碰到东西?某半径内有哪些敌人?这类操作称为碰撞查询。最常见的是投射 (cast),也叫 trace 或 probe:沿射线或线段移动一个假想物体,看它会碰到什么;被投射的东西并不真正进入碰撞世界,不会影响其他物体(GEA4 卷 II 14.3.7)。游戏开发游戏中的物理与中间件Physics in Games & Middleware今天游戏引擎所说的"物理",准确说是经典刚体动力学模拟:理想化的、无限坚硬不可形变的物体在力的作用下如何运动和相互作用。它高度依赖碰撞检测系统;反过来,碰撞检测可以独立存在,几乎所有有移动物体的游戏都有某种碰撞检测,但不一定有物理。GEA 先讨论要不要物理、物理是否好玩、它对项目各部门的影响,再列出主流中间件(GEA4 卷 II 第 14 章引言、14.1–14.2)。游戏开发刚体动力学Rigid Body Dynamics游戏物理引擎关心的是动力学:力如何影响运动。它做了两个简化:经典(牛顿)力学(无量子、无相对论效应)和刚体(形状不变)。无约束刚体有 6 个自由度,其运动可以分成互相独立的线性动力学(质心像质点一样平动)和角动力学(绕质心转动)。线性部分是 F=ma 的常微分方程;三维角动力学则以角动量为主变量,并用一个四元数形式的微分方程更新朝向(GEA4 卷 II 14.4)。游戏开发碰撞响应与约束求解Collision Response & Constraint Solving刚体碰撞后,模拟必须让它们做出逼真的反应,并保证一步结束时不再互相穿透,这就是碰撞响应。主流方法有三种:基于牛顿恢复定律的冲量法、像硬阻尼弹簧一样的惩罚力、以及把碰撞当作约束交给通用约束求解器。此外还要处理摩擦、多边形汤上的伪接触(焊接),以及让物体安静下来(休眠与模拟岛)。约束(点对点、铰链、棱柱、布娃娃、动力约束等)限制刚体的自由度;每帧的物理步骤是"积分 → 碰撞检测 → 碰撞解决 → 约束求解"的迭代误差最小化(GEA4 卷 II 14.4.7–14.4.10)。游戏数学相交测试基础与分离轴Intersection Test Fundamentals & Separating Axis相交测试判断两个对象是否相交(包含、边界相交或分离),有时还需要最近交点或穿透深度与方向,是碰撞检测、视锥剔除、拾取和光线追踪的基础。本页汇总 RTR4 第 22 章的通用工具:光线与曲面的表示、常见包围体定义(AABB、OBB、k-DOP、凸多面体、胶囊与菱形体)、分离轴测试 (SAT)、几何概率与表面积启发式 (SAH)、设计相交例程的经验法则,以及基于 GPU 的拾取。游戏数学包围体重叠测试Bounding Volume Overlap Tests碰撞检测先测试包围体是否重叠,只有重叠时才逐图元测试;最底层则需要三角形之间、三角形与盒之间的精确测试。本页整理 RTR4 第 22.10~22.13 节:平面与 AABB/OBB(只测两个角的"投影半径"法)、三角形-三角形(Guigue-Devillers 行列式法)、三角形-AABB(13 轴 SAT)、球-球、球-盒(Arvo 及其向量化版本)、AABB-AABB、k-DOP-k-DOP 与 OBB-OBB(15 轴 SAT)。更复杂的 BV(OBB、k-DOP)空体积更少、更紧,代价是测试更贵。游戏数学几何图元的表示Geometric Primitive Representations除点、向量、矩阵、四元数外,游戏引擎还天天用到直线/射线/线段、球、平面、AABB、OBB、视锥和凸多面体区域。本页按 GEA 整理它们在内存中如何表示以及最基本的「点在内部吗」测试;各种求交与重叠算法见 RTR4 来源的相交测试系列页面。游戏数学包围体构建Bounding Volume Construction紧凑的包围体能降低相交测试成本:光线击中凸体的概率与其表面积成正比,拒绝永远不比相交慢,所以求交加速应最小化 BV 表面积;碰撞检测则常最小化体积。本页讲 AABB/k-DOP、包围球(AABB 法、Ritter 近似法、Welzl 最优法)、凸包(Quickhull)以及 OBB(PCA 法、数值最小化、Larsson 与 Källberg 的近优线性算法)的构建方法与取舍。图形学空间数据结构Spatial Data Structures空间数据结构把几何体按 n 维空间组织起来,通常是层次化的树,使"几何体是否重叠"一类查询从 O(n) 降到大约 O(\log n)。常见类型有包围体层次 (BVH)、BSP 树(轴对齐的 k-d 树与多边形对齐两种)、四叉树/八叉树(含松散八叉树),以及更偏向组织模型关系的场景图。它们是剔除、光线求交、碰撞检测等加速算法的基础。游戏开发空间分区Spatial Partition空间分区 (Spatial Partition) 把对象按位置存进空间数据结构,使“找出某位置附近的对象”这类查询不必遍历所有对象,把 O(n) 或 O(n^2) 的操作降下来。最简单的是固定网格;还可以选层次结构(四叉树、BSP、k-d 树、BVH),以及边界固定或随对象自适应的划分方式。代价是对象移动时要更新结构,并且要额外占用内存。游戏开发 · UE5Chaos 物理与碰撞Chaos Physics & CollisionChaos 是 UE5 的物理与破碎系统。日常最常打交道的是碰撞模型:每个物理体有对象类型 (Object Type) 和对其他类型的 Block/Overlap/Ignore 响应,结果由双方共同决定;Hit 与 Overlap 事件都要显式开启。骨骼网格通过物理资产 (Physics Asset) 获得刚体与约束(布娃娃、物理动画);Chaos 破碎基于几何集合 (Geometry Collection) 的分级断裂、簇阈值、连接图与缓存回放,并能把事件交给 Niagara。物理体用盒/球/胶囊/凸包代理,原理对应游戏数学中的包围体与相交测试。游戏开发 · UE5材质系统Material SystemUE 材质 (Material) 是用节点图描述「表面如何与光交互」的着色器生成器。它有三个正交维度:着色模型 (Shading Model) 决定光照模型(Default Lit、次表面、清漆、毛发等),混合模式 (Blend Mode) 决定与背景的合成公式(Opaque/Masked/Translucent…),参数 + 材质实例 (Material Instance) 让美术在不重编译着色器的前提下做变体。PBR 输入(Base Color/Roughness/Metallic/Specular)直接对应 RTR4 的金属度-粗糙度工作流;最大的工程坑是静态参数导致的着色器排列爆炸。游戏开发物理引擎与游戏的集成Integrating a Physics Engine into a Game碰撞/物理世界里的刚体只是抽象的数学描述,要通过游戏对象这个中枢,间接连到屏幕上的视觉表现。刚体按谁驱动分三类:物理驱动、游戏驱动(Havok 称 key framed)、固定;Havok 用可在运行时切换的"运动类型"统一表示。每帧更新有固定顺序:先更新游戏驱动刚体、幻影、力、冲量和约束,再步进模拟,然后回读物理驱动对象、查询幻影、执行投射查询。物理宜用恒定的 1/30 或 1/60 秒时间步。GEA 还逐一讨论了子弹、手雷、爆炸、可破坏物、角色移动、相机碰撞和布娃娃的实际做法(GEA4 卷 II 14.5–14.6)。