外观
理论与业界方案
约 17070 字大约 57 分钟
2026-10-14
检索日期:2026-10-10。用途:为 Unreal Chaos Destruction 演示教学页提供理论支撑。 语言约定:中文叙述,术语保留英文;每条结论后附来源 URL 或来源编号
[Sx](编号见文末「来源索引」)。
0. 阅读须知
0.1 证据等级标签
| 标签 | 含义 |
|---|---|
| [原文] | 我下载并读到了论文 PDF / 源码 / 幻灯片全文 |
| [官方文档] | 读到了厂商官方文档页面正文 |
| [摘要] | 只拿到摘要或搜索引擎对该页的转述,没有读到正文(页面 403 / 需 JS / 需登录) |
| [二手] | 第三方博客、论坛、媒体转述 |
| [推导] | 由已验证事实推导 / 我自己写的伪代码,已标明 |
| 未验证 | 找不到可靠来源,或来源之间矛盾;使用时请自行复核 |
0.2 需要先纠正的几处
- Müller 2013 的作者:Real Time Dynamic Fracture with Volumetric Approximate Convex Decompositions 的作者是 Matthias Müller、Nuttapong Chentanez、Tae-Yong Kim(均为 NVIDIA),ACM TOG 32(4),Article 115,SIGGRAPH 2013。没有 McCloskey / Heidelberger。[原文] https://matthias-research.github.io/pages/publications/fractureSG2013.pdf ;DOI https://doi.org/10.1145/2461912.2461934
- Frostbite "Destruction Masking" 不是 GDC 而是 SIGGRAPH 2010 "Advances in Real-time Rendering" 课程(Robert Kihl, DICE)。[官方/幻灯片] https://www.ea.com/frostbite/news/destruction-masking-in-frostbite-2-using-volume-distance-fields
- Digital Dragons:未检索到任何关于 destruction performance budget / debris pooling 的 Digital Dragons 演讲(见 §4.2 与 §9)。我用 APEX LOD 预算公式、Epic 官方优化页、Rainbow Six Siege 渲染幻灯片等替代,并明确标注。
- Fortune 算法引用:首次发表于 Proc. 2nd Annual Symposium on Computational Geometry (SoCG), 1986, pp. 313–322;Algorithmica 2 (1987) 为期刊版(期刊版信息来自记忆,未验证)。https://en.wikipedia.org/wiki/Fortune%27s_algorithm
- 我读到的 "Real-Time Simulation of Deformation and Fracture of Stiff Materials"(Müller, McMillan, Dorsey, Jagnow, 2001,MIT)是 2013 论文的前身思路(撞击时静态 FEM 求应力 + 碎片按刚体),不是 2013 论文。https://matthias-research.github.io/pages/publications/fracture.pdf
0.3 无法直接读取正文的关键页面(对应结论均降级为 [摘要]/[二手])
- Epic Chaos Destruction Optimization(需 JS / Cloudflare):https://dev.epicgames.com/documentation/en-us/unreal-engine/chaos-destruction-optimization
- Epic Fracture Mode / Fracturing Geometry Collections User Guide(403 / 仅目录)。
- GDC Vault 全部演讲(需会员;只有摘要):Chaos Destruction 2025、Control 2020、LEGO Fortnite 2024。
- Teardown 的 GPC 演讲、Software Engineering Daily 逐字稿(404/403)。
- Houdini RBD Configure 页正文过长,只读到前 10 万字符。
1. TL;DR(最关键结论)
- 预破碎 + 层级 + 连接图 + 阈值 是业界主流:UE Chaos(Geometry Collection + Clustering + Connection Graph + Damage Threshold)、APEX(chunk depth + support graph)、Blast(chunk + bond + support graph)结构同构。
- Voronoi 破碎的核心就是"每个站点 = 若干半平面的交集",Blender Cell Fracture 的源码就是"按距离排序 + 半平面裁剪 + 提前退出(2×最远顶点距离)",非常适合 JS Canvas 演示。
- "悬空碎块"= 连通分量问题:Müller 2013 用 flood fill(并按 |d| 排序匹配共面),Bullet FractureDemo 直接用
btUnionFind,Blast 在 support graph 上做 island detection。 - 动态破碎可行但受限:Müller 2013 在 GTX 680 上单次破碎 < 50 ms,Arena 20k 碎片 > 30 fps;代价是必须预做 VACD 且只做几何(非应力)破碎。
- 性能杠杆在"同时醒着的刚体数"而不是碎片总数:Remove on Sleep/Break、throttle CVar、APEX LOD benefit/cost 预算、Müller 的"小于阈值不创建真刚体而用粒子"都是这条思路。
- PBD/XPBD 的堆叠稳定性来自 substep 而不是更多 iteration(Small Steps,Fig.9 胶囊堆);Chaos 官方工程师明确说 Chaos "based on PBD"。
2. 主题一:破碎几何生成
2.1 Voronoi 图基础
定义。 站点集 P={p1..pn},站点 pi 的 Voronoi cell:
Vi={x∣∥x−pi∥≤∥x−pj∥, ∀j=i}
等价于 n−1 个半平面的交集;pi,pj 的垂直平分线对应的半平面:
2(pj−pi)⋅x ≤ ∥pj∥2−∥pi∥2
每个 cell 是凸多边形/凸多面体——这正是 Müller 2013 选 Voronoi 作为 fracture pattern 的原因(“all cells are convex”)[原文]。
与 Delaunay 的对偶。 Delaunay 三角剖分是 Voronoi 的对偶图;Delaunay 三角形外接圆圆心 = Voronoi 顶点;Delaunay 边 ↔ Voronoi 边。空圆性质(incircle 判定,a,b,c 逆时针,d 在圆内则 > 0):
detax−dxbx−dxcx−dxay−dyby−dycy−dy(ax−dx)2+(ay−dy)2(bx−dx)2+(by−dy)2(cx−dx)2+(cy−dy)2>0
来源:对偶关系与 Bowyer–Watson 步骤 [二手/百科] https://en.wikipedia.org/wiki/Bowyer%E2%80%93Watson_algorithm ;行列式式为标准定义 [推导]。
三种常见算法对比
| 算法 | 思路 | 复杂度 | 优点 | 缺点 | 来源 |
|---|---|---|---|---|---|
| 半平面裁剪(cell-by-cell) | 每个站点从包围盒出发,依次被其它站点的平分线裁剪 | 朴素 O(n2) 次裁剪;加"按距离排序+提前退出"后每站点只处理近邻;排序使整体 O(n2logn);配空间网格能否降到近线性未验证(我未查到文献)[推导] | 实现最短,2D/3D 通用,天然得到 cell 多边形,适合"每个 cell 一块碎片" | 全局 O(n2) 级,n 大时慢 | Blender 源码 [原文](见 §2.3) |
| Bowyer–Watson | 逐点插入:删除外接圆含新点的三角形,空洞边与新点连成新三角形,最后去掉超级三角形 | 朴素 O(n2);用连通性定位平均 O(nlogn);退化情形最坏 O(n2) | 增量、可动画演示"插入一个点";任意维度 | 要处理超级三角形、共圆退化;需自己做对偶得到 Voronoi | https://en.wikipedia.org/wiki/Bowyer%E2%80%93Watson_algorithm ;Bowyer 与 Watson 于 1981 年同期刊独立发表(The Computer Journal) |
| Fortune sweepline | 扫描线 + beach line(抛物线弧);事件:site event / circle event | O(nlogn) 时间,O(n) 空间 | 渐近最优、确定性 | 实现复杂;浮点精度问题;不适合做教学最小实现 | https://en.wikipedia.org/wiki/Fortune%27s_algorithm ;https://codeforces.com/blog/entry/85638 [二手] |
| Voro++(3D cell-based) | 逐 cell 直接构造,不先建全局 Delaunay | 以单 cell 为单位,适合大规模粒子 | 3D 成熟库,可带 radical(加权)Voronoi、邻接表 | 面向科学计算 | Rycroft 2009, Chaos 19, 041111 [摘要] https://pubs.aip.org/aip/cha/article/19/4/041111/135670/VORO-A-three-dimensional-Voronoi-cell-library-in-C |
提前退出的正确性(推导):设当前 cell 的最远顶点距站点 R。若另一站点 pj 满足 ∥pj−pi∥>2R,则其平分线到 pi 的距离 >R,必然不与当前 cell 相交,可停止处理后续(更远的)站点。Blender 源码里用的正是
distance_max = 2 × max|vertex|(见 §2.3),与此推导一致。
2.2 点(site)分布
| 分布 | 做法 | 效果 | 与 UE Fracture Mode 对应 | 来源 |
|---|---|---|---|---|
| 均匀随机 | 在包围盒内均匀采样 | 大小差异大,有极小碎片 | Uniform:给 Voronoi sites 的 min/max 数量,在范围内随机取值 | UE 文档(搜索摘要)https://dev.epicgames.com/documentation/en-us/unreal-engine/fracturing-geometry-collections-user-guide [摘要] |
| Jittered grid | 规则网格 + 抖动 | 大小更均匀 | —— | Unity 开源实现 README https://github.com/sinanata/unity-mesh-fracture [二手] |
| Poisson-disk(蓝噪声) | Bridson:背景网格 cell 边长 r/n,active list,在环 [r,2r] 内试 k 个候选,O(N) | 最小间距保证,碎片尺寸均匀自然 | 无直接对应(可用 Lloyd 近似) | Bridson SIGGRAPH 2007 Sketch https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf [摘要];k 常取 30 为未验证记忆 |
| Lloyd 松弛 | 反复:求 Voronoi → 站点移到 cell 质心 | 趋向 Centroidal Voronoi Tessellation(CVT),蓝噪声特性;收敛慢,实际迭代几次即可 | —— | Du/Faber/Gunzburger 1999 SIAM Review https://people.sc.fsu.edu/~mgunzburger/files_papers/gunzburger-cvt-siamreview.pdf [摘要];https://en.wikipedia.org/wiki/Lloyd%27s_algorithm |
| 聚簇(Clustered) | 先选若干簇心,再在簇心周围采样 | 局部"岛屿"状密集碎片 + 大块 | Clustered:Uniform 分布均匀,Cluster 把 sites 聚成近距离的岛 | UE 文档 [摘要] |
| 径向(Radial) | 站点沿以冲击点为心的环/射线分布 | 蛛网状、放射状裂纹 | Radial:站点从中心点向外辐射,Gizmo 设中心 | UE 文档 [摘要] |
| 冲击点密度衰减 | 距冲击点越近站点越密 | 局部细碎、远处大块;Müller 2013 的默认做法 | 无直接对应;Chaos 更接近"预破碎 + 场(Field)按距离衰减 strain" | Müller 2013:"Voronoi decomposition with decreasing node density away from the origin"(破碎图案原点 = 冲击点)[原文] |
冲击密度的一个可实现公式(我自己的推导,已在 Node 中数值验证):在半径 R 圆内,取 r=Ruγ(u∼U(0,1),角度均匀),则面密度 ρ(r)∝r1/γ−2。
- γ=0.5 ⇒ 面密度恒定(等价于 r=Ru);
- γ>0.5 ⇒ 越靠近冲击点越密(γ=1 ⇒ ρ∝1/r,γ=1.5 ⇒ ρ∝r−4/3)。
- 实测 20000 点、4 环:γ=0.5 四环密度约 6300/6390/6285/6421(平坦);γ=1 为 25750/8369/5117/3631;γ=1.5 为 40153/7993/3991/2539。
2.3 Boolean / CSG 切割与内部面(internal faces)
Blender Cell Fracture 的真实实现 [原文:我下载并读了源码] 源码:https://github.com/blender/blender-addons/blob/main/object_fracture_cell/fracture_cell_calc.py 与 fracture_cell_setup.py(该仓库已归档,内容经 raw.githubusercontent.com 取得)。流程:
- 点来源:物体顶点 / 粒子系统 / 随机 / 画笔标注(
_points_from_object);可选source_noise:每个点加random_unit_vector() × (noise × 包围盒对角线/2 × random())。 - 每个站点的 cell(
points_as_bmesh_cells):从包围盒 6 个平面出发(注释说明"用凸包也行但包围盒最快");把其它站点按到当前站点的距离排序;依次加入平分面plane[3] = -nlength/2 + margin_cell;每加一个平面调用mathutils.geometry.points_in_planes(planes)求顶点,丢弃冗余平面;更新distance_max = 2 × max(|vertex|);若下一个站点距离 >distance_max则break(即 §2.1 的提前退出)。 - cell → 凸包 mesh:顶点加 ±0.0005 的微小噪声(源码注释 "WORKAROUND FOR CONVEX HULL BUG/LIMIT"),
remove_doubles(0.005),bmesh.ops.convex_hull,dissolve_limit(angle_limit=0.001)。 - 与原网格求交(
cell_fracture_boolean):给每个 cell 加Booleanmodifier(INTERSECT,对象=原 mesh);内部面标记技巧:求交前把 cell 的所有多边形设hide=True、原 mesh 设hide=False,求交后仍 hidden 的面 = 由 cell 平面产生的内部切面,未 hidden 的 = 原外表面。cell_fracture_interior_handle据此生成Interior顶点组或把内外交界边设成 sharp。 use_island_split:对结果做mesh.separate(type='LOOSE'),即非连通部分分开(与 §3.2 的 island 检测同一思想)。
其它实现对照
- Müller 2013:把可视 mesh 与每个凸体 cell 做 clip,新 cell 完全在可视 mesh 内则成为 internal convex;"mesh clipping 不仅裁剪,还在 cell 面上生成几何使 mesh 水密";关键稳健性经验是用一般(可能非凸)多边形面而非三角形,否则切几次就产生大量病态三角形(其 Fig.9)[原文]。
- Houdini Voronoi Fracture:两个输入(被破碎几何 + 每个点定义一个 cell);
Create Interior Surfaces控制是否生成内部面(开放曲面或要薄壳时通常关闭);输出Interior Group/Exterior Group,Merge with Existing Groups可在多级破碎中累计内部面;并输出 constraint geometry(每片一个点 + 邻接线),供 RBD 约束网络;Cut Plane Offset打开缝隙。[官方文档] https://www.sidefx.com/docs/houdini/nodes/sop/voronoifracture.html - UE:CEDEC 2022(Epic Japan)幻灯片:材质槽中偶数槽用于内部面,默认平铺 UV;
AutoUV工具用盒投影 + "到外壳的烘焙距离"生成内部 UV。[官方培训材料] https://www.docswell.com/s/EpicGamesJapan/5V3PXZ-UE_CEDEC2022_ChaosDestruction ;Dataflow 节点VoronoiFracture/SliceCutter的SplitIslands(默认 True)、Grout、ChanceToFracture等见 §2.5。[官方文档] https://dev.epicgames.com/documentation/unreal-engine/node-reference/Dataflow/VoronoiFracture - Unity 开源 Voronoi 碎裂:对每个种子,用其它种子的平分面半平面裁剪整个三角形列表,切口交点按世界坐标去重并串成 cap 多边形(凸,故可 fan 三角化),碎片 2 个 submesh(外表面 + 内部 cap,flat normal);作者称"对碎片数 O(n²),纯 CPU"。[二手] https://github.com/sinanata/unity-mesh-fracture
2.4 Slice / Planar / Brick
- UE:Planar(沿平面切,可加噪声)、Slice(X/Y/Z 方向网格切 + 随机角度/偏移)、Brick(按图案切,细节在搜索摘要中被截断,未验证)。[摘要] https://dev.epicgames.com/documentation/en-us/unreal-engine/fracturing-geometry-collections-user-guide
SliceCutter参数(逐项读自官方节点参考):SlicesX(默认3) /SlicesY(3) /SlicesZ(1) /SliceAngleVariation(度, 0) /SliceOffsetVariation(cm, 0) /RandomSeed/ChanceToFracture(1.0) /Grout(0)。[官方文档] https://dev.epicgames.com/documentation/unreal-engine/node-reference/Dataflow/SliceCutter- 数学上 Slice/Planar = 用有限个平面递归二分凸多边形/多面体(半空间裁剪的另一种用法),比 Voronoi 更接近"砖/木板/石板"。
2.5 噪声边缘(noise / fractal fracture surface)
- UE Dataflow 的
VoronoiFracture/SliceCutter噪声参数(官方节点参考):Amplitude(cm,Perlin 位移幅度,0=无噪声)、Frequency(默认 0.1,小值更平滑)、Persistence(0.5,每个 octave 后幅度缩放)、Lacunarity(2.0,频率缩放)、OctaveNumber(4;1–2 像缓丘,>4 变崎岖)、PointSpacing(默认 10cm,切面上带噪声顶点的间距;越大三角形越少、噪声细节越少)、CollisionSampleSpacing(50cm,仅AddSamplesForCollision时补碰撞用顶点)。[官方文档] https://dev.epicgames.com/documentation/unreal-engine/node-reference/Dataflow/VoronoiFracture - 即 UE 的"噪声边缘" = 先把切面重新细分到
PointSpacing,再用分形 Perlin noise(fBm: Σ persistence^k · noise(lacunarity^k · x))沿法线位移。 - 理论背景:真实金属断口是分形曲面,分形维数 D 与韧性相关(Mandelbrot, Passoja, Paullay, Nature 308, 721–722, 1984)。[摘要] https://www.nature.com/articles/308721a0 。把它说成"游戏用 fBm 的物理依据"是我的解读,游戏引擎文档并未引用该论文(未验证)。
- 2D 演示可用 midpoint displacement:每层细分,位移幅度乘
rough(=2−H);见 §7 A6。
2.6 Müller et al. 2013(SIGGRAPH)详解 [原文]
PDF 全文:https://matthias-research.github.io/pages/publications/fractureSG2013.pdf
问题:预破碎(pre-fracture)廉价,但(a)破碎图案与撞击点不对齐,(b)层级数固定、美术成本高。目标:不限数量的动态碎裂,足够快到可用于游戏。
核心表示 — Compound / VACD
- 可视 mesh → 一组互不重叠的凸体(convex)组成的 compound。性质:(a) 由凸体组成;(b) 互不重叠;(c) 两凸体物理相连当且仅当各有一个面共面、法线相反且部分重叠。
- Fracture pattern = 整个空间的凸分解(用大 AABB 的分解),cells 都凸,撞击点位于原点;示例中玻璃用程序生成的蛛网,其它场景用节点密度随离原点距离递减的 Voronoi。
运行时算法(5 步,Fig.2)
- Alignment:平移 pattern 使原点对齐撞击点(可加绕原点的任意变换,cells 保持凸)。
- Intersection:每个 cell 与每个 convex 求交——"把 convex 逐个对 cell 的平面裁剪,而不是反过来";用任意凸-凸碰撞加速结构做粗筛;新顶点属性(法线)线性插值。
- Welding:若某 cell 被新凸体完全覆盖(比较 cell 预计算体积与新凸体体积之和;若 pattern 有非刚体变换,体积乘 det),用一个等形凸体替换。
- Compound formation:同一 cell(或同一"颜色"的多个 cell,以支持非凸片)里的凸体合并成新 compound。
- Island detection:新 compound 可能含互不相连的岛 → 拆开。
Island detection 的关键优化:对 compound 全部凸体的所有面建表,键为平面方程 n⋅x+d=0 的 ∣d∣,排序后单遍扫描找 ∣d∣ 相同(容差内)的候选配对,再检验性质 (c),用共面投影后的平面凸-凸相交算重叠比例 s∈[0,1];然后在邻接结构上 flood fill 求连通分量。重叠比 s 还用于玻璃渲染(仅渲染 s<1 的面,使撞击半径外的玻璃看似完好)。
Partial fracture:把 cells 分为撞击球内的 near cells 与 far cells;撞击球外完全不涉及的 convex 不切,直接并入同一 compound。作用:局部破坏、成本只与"被撞击物体大小"有关而非整个场景。
VACD 预处理(Section 4):用户在 mesh 上放 Voronoi nodes → Voronoi 分解包围盒 → mesh 对 cells 裁剪 → 每个 cell 内做 island detection → 用 approximate convex hull (ACH) 拟合;双精度预处理、运行时单精度;10–100k 三角形的 mesh "只需不到一秒"完成分解,可交互调点(龙模型 80k 三角形放 12 个节点)。
ACH 算法(Algorithm 1,体积最大化增量凸包)——伪代码见 §7 A15;k-DOP 方向:AABB(3 轴)、14-dop(+4 条体对角)、26-dop(+12 条棱方向)。
初始凸体重叠(ghost convex):重叠对生成"负体积、法线朝内、不渲染"的 ghost convex,保证体积求和与连通性(与共面性质兼容)。
数据(i7 3.07GHz + GTX 680)
- Arena:1M 顶点 / 500k 面,破碎至 20k 片 / 32k convex 时仍 >30 fps(含刚体、粉尘与渲染);全程单次破碎 < 50 ms;小/中型物体 < 10 ms。
- 帧时间构成:渲染约 15%、刚体 45%、粉尘 40%。
- bunny(6k 顶点,凸包 645 顶点):用 4 / 6 个顶点的 ACH 体积≈14-dop / 26-dop;20 个顶点的 ACH 体积仅比真凸包大 5%。
- 刚体用 Tonge 等 2012 的 GPU 并行求解器;小于阈值尺寸不创建真刚体,改为粒子:按暴露面均匀采样,渲染时用实例化的预定义小 mesh,物理为带高角阻尼的球,防止地面滚动。
局限(作者自述):纯几何/pattern 式,物理真实性不及应力法;本文碰撞触发的破碎不会"自重塌";pattern 与撞击强度无关(可预设多套 pattern 按力度选);结构不会因自重断裂(未来做 connectivity graph 上的简化应力分析)。
2.7 为什么 Voronoi 不够"真实":Fracture Modes
Sellán 等 Breaking Good: Fracture Modes for Realtime Destruction(ACM TOG 42(1), 2023;arXiv 2111.05249):基于几何启发的 prefracture(含 Voronoi)会产生"凸、不真实、容易辨认"的碎片,与物体弹性响应/结构弱点无关;他们用稀疏化特征值问题求"最低能量断裂模态",运行时把撞击投影到模态空间,无在线裂纹传播。[摘要] https://arxiv.org/abs/2111.05249 ;https://dl.acm.org/doi/10.1145/3549540 。教学页提示:可把"Voronoi=凸碎片 + 无视结构弱点"作为 Voronoi 方案的诚实局限。
2.8 各引擎/工具的破碎生成对照
| 工具 | 破碎方式 | 关键概念 | 来源 |
|---|---|---|---|
| UE Chaos Fracture Mode | Uniform / Clustered / Radial(均为 Voronoi),Planar / Slice / Brick,噪声,Dataflow 版 | Geometry Collection、Fracture Level、Auto Cluster | §2.2–2.5 |
| Houdini Voronoi Fracture 3.0 | 点驱动 Voronoi + 内部面 + constraint 输出;建议在 IsoOffset 密度体里 Scatter 以控制密度;先 PolyReduce(复杂度对速度影响最大) | Interior/Exterior Group | https://www.sidefx.com/docs/houdini/nodes/sop/voronoifracture.html |
| Blender Cell Fracture | 见 §2.3 | margin、recursion、source_noise、Interior vgroup | 源码 [原文] |
| NVIDIA APEX Destruction | DestructibleAssetAuthoring 把 chunk[0] 用 Voronoi 切为 chunk[1..];FractureToolsAPI 可按 chunkIndex 只在该 chunk 体积内放 Voronoi 站点;Slice 与 cutout 破碎;Voronoi 模式从 APEX Destruction 1.2 引入 | chunk hierarchy、depth | https://archive.docs.nvidia.com/gameworks/content/gameworkslibrary/physx/apexsdk/APEX_Destruction/Destruction_Module.html [官方文档];FractureToolsAPI 类参考 [摘要] |
| NVIDIA Blast | 预破碎层级 chunk;minConvexSize 等 | chunk / bond / support graph | https://docs.omniverse.nvidia.com/kit/docs/blast-sdk/latest/docs/api/introduction.html [官方文档] |
| Bullet | FractureDemo(compound child + 约束/胶水)与 VoronoiFractureDemo(Alain Ducharme 2011,暴力 Voronoi,仅适用凸源 mesh) | btFractureBody、btFractureDynamicsWorld | https://github.com/bulletphysics/bullet3/tree/master/examples/FractureDemo [原文,见 §3.2] |
| Havok Destruction | GDC 2008 发布;基于 Havok Physics 的刚体破坏中间件;已停产;公开技术文档缺失 | —— | https://www.gamedeveloper.com/programming/havok-announces-havok-cloth-havok-destruction [二手];内部实现未验证 |
3. 主题二:层级与连接图
3.1 Hierarchical clustering(簇层级)
- UE:Geometry Collection 中每次 fracture 产生一个 Level;"Level 0 / Root → Level 1 → Level 2…";Clustering 用于"分阶段拆分、减少刚体数、控制破坏方式"。内部 clustering model 把相连元素的质量与惯量合并为一个更大的刚体,运行时再动态拆开。[官方文档] https://dev.epicgames.com/documentation/en-us/unreal-engine/destruction-overview ;CEDEC 幻灯片示例:Level 1 用 Uniform Voronoi 24 个站点,再选 Level 1 以 400 个站点二次破碎并勾选
Group Fracture;或先 Voronoi 500,再Auto Cluster(按指定簇数聚类);Magnet(按邻接环数分组)、Flatten(压平中间层)、Cluster/Uncluster/Merge/Level Up等。[官方培训材料] https://www.docswell.com/s/EpicGamesJapan/5V3PXZ-UE_CEDEC2022_ChaosDestruction ;[官方文档] https://dev.epicgames.com/documentation/en-us/unreal-engine/cluster-geometry-collections-user-guide-in-unreal-engine - APEX:每个 chunk 存 depth、parent index、first child、children 数;层级按 BFS 顺序重排;
minimumFractureDepth、debrisDepth(该深度及以下视为碎屑)、essentialDepth(LOD 不会清除)、damageDepthLimit(伤害向下传播的层数)、runtimeFracture.depthLimit(默认 2)。[官方文档+摘要] APEX 文档页 + DestructibleParameters 类参考 https://archive.docs.nvidia.com/gameworks/content/gameworkslibrary/physx/apexsdk/_static/build_framework/structnvidia_1_1apex_1_1_destructible_parameters.html - Blast:Asset 含一个或多个 chunk 层级树;"破碎一个 chunk 产生其子 chunk";任何层级的 chunk 都可标为 support chunk,support chunks 必须对 root 空间形成 exact cover(无重复体积)。[官方文档] https://docs.omniverse.nvidia.com/kit/docs/blast-sdk/latest/docs/api/introduction.html
- Müller 2013:不是层级预破碎,而是"动态层级"——任何 compound 可被再次应用 pattern("不限数量的碎片")。[原文]
3.2 Connection graph 与连通分量(union-find)
UE Chaos:Connection Graph 在模拟开始时由"每块碎片的最近邻"建立;图中每条 link 在 cluster 内是刚性约束,带初始 strain 值;模拟中持续检查,当碰撞约束或 Field 求值产生的冲量超过该 link 的上限时 link 断开;断开的碎块可因 Physics Fields 与碰撞与结构分离。[官方文档] https://dev.epicgames.com/documentation/en-us/unreal-engine/destruction-overview
Cluster Connection Type是 Geometry Collection 的可编辑属性(Python API 可见cluster_connection_type: ClusterConnectionTypeEnum)。[摘要] https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/class/GeometryCollection?application_version=4.27- 枚举值:Houdini–Unreal 文档(约 UE 4.26 时代)列出整数映射
0=Point Implicit、1=MinimalSpanningSubsetDelaunayTriangulation、3=PointImplicitAugmentedMinimalDelaunay。[二手/摘要] https://www.sidefx.com/docs/houdini/unreal/chaosintegration.html 。每个选项的具体语义(点隐式/Delaunay 的细节)Epic 文档没有逐条解释,我的理解为推断,未验证。 - 调试可视化:
p.Chaos.DebugDraw.Enabled 1与p.Chaos.Solver.DebugDraw.Cluster.Constraints 1(论坛帖,二手)https://forums.unrealengine.com/t/geometry-collections-cluster-unions-and-connectivity-graph/2075192 - 世界支撑:
World Support(部分碎片设为 Kinematic)可减少 Anchor Field 的需要;Anchor Field 必须在 Construction Script 中用Add Field Command施加,因为 connection graph 必须在模拟开始前存在;Apply Physics Field无效。[官方文档] https://dev.epicgames.com/documentation/en-us/unreal-engine/physics-in-unreal-engine ;https://dev.epicgames.com/documentation/en-us/unreal-engine/chaos-destruction-overview?application_version=4.27 - UE 内部是否用 union-find / BFS 做 island 检测:未验证(Chaos 源码未公开可读)。
其它实现的连接图 + 连通分量(可作为教学类比)
| 实现 | 连接图节点/边 | 判断悬空 | 来源 |
|---|---|---|---|
| Müller 2013 | 凸体;边 = 共面反向重叠的面 | flood fill;用平面常数 abs(d) 排序加速邻接发现 | [原文] |
| Bullet FractureDemo | compound 子形状;recomputeConnectivity 对每对子形状做 contact test,距离 ≤ margin(0.05) 视为相连 | btUnionFind:unite(tag0,tag1) → sortIslands() → 每个 island 建新 btFractureBody,并按旧速度/冲量重新施加 | 源码 [原文] https://github.com/bulletphysics/bullet3/blob/master/examples/FractureDemo/btFractureDynamicsWorld.cpp |
| APEX | support chunk;"只要存在连到 support chunk 的路径就保持固定;断连的 island 变 dynamic 并脱落";还支持跨 actor 的 inter-actor support | 图连通性 | [官方文档] APEX Destruction_Module 页 |
| Blast | support graph(support chunk=节点,bond=边);bond 带 centroid / normal / area;也可连接到"世界" | 伤害处理完后,健康 ≤ 0 的 bond 视为断开,对 support graph 做 island detection,每个新 island 成为一个新 actor | [官方文档] Blast introduction |
| Houdini | Connect Adjacent Pieces、Glue Cluster(给 glue constraint 网络加强度) | 约束断裂后由 solver 管理 | RBD Configure 页(只读到前段)[官方文档] https://www.sidefx.com/docs/houdini/nodes/sop/rbdconfigure.html |
| Teardown(体素) | 体素邻接 | 一位社区开发者的复刻写道:体素被摧毁后需要找出物体是否分离,用 flood fill 并分摊到多帧,代价是分裂有延迟 | [二手] https://milanbonten.github.io/voxel-physics-engine(搜索摘要;该页直接抓取返回 404,未验证) |
Union-Find 复杂度:按秩合并 + 路径压缩,m 次操作 Θ(mα(n)),α 为反阿克曼函数(实际 ≤4);无优化最坏树高 O(n)。https://en.wikipedia.org/wiki/Disjoint-set_data_structure 。
3.3 Strain / Damage 阈值模型
UE Chaos(官方文档)
- Damage Threshold 按 cluster level 设置(数组,每个 Level 一个值);官方 Dataflow 教程示例为
5000, 500, 50。[摘要] https://dev.epicgames.com/documentation/unreal-engine/dataflow-for-destruction-quickstart - 语义:外部 strain 小于阈值则不破;示例:阈值 200,000 在 400,000 strain 下破,阈值 500,000 不破。[官方文档] https://dev.epicgames.com/documentation/en-us/unreal-engine/chaos-fields-user-guide-in-unreal-engine
- External Strain:参数
Strain Magnitude、Strain Falloff Type(Linear / Inverse / Square / Logarithmic)、Strain Falloff Min Max、Num Strain Hits(场每次命中激活一次,多次命中可一次破多层)。Epic 建议性能上避免 external strain falloff。[官方文档] 同上。 - CEDEC 补充:External strain "破开任何阈值 ≤ 其值的 cluster 的一层,不累积";
Internal strain是从阈值中减去(使之更脆),例如Decay Amount=100000、Decay Delay=1s、OnTick 每秒降 100000。bUseSizeSpecificDamageThreshold+ 资产Size Specific Data(MaxSizecm³ →Damage Threshold)可按碎片大小设阈值;阈值是组件上的而非资产上的。[官方培训材料/官方文档] Max Cluster Level:限制最多破到哪一级。[官方文档] https://dev.epicgames.com/documentation/en-us/unreal-engine/chaos-destruction-overview?application_version=4.27- 因此 UE 的外部 strain 是"瞬时判定",Internal strain 才带"累积/衰减"语义——教学页可用两种模型对比(见 §7 A10)。
Blast(累积型):伤害 = bond/chunk 的 health 损耗;由用户的 material function(可依赖 bond 法线、面积、到冲击点距离)决定每条 bond 的损失;处理完所有伤害事件后 health ≤ 0 的 bond 断开。[官方文档] Blast introduction。Stress Solver(NvBlastExtStress):在 bond 图上迭代扩散力,tension / compression / shear 分别设限,重力需至少一个质量 0 的节点作锚;每帧 addForce/addGravity → update() → 若 getOverstressedBondCount()>0 则 generateFractureCommands();耗时与 maxSolverIterationsPerFrame 线性相关;graphReductionLevel 可简化图(实验性)。[官方文档] https://docs.omniverse.nvidia.com/kit/docs/blast-sdk/latest/docs/api/extensions/ext_stress.html 。Blast 5.1.0 引入"integrated stress damage":把冲量/运动转成 bond 伤害,使用 elastic 与 fatal 两档应力极限。[摘要] https://github.com/NVIDIAGameWorks/Blast/releases/tag/v5.1.0
APEX:参数控制"多大伤害使 chunk 破碎、冲击是否造成伤害、是否先变形再断";伤害 API 分 direct damage 与 impact damage;半径伤害同时决定局部运行时破碎的半径。[官方文档]
Bullet FractureDemo(极简参考):同一 manifold 上所有接触点的 m_appliedImpulse 求和 totalImpact,低于 40 的忽略(注释:"否则静止接触久了会把物体弄碎");注释还写明更好的做法:把断裂阈值存为 child shape 之间的连接信息,用冲量和偏移算 "impact value",沿连接图传播削弱并在超阈值时断开。[原文] 同上源码。
3.4 Stress-based fracture(应力法)
O'Brien & Hodgins 1999(SIGGRAPH 99, Graphical Modeling and Animation of Brittle Fracture)[原文] PDF:http://graphics.berkeley.edu/papers/Obrien-GMA-1999-08/Obrien-GMA-1999-08.pdf
- 基于线弹性断裂力学(LEFM);四面体 FEM 的单元应力张量 σ 特征分解 σ=∑iνin^in^iT(νi>0 为拉应力):
- 拉分量 σ+=∑imax(0,νi)m(n^i),压分量 σ−=∑imin(0,νi)m(n^i),其中 m(v)=vvT/∣v∣(v=0)。
- 对每个节点把相邻单元作用力拆成拉/压分量并求"未平衡"部分,形成separation tensor ς;它"不响应会产生刚体平动的未平衡作用",对材料与世界坐标变换不变。(论文式 (32):ς=21(−m(f+)+∑m(fi+)+m(f−)−∑m(fi−))——PDF 文本抽取符号有乱码,上式的各项符号未逐项核对,使用前请对照原文。)
- 判据:设 ς 的最大正特征值为 ν+;若 ν+>τ(材料 toughness),该节点开裂;裂面法向 = 对应特征向量 n^+,裂面过该节点。多个特征值超阈值时先处理最大者,重划分网格后重算。
- 局部重划分:复制节点为 n+/n−;未被裂面相交的单元按所在侧重新分配;被相交的四面体通常拆成 3 个四面体(一侧 1 个、另一侧 2 个);邻接单元也要拆以保持一致;使用阈值 5 mm、0.1 rad 做 snap。
- 裂纹方向:拉力使裂纹沿垂直于最大拉伸载荷方向扩展,压缩载荷倾向阻止垂直于它的裂纹。
Parker & O'Brien 2009(SCA 2009 最佳论文;Star Wars: The Force Unleashed)[原文] PDF(http):http://graphics.berkeley.edu/papers/Parker-RTD-2009-08/Parker-RTD-2009-08.pdf
- corotational 线性四面体 FEM;前向:F=∂x/∂u=DxDu−1,极分解 F=QA,corotational 应变 ϵ~=21(F~+F~T)−I,F~=QTF;线性各向同性本构 σ=λTr(ϵ~)I+2μϵ~。
- 塑性(加法模型):∥ϵE∥>ϵy 时 ϵP+=c∥ϵE∥∥ϵE∥−ϵyϵE(c 蠕变率),∥ϵP∥ 夹到最大塑性应变。
- 时间积分:线性化 backward Euler,(M+ΔtC+Δt2K)v+=Δtf+Mv−ΔtK(x−u),共轭梯度,相对误差 0.001,迭代上限作为"材料参数"暴露给设计师;元素体积 < 参考的 6% 时改用 QR 分解防止反转。
- 断裂(简化自 O'Brien):用 separation tensor 的特征值判断,λ> toughness 时按特征向量法平面复制节点;禁用了单元切分(防止四面体数量不可预测地增长);随机顺序检查节点、不做 residual propagation、每步断裂事件设上限;若断裂会产生少于 3 个面连通四面体的连通分量则抑制(避免"单四面体碎片");仅沿四面体边界裂开会显得"人工积木感"→ 用 splinters(嵌入的高分辨率渲染碎片,每个 splinter 与含其质心的四面体关联;断裂后重新关联)掩盖。
- 工程:islands 状态 live / asleep / kinematic,islands 初始 asleep,被撞击或节点受力超阈值才唤醒;节点速度低于阈值持续多步则入睡;粗相用 sweep and prune;"fast 物体"(每步移动 > 包围盒对角线 1/6)线性插值子步;碰撞响应只用阻尼力避免抖动。
- 作者的比较:纯准静态方法"尸体般"缺乏动态——"裂前存储弹性能量,断裂后转为动能",动态传播更有活力。
其它:Müller et al. 2001(MIT)撞击时做静态 FEM 求应力,用应力张量朝向求裂面,使裂纹速度/尺寸与网格粒度无关 [原文] https://matthias-research.github.io/pages/publications/fracture.pdf ;Su/Schroeder/Fedkiw 2009 Energy stability and fracture for frame rate rigid body simulations(SCA'09)提出基于碰撞的预评分(prescoring)快速刚体断裂,并给出能量稳定的接触处理 [摘要] https://dl.acm.org/doi/10.1145/1599470.1599491 。
3.5 Peridynamics 与 position-based(简)
- Peridynamics(Silling 2000, J. Mech. Phys. Solids 48(1):175–209):用积分(而非空间偏导)计算质点受力,天然允许不连续(裂纹)出现,适合"裂纹位置事先未知";bond-based 版本把邻域(horizon)内的点对作为 bond,bond 拉伸超限即断。[摘要] https://www.sciencedirect.com/science/article/abs/pii/S0022509699000290 。它在商业游戏实时破碎中的应用我没有找到可靠来源(未验证),建议教学页仅作"另一类连续体视角"一句话带过。
- Position-based:PBD/XPBD 是游戏刚体/布料/软体的主流求解框架,不是断裂判据;断裂在 PBD 里通常实现为"约束违反/拉格朗日乘子超限则移除约束"(通用做法,未找到专门文献,未验证)。其对堆叠/碎片堆的稳定性见 §5.4。
3.6 冲量/能量判据
- 冲量判据(工程上最常见):碰撞约束产生的冲量(或 manifold 内 applied impulse 之和)> link 极限 → 断开。UE 文档明确写 "impulse from collision constraints or field evaluations exceeds the link's limit";Bullet FractureDemo 以 40 为阈值;Blast 5.1 把冲量转 bond 伤害。均已在 §3.3 引用。
- 能量判据:Su/Schroeder/Fedkiw 2009 的能量稳定与断裂预评分 [摘要]。通用物理:两体相对速度 vrel、折合质量 μ=m1+m2m1m2,撞击动能 E=21μvrel2,冲量 J=Δp。教学页可展示"同样的子弹、不同质量/速度 → 冲量与能量不同 → 破不破不同"(这是通用力学,不是某引擎的实现)。
3.7 伤害传播(BFS / 衰减)
- Blast:伤害在某位置施加,带最大影响半径;material function 依据 bond 法线、面积、与冲击点距离给出 health 损失(即"距离衰减"由用户 shader 定义)。[官方文档]
- APEX:
damageDepthLimit(相对被击 chunk 向下传播多少层)、radius damage。[官方文档] - UE:Field 自带 Falloff:
Strain Falloff Type= Linear / Inverse / Square / Logarithmic;Velocity Field Falloff Type= None / Linear / Inverse / Squared / Logarithmic,文档描述为与距离度量 X 分别成 X, 1/X, X2, logX 比例 [官方文档,Fields 指南 §E3];2025 GDC 摘要提到 break propagation 与 shock propagation——这是 UE 5.5 的新特性,具体算法我只看到摘要,未验证。[摘要] https://gdcvault.com/play/1035357/Dynamic-Destruction-in-UE5-with - Bullet 源码注释:沿连接信息传播削弱 [原文]。
- 教学可用的 BFS 衰减模型见 §7 A9(已在 Node 中验证)。
4. 主题三:业界方案与大规模工程经验
4.1 方案总览表
| 方案 | 技术路线 | 可验证的要点 | 来源 / 等级 |
|---|---|---|---|
| Frostbite(BF:BC2/BF3/BF4) | 预破碎 + 大量刚体;"Destruction Masking" 用 signed volume distance field 在完好几何上"挖洞"(渲染技巧) | SIGGRAPH 2010(Robert Kihl)。FB1 的痛点:每个可毁部件要单独做 UV mask。FB2:美术在几何上放球,由球群算出 SDF,存体纹理(约 2 m/像素),shader 里加细节纹理增强边缘;渲染三法:前向、deferred decals(体积包围盒,读深度还原局部位置)、PS3 上按 SDF 剔除三角形(PLAYSTATION Edge Geometry)。PS3 典型耗时:前向 0.77 ms,前向 [branch] 0.45 ms,延迟体积 0.31 ms,三角形剔除 0.20 ms | https://www.ea.com/frostbite/news/destruction-masking-in-frostbite-2-using-volume-distance-fields ;https://www.slideshare.net/slideshow/siggraph10-arrdestruction-maskinginfrostbite2/4883521 [官方幻灯片]。"Levolution"仅在 BF4 概览幻灯片出现,无深入技术 [二手] |
| Red Faction: Guerrilla(Geo-Mod 2.0) | 原版 Geo-Mod 是实时 CSG;Geo-Mod 2.0 转向预破碎 mesh + 应力/冲击驱动,所有人造结构可毁 | 开发者访谈:美术需要"当结构工程师";VP Dan Cermak:系统实时计算压力与重力,桥在撞击点断裂后重量转移使其余部分倒塌;"voxel-like structural integrity"的说法与上述矛盾,未验证。使用 Havok 与否:未验证 | https://www.redfactionwiki.com/wiki/Geo-Mod_2.0 [二手];https://www.cbsnews.com/news/red-faction-guerrilla-q-a/ ;https://www.gamespot.com/articles/red-faction-guerrilla-destructability-impressions/1100-6187787/ [二手/摘要] |
| Teardown | 全体素世界;物体=规则网格上的体素体积;破坏即修改体素,分离出的块成为真实刚体 | 80.lv 访谈:碰撞检测为体素重写后"没想象中慢",后用 SIMD 与多线程优化破坏与物理;多人同步传破坏指令而非体素数据(确定性),加入者需回放过去的指令,缓冲有上限;材质为 8-bit 调色板(≤255 种),碎块继承父调色板;2024 年年终总结:旧引擎体素为无压缩规则 3D 网格(需把资产拆成多个小 shape 以贴合包围盒),新引擎改为 8×8×8 稀疏 chunk + 3D 位图;物理"使用 substepping 而非 solver iterations,并有并行求解器处理大量堆积物体"(该句未明确限定旧/新引擎) | https://80.lv/articles/teardown-developer-breaks-down-multiplayer-and-voxel-destruction-tech ;https://blog.voxagon.se/ ;https://blog.voxagon.se/2024/12/29/year-summary.html [官方博客]。Steam 开发者 Emil(2020)称"为每个体素算承重强度会让游戏在所有现代电脑上跑不动",所以早期建筑要到最后一个连通体素被毁才塌 [二手/摘要]。体素分裂(flood fill)的官方描述未找到 |
| Control(Remedy, Northlight) | 程序化 + Houdini 流水线的系统化破坏 | Remedy 官网:Northlight 物理库"built on PhysX",实时破坏系统支持大量可破坏物体。GDC 2020 演讲 Destructible Environments in Control: Lessons in Procedural Destruction(Johannes Richter, Principal VFX Artist)覆盖 Houdini 工作流与程序化破坏原则、性能约束。演讲正文需 GDC Vault 会员,我没读到:池化、休眠、debris 预算等内容均未验证 | https://www.remedygames.com/northlight [官方];https://www.gdcvault.com/play/1026820/Destructible-Environments-in-Control-Lessons [摘要];https://80.lv/articles/gdc-talk-destructible-environments-in-control [摘要] |
| Rainbow Six Siege(RealBlast) | 程序化 + 预破碎混合;材质驱动破坏 | GDC 2016 The Art of Destruction in Rainbow Six: Siege(Julien L'Heureux)覆盖 RealBlast 集成与技术难点(正文未读)。渲染侧(El Mansouri GDC 2016 幻灯片,已读全文):目标 60 fps;GPU 平均 14 ms(非战斗)、主机 CPU 线性时间上限 38 ms;"在 1GB RAM 内跑"是挑战;"墙/地由程序生成独特几何,打洞会降低 occlusion 效率";"可破坏道具与碎屑通常是小 mesh 但数量很大,可实例化或独立";"碎屑共享材质";"材质驱动的 draw call 系统,材质定义破坏属性";需要更细的剔除粒度来跟上破坏 | https://www.gdcvault.com/play/1023003/The-Art-of-Destruction-in [摘要];https://media.gdcvault.com/gdc2016/Presentations/El_Mansouri_Jalal_Rendering_Rainbow_Six.pdf [原文] |
| Space Marine 2 / Saber Swarm Engine | 并非破碎,是群体渲染/AI;可借鉴"分层 LOD 式人群" | 开发者称 WWZ 引擎可同屏"upwards of 500 actual enemies";分层:远处数千、中景较少、前景约 200;"非常节省内存"。技术细节无官方 GDC/论文来源 | https://www.thegamer.com/warhammer-40k-space-marine-2-chose-tyranids-world-war-z-engine/ [二手];渲染/CPU 瓶颈的说法来自设置指南站,未验证 |
| Fortnite / LEGO Fortnite(Chaos) | 2020 年(v11.50)起 Fortnite 迁移到 Chaos Destruction | GDC 2024 Chaos Physics in LEGO Fortnite(Michael Lentine)覆盖 Rigid Body、Destruction、Networked Physics 更新(仅摘要)。具体参数/规模未验证 | https://gdcvault.com/play/1034659/Chaos-Physics-in-LEGO-Fortnite [摘要];https://us.millenium.gg/news/12042.html (v11.50 迁移新闻)[二手] |
| Unity | 无官方内置运行时破碎;第三方:开源 Voronoi 碎裂(预烘焙缓存 + 凸包 collider)等 | 示例:预烘焙缓存使撞击时 O(1) 查找;每帧只构建 2 个碎片摊平卡顿;超过 100 顶点的 convex 上限改用 BoxCollider;没有对象池,碎片是新实例化子物体,默认生命周期 5 s + 1 s dissolve;作者自称单角色 Voronoi 3–9 ms(桌面) | https://github.com/sinanata/unity-mesh-fracture [二手,自述数字未经基准验证]。Unity DOTS 专用破碎方案:未找到可靠来源(未验证) |
| Godot | 无内置破坏;Jolt 内置(4.4+);常见做法=Blender Cell Fracture 预烘焙 + RigidBody3D | can_sleep 让静止碎片休眠;Jolt 与 GodotPhysics 休眠行为不同 | https://docs.godotengine.org/en/stable/classes/class_rigidbody3d.html ;https://www.strayspark.studio/blog/godot-46-jolt-physics-migration-guide [二手/摘要]。无成熟 Godot 4 Voronoi 运行时破碎插件(未找到) |
| Havok Destruction | 2008 发布的刚体破坏中间件,已停产 | 公开技术资料极少 | https://www.gamedeveloper.com/programming/havok-announces-havok-cloth-havok-destruction [二手] |
| NVIDIA APEX / Blast | chunk + support graph + LOD 预算(APEX);bond 图 + 应力求解(Blast) | 见 §3 与 §4.2 | 官方文档 |
4.2 性能预算 / 碎屑池化 / 碎屑 LOD / 休眠与回收(可核实的做法)
- APEX 的 LOD 预算(最完整的公开描述) [官方文档] https://archive.docs.nvidia.com/gameworks/content/gameworkslibrary/physx/apexsdk/APEX_Destruction/Destruction_Module.html
- 收益函数:B(r,t)=distanceWeight⋅D(r)+ageWeight⋅A(t)+bias,权重和为 1,bias ∈ [0,1];距离重要性用线性弧长(线性衰减,不是平方);
- 成本 = chunk 数 × 用户系数,与模块预算比较;超预算时降低破碎细节或按 benefit/cost 升序移除 chunk;
essentialDepth(默认 0)内的 chunk 永不被 LOD 清除,可能出现预算超支;- LOD 参数含最小 chunk 尺寸、同时动态 chunk island 上限、chunk 总数上限、chunk 生命周期与离原点最大距离;
setDeleteFracturedChunks可让不可见 actor 直接删除碎块; - 每 tick 破碎计算量设上限,超出部分推迟,本地事件优先于同步事件;
minConvexSize默认 0.02; - 小 chunk 破碎可用 APEX 粒子系统(BasicIOS + IOFX + Emitter 模块)模拟碎屑/粉尘。
- UE Chaos(官方优化页只拿到摘要):
- Remove on Sleep:按资产设置(例如睡眠 3 秒后消失)、可缩小至消失(
Scale on Removal默认开)、有 removal 通知允许"碎裂成更小块"、Slow Moving Velocity Threshold;SleepMin/Max 仅在启用 Remove on Sleep 时生效。 - Remove on Break(UE 5.1 加入;Fracture Mode 的 Utilities 最后一项):按 bone/cluster 设最小/最大计时、可改为碎裂。
- 已知问题:论坛有 UE 5.7 用户反馈"移除后 Geometry Collection 失去碰撞"(https://forums.unrealengine.com/t/geometry-collection-loses-collision-after-breaking-fracturing-chaos-destruction/2684190),Epic 缺陷追踪 UE-358545 描述类似现象(https://issues.unrealengine.com/issue/UE-358545;二者是否同一问题未验证)。
- 破碎节流 CVar:
p.Chaos.Clustering.PerAdvanceBreaksAllowed <count>、p.Chaos.Clustering.PerAdvanceBreaksRescheduleLimit <nb of frame>(两个独立来源:Epic 优化页摘要 + hzFishy 笔记 https://notes.hzfishy.fr/Unreal-Engine/Physics/Types/Geometry-Collection);释放刚体节流p.Clustering.ParticleReleaseThrottlingMinCount/MaxCount(仅在 Epic 优化页的搜索摘要里看到,单一来源,未验证)。 - Field:
Sleep / Disable / Kill三类场:Sleep 设休眠阈值,Disable 设移除刚体及碰撞的阈值,Kill 无阈值立即移除;需要 Culling 否则可能覆盖地图中其它同类场;睡眠体可被 Field 与碰撞唤醒,Disabled 体仅被 Field 唤醒。[官方文档] - 缓存系统(Chaos Cache):录制/回放,资产大小约 = 碎片数 × 帧数。[官方培训材料]
- Remove on Sleep:按资产设置(例如睡眠 3 秒后消失)、可缩小至消失(
- Müller 2013:小于阈值尺寸的碎屑不是真刚体,用实例化小 mesh + 球体模拟(高角阻尼);粉尘用粒子;破碎成本只取决于被击物体的大小;建议未来把破碎计算分摊到多个时间步。[原文]
- Parker 2009:islands 休眠、每步断裂事件上限、抑制单四面体碎片。[原文]
- Unity 开源实现:预烘焙 + 每帧摊销(
BUILD_BATCH_SIZE=2)。[二手] - 关于"debris pooling":我没有找到专门讲对象池化碎块的权威演讲;Unity 样例明确没有池化;UE 的 Remove on Sleep/Break 是"回收"而非"池"。若教学页需要池化概念,请标注为通用游戏工程做法(预分配刚体/Instance、复用、上限+淘汰),非某引擎公开实现(未验证)。
5. 主题四:凸包碰撞与加速
5.1 凸分解(V-HACD / CoACD)
| V-HACD | CoACD | |
|---|---|---|
| 思路 | 先体素化,再用轴对齐切平面 + 体积型 concavity 贪心递归分割,得到近似凸块集合;放宽"严格凸"以避免 NP-hard 的精确凸分解 | 直接对 3D 实体 mesh 用任意 3D 平面切割(无体素化误差、凸块互不相交);collision-aware concavity(基于双向 Hausdorff 距离,让接触相关表面占主导);用 Monte Carlo tree search 选切割序列(多步,不是贪心) |
| 参数 | v4:maxConvexHulls(默认32)、resolution(README 建议高精度 10,000,000)、minimumVolumePercentErrorAllowed(替代旧 concavity)、maxRecursionDepth(默认10;深度 10 约 1,024 块) | 阈值 -t/--threshold(默认 0.05,范围 0.01–1)[摘要] |
| 状态 | 已弃用并归档,作者指向 CoACD | SIGGRAPH 2022,ACM TOG 41(4) Article 42;声称在大型关节物体数据集上"更接近原形且部件更少"(摘要,无量化数字) |
| 来源 | https://github.com/kmammou/v-hacd [官方 README] | https://arxiv.org/abs/2205.02961 ;https://github.com/SarahWeiii/CoACD [摘要] |
注意区分:V-HACD/CoACD 是表面碰撞用的近似凸分解(允许重叠);Müller 的 VACD 是体积分解(互不重叠、同时用于破碎与碰撞)。[原文,§2 Related Work]
5.2 Convex hull 简化与碎片碰撞代理
- Müller ACH:固定方向的 k-DOP(Kavan 2006)→ 他们改成按输入选方向;Algorithm 1 见 §7 A15。结论:20 顶点 ACH 体积只比真凸包大 5%。[原文]
- UE Geometry Collection 碰撞类型(4.27 官方文档):
Implicit-ImplicitvsParticle-Implicit;Implicit Type:Analytic(box/sphere/capsule,快、省内存、精度低,初始可能相交后弹开;Collision Object Reduction Percentage可调;"Capsules 功能受限,应避免")与 Level Set(体素化 SDF,精度高,内存与分辨率立方增长,Min/Max Level Set Resolution;应配 Particle-Implicit);Collision Particles Fraction太低会"穿地"。[官方文档] https://dev.epicgames.com/documentation/en-us/unreal-engine/chaos-destruction-overview?application_version=4.27 。CEDEC 同样的总结:Implicit-implicit=低成本/小数据/低精度;Particle-implicit(Level-Set)=高成本/大数据/高精度。 - Unity 开源样例:每片烘焙凸包(上限 100 顶点,超出退化成 BoxCollider),兄弟碎片两两
IgnoreCollision以免共享边界把碎片"顶飞"。[二手]
5.3 Broadphase / sleeping / island
- Parker 2009:对 island 包围盒做 sweep and prune,维护持久接触列表增量更新;Islands 三态 live/asleep/kinematic,低于速度阈值若干步即休眠。[原文]
- Jolt Physics 官方架构文档 [官方文档] https://jrouwe.github.io/JoltPhysics/ :
- 每个 step 把动态体按接触/约束分成 islands;静态体不属于 island;一个 island 内所有体静止则整个 island 休眠;休眠体仍能被唤醒(接触活动体或
ActivateBody);移除体不会自动唤醒邻居(需ActivateBodiesInAABox);休眠判据设置mTimeBeforeSleep与mPointVelocitySleepThreshold。 - Broadphase 为
BroadPhaseQuadTree(四叉树,一次 SIMD 处理 4 个子节点);动体无锁扩张包围盒,后台线程重建紧致树;按BroadPhaseLayer分树,建议静态/动态两层;批量添加用AddBodiesPrepare/Finalize,单个添加得到低效树时调用OptimizeBroadPhase。 - CCD:
EMotionQuality::LinearCast。
- 每个 step 把动态体按接触/约束分成 islands;静态体不属于 island;一个 island 内所有体静止则整个 island 休眠;休眠体仍能被唤醒(接触活动体或
- UE Chaos:休眠/禁用/杀死通过 Field 控制(见 §4.2);Chaos 内部 broadphase 与 island 构建细节我没有找到官方公开描述(未验证)。二手 DeepWiki 称 Chaos 有 island 分组与并行求解(未验证)https://deepwiki.com/mikeroyal/Unreal-Engine-Guide/2.3-chaos-physics-and-simulation 。
5.4 PBD/XPBD 与堆叠稳定性;Chaos 的 Iterations 与 Pushout
Small Steps in Physics Simulation(Macklin 等, SCA 2019)[原文] https://mmacklin.com/smallsteps.pdf
- 观察:一个大步 + n 次迭代,不如 n 个小步 × 每步 1 次迭代。位置误差约与 Δt2 成比例(时间步减半,误差降为 1/4)。
- XPBD 约束投影:Δx=M−1∇CTΔλ,Δλ=∇CM−1∇CT+α~−C−α~λ,α~=α/Δt2。
- 算法 1:碰撞检测每帧一次;
ts = tf / nsteps;每子步:预测位置 x~=xn+tsvn+ts2M−1fext → 对约束做 一次投影 → 更新速度 v=(xn+1−xn)/ts。 - 数据:15 万粒子布料,1 substep × 30 iter:12.4 ms/frame 且明显拉伸;30 substeps × 1 iter:13.5 ms/frame,拉伸少得多。
- 堆叠(Fig.9):一个重箱压在一摞胶囊上——1 substep × 100 iterations 时堆叠很快塌,100 substeps × 1 iteration 稳定。
- 接触:用
vmax限制单子步最大分离速度来防止"爆炸式"分离:Cn(x)=nT[a(x)−b(x)]+max(d0−vmaxts,0)≥0。 - Teardown 作者 2024 年终总结的性能部分提到"物理使用 substepping 而非 solver iterations,并有并行求解器处理大量堆积物体"(文中未明确限定指旧引擎还是新引擎)[官方博客 https://blog.voxagon.se/2024/12/29/year-summary.html]。
Chaos 的求解器
- Epic 工程师(ChrisCUE,Ask Unreal Anything: Physics, 2022-09-28):"Chaos is actually based on PBD";为匹配 PhysX 在既有场景的性能,牺牲了部分 PBD 优势,如把约束误差计算线性化;建议通过调高 iteration 或开启 substepping 并设置 max substep tick 来改善;另有更贵但更稳定的非线性关节求解(
p.Chaos.Solver.Joint.UseLinearSolver 0);对 XPBD 的评价:"our solution is fairly similar"但为性能做了捷径。[官方论坛帖] https://forums.unrealengine.com/t/ask-unreal-anything-physics-september-28-2022-at-1pm-edt/654248?page=4 - SolverIterations 默认值(UE 4.27 Python API,Chaos only):
solver_iterations=3、joint_iterations=2、collision_iterations=2、solver_push_out_iterations=1、joint_push_out_iterations=1、collision_push_out_iterations=0;官方提示:solver 迭代——碰撞与关节互相拉扯/关节链被拉长时增大;collision 迭代——减少碰撞抖动;collision push-out 迭代——缓解穿透。[官方文档] https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/class/SolverIterations?application_version=4.27 。(更新版本默认值可能不同,未验证) - ChaosSolverConfiguration:
Collision Pair Iterations/Collision Push Out Pair Iterations(对每个约束连续解 N 次);Collision Max Push Out Velocity:开局互相穿透的两体被拉开时的最大速度,0 表示不限制,非零可避免深穿透导致的"爆炸";也可改为增加 velocity iterations,更贵但能一帧内去穿透。[摘要] https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/class/ChaosSolverConfiguration?application_version=5.0 - Pushout 的机制:Epic 文档未逐字解释。通用 PBD 语境下,pushout 是一个只修正位置、不改变速度(因而不注入能量)的去穿透阶段——这是我的通用解释,Chaos 的具体实现未验证。
- 对碎片堆的实践含义:①碎片数量多 → 把
Collision Particles Fraction、Level-Set 分辨率调低(见 §5.2);②增加迭代或 substep;③激进地让碎片尽早休眠/移除(§4.2)。
6. 主题五:渲染与内存
- Geometry Collection 渲染模型:1 个 GC = 每个材质 1 次 draw call;初始即已破碎;默认无 LOD;支持 Nanite。Nanite mesh 不能直接转 GC:先破碎完成后再勾选
Enable Nanite。[官方培训材料] CEDEC 2022。 - Nanite 对碎块的意义(Epic 优化页摘要):源静态网格启用 Nanite 时资产自动启用;利用 Nanite 簇做"自动 LOD"(GC 本身无 LOD);减小资产磁盘/内存体积;cook 时可剥离几何属性,文件体积减少约 95%([摘要],单一来源,数字未独立验证)。已知限制(论坛):Nanite 下材质需 "use with nanite" 且 GC 渲染只支持 opaque/masked;破碎时 Nanite 的三角面减比例不会真正反映到磁盘数据(一用户的单个 GC 达 100 MB)。[二手] https://forums.unrealengine.com/t/fracturing-nanite-meshes/2420606
- Root Mesh Proxy / 实例化:把 GC 在破碎之前替换为静态网格代理(支持 LOD、Nanite、Lumen);
Custom Renderer Type = GeometryCollectionRootProxyRenderer时以 ISM 跨所有 GC 实例化渲染。[摘要+二手] hzFishy 笔记也列出 "Root Mesh Proxies: swap the GC for one or more static meshes until the component breaks"。 - GPU 驱动碎片:开源项目
niagara-destruction-driver:在 Niagara 里 GPU 模拟每块碎片(bone)→ 变换写入 render target → 顶点着色器按 UV 通道里的 bone index 取变换,经材质 World Position Offset 实现;非实例化;限制:仅一级 chunk、无碰撞追踪、无网络复制、Nanite 未支持、可能浮空;作者称"比 Chaos 更高性能"(无基准)。[二手/README] https://github.com/eanticev/niagara-destruction-driver - 碎块对象池:见 §4.2 第 6 点(未找到权威公开资料)。
- debris → particles(小碎块粒子化):
- Müller 2013:粒子 + 实例化小 mesh + 球体物理。[原文]
- APEX:最小 chunk 破碎时用 APEX 粒子系统模拟碎屑/粉尘。[官方文档]
- UE:Niagara 通过
NiagaraDataInterfaceChaosDestruction读取 Collision / Breaking / Trailing 事件;需在 Project Settings > Physics > Chaos Physics 开Generate Collision Data / Generate Break Data / Generate Trailing Data / Generate Contact Graph,并在 GC Actor 启用Notify Collisions / Notify Breaks;Niagara 里加Chaos Destruction Data参数与Apply Chaos Data模块;事件可按类型、物理材质、位置过滤并做空间分区控制分布。[官方培训材料+摘要] CEDEC 与 https://dq8iqaixvew1d.cloudfront.net/en-US/InteractiveExperiences/Physics/ChaosPhysics/ChaosDestruction/ChaosDestructionOverview/index.html - 已知坑:Niagara 的 Chaos break event 默认影响世界上所有破碎事件(论坛)。[二手]
- Niagara Data Channels 用于向大型共享 Niagara System 注入事件,适合"许多小碎片 → 一个共享特效";5.4 时仍为实验性;NDC 读取接口的
GetNDCSpawnData合并为单个内部 spawn 事件并解除 GPU 上每帧 8 次 NDC spawn 的旧限制。[摘要] https://portal.productboard.com/epicgames/1-unreal-engine-public-roadmap/c/1484-niagara-data-channels-experimental
- 音频事件节流:
- UE
Sound Concurrency:Max Count、Limit to Owner、Retrigger Time、Voice Steal Release Time;Resolution Rule:Prevent New / Stop Oldest / Stop Farthest Then Prevent New / Stop Farthest Then Oldest / Stop Lowest Priority / Stop Lowest Priority Then Prevent New / Stop Quietest。[官方文档] https://dev.epicgames.com/documentation/unreal-engine/sound-concurrency-reference-guide - Wwise
Limit sound instances:全局或每 GameObject,达到上限时 "Kill voice for lowest priority" 或 "use virtual voice settings",同优先级可丢弃最新或最旧。[摘要] https://www.audiokinetic.com/library/edge/?source=Help&id=limiting_object_playback_instances - 关键点:并发限制只限制播放,不减少事件数;Chaos 的碰撞/破碎事件源头节流应配合 §4.2 的 CVar 与脚本侧冷却/冲量阈值。("如何搭配"属于建议,非官方结论。)
- 破碎音效的物理侧:Zheng & James 2010 Rigid-body fracture sound with precomputed soundbanks(SIGGRAPH 2010)。[摘要] https://dl.acm.org/doi/10.1145/1778765.1778806
- UE
7. 适合做交互演示的算法清单与最小伪代码
以下代码为我编写的 JavaScript 最小实现(非引用自任何引擎源码;算法思想的出处见各节)。本节全部 14 个
js代码块已从本文件原样抽出,在 Node v22 中执行断言通过:Voronoi 面积和 = 画布面积、随机点与"最近站点 cell"零不一致;Bowyer–Watson 三角形数 = 2n−2−h(n=60 时 108);Lloyd 10 次迭代使 cell 面积变异系数 0.554 → 0.240;Poisson-disk 最小间距 ≥ r;3×3 方格网得到 12 个 bond,切断底部连接后 6 块作为一个悬空 island 被识别;SAP 配对数与 O(n²) 暴力一致;A15 为文字伪代码(未执行)。可直接搬进 Canvas 页面。
A1. 站点 → Voronoi cell(半平面裁剪,含提前退出)
// poly: [{x,y}] 凸多边形;保留满足 a*x + b*y <= c 的一侧
function clipPoly(poly, a, b, c) {
const out = [];
for (let i = 0; i < poly.length; i++) {
const P = poly[i], Q = poly[(i + 1) % poly.length];
const fp = a*P.x + b*P.y - c, fq = a*Q.x + b*Q.y - c;
if (fp <= 0) out.push(P);
if ((fp < 0 && fq > 0) || (fp > 0 && fq < 0)) {
const t = fp / (fp - fq);
out.push({ x: P.x + t*(Q.x-P.x), y: P.y + t*(Q.y-P.y) });
}
}
return out;
}
function voronoiCells(sites, W, H) {
const d2 = (a,b)=>(a.x-b.x)**2+(a.y-b.y)**2;
return sites.map((s, i) => {
let poly = [{x:0,y:0},{x:W,y:0},{x:W,y:H},{x:0,y:H}];
const order = sites.map((_,j)=>j).filter(j=>j!==i)
.sort((j,k)=>d2(s,sites[j])-d2(s,sites[k])); // 近邻优先
let R = Infinity; // 当前 cell 最远顶点距离
for (const j of order) {
const t = sites[j];
if (Math.sqrt(d2(s,t)) > 2*R) break; // 提前退出(Blender 同款)
// 比 t 更靠近 s:2(t-s)·x <= |t|^2-|s|^2
poly = clipPoly(poly, 2*(t.x-s.x), 2*(t.y-s.y),
t.x*t.x+t.y*t.y - s.x*s.x - s.y*s.y);
if (!poly.length) break;
R = Math.max(...poly.map(v => Math.hypot(v.x-s.x, v.y-s.y)));
}
return poly;
});
}A2. Bowyer–Watson(Delaunay;Voronoi 为其对偶)
function bowyerWatson(pts) { // 返回三角形下标数组 [[i,j,k],...]
const M = 1e5, n = pts.length;
const P = pts.concat([{x:-M,y:-M},{x:M,y:-M},{x:0,y:M}]); // 超级三角形
let T = [[n, n+1, n+2]];
const orient = (a,b,c)=>(b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
const inCirc = (t,p) => {
let [a,b,c] = t.map(i=>P[i]); if (orient(a,b,c) < 0) [b,c]=[c,b];
const ax=a.x-p.x, ay=a.y-p.y, bx=b.x-p.x, by=b.y-p.y, cx=c.x-p.x, cy=c.y-p.y;
return (ax*ax+ay*ay)*(bx*cy-cx*by) - (bx*bx+by*by)*(ax*cy-cx*ay)
+ (cx*cx+cy*cy)*(ax*by-bx*ay) > 0;
};
for (let i = 0; i < n; i++) {
const bad = T.filter(t => inCirc(t, P[i])), cnt = new Map();
for (const t of bad) for (let k = 0; k < 3; k++) {
const a=t[k], b=t[(k+1)%3], key = a<b ? a+','+b : b+','+a;
cnt.set(key, (cnt.get(key)||0)+1);
}
T = T.filter(t => !bad.includes(t));
for (const [key,c] of cnt) if (c === 1) { // 空洞边界(只属于一个坏三角形)
const [a,b] = key.split(',').map(Number); T.push([a,b,i]);
}
}
return T.filter(t => t.every(i => i < n)); // 去掉含超级顶点的三角形
}
// Voronoi 顶点 = 每个三角形的外接圆圆心;Delaunay 边 <-> Voronoi 边A3. Lloyd 松弛
function polyCentroid(p) { // 多边形质心
let A=0, cx=0, cy=0;
for (let i=0;i<p.length;i++){ const q=p[(i+1)%p.length], cr=p[i].x*q.y-q.x*p[i].y;
A+=cr; cx+=(p[i].x+q.x)*cr; cy+=(p[i].y+q.y)*cr; }
A/=2; return { x: cx/(6*A), y: cy/(6*A) };
}
function lloyd(sites, W, H, iters) {
let S = sites.map(s=>({...s}));
for (let k=0;k<iters;k++) {
const cells = voronoiCells(S, W, H);
S = cells.map((c,i)=> c.length>=3 ? polyCentroid(c) : S[i]); // 站点移到质心
}
return S;
}
// 演示点:每按一次"松弛",cell 面积的变异系数下降;也可用 over-relaxation:p += ω(c-p), ω<2A4. 点分布:均匀 / 径向 / 聚簇 / 冲击密度衰减 / Poisson-disk
const U = (a,b)=>a+(b-a)*Math.random();
const uniform = (n,W,H)=>Array.from({length:n},()=>({x:U(0,W),y:U(0,H)}));
// 冲击密度衰减:r = R * u^gamma(gamma=0.5 均匀;gamma 越大越集中于冲击点;面密度 ∝ r^(1/gamma-2))
const impact = (n,cx,cy,R,gamma)=>Array.from({length:n},()=>{
const r=R*Math.pow(Math.random(),gamma), a=U(0,2*Math.PI);
return {x:cx+r*Math.cos(a), y:cy+r*Math.sin(a)};});
// 径向(蛛网):rings 个同心环,每环 k 个点,半径几何级数增长,角度加抖动
const radial = (cx,cy,rings,k0,r0,growth)=>{ const P=[];
for(let i=0;i<rings;i++){ const r=r0*Math.pow(growth,i), k=k0+i*2;
for(let j=0;j<k;j++){ const a=(j+U(-.3,.3))/k*2*Math.PI;
P.push({x:cx+r*Math.cos(a), y:cy+r*Math.sin(a)}); } } return P; };
// 聚簇:簇心均匀、簇内高斯
const gauss=()=>Math.sqrt(-2*Math.log(1-Math.random()))*Math.cos(2*Math.PI*Math.random());
const clustered=(nc,per,sigma,W,H)=>uniform(nc,W,H).flatMap(c=>
Array.from({length:per},()=>({x:c.x+sigma*gauss(), y:c.y+sigma*gauss()})));
// Poisson-disk(Bridson):背景网格 + active list,候选落在 [r,2r] 环
function poisson(W,H,r,k=30){
const cell=r/Math.SQRT2, gw=Math.ceil(W/cell), gh=Math.ceil(H/cell);
const grid=new Array(gw*gh).fill(-1), pts=[], act=[];
const add=p=>{pts.push(p);act.push(pts.length-1);grid[Math.floor(p.y/cell)*gw+Math.floor(p.x/cell)]=pts.length-1;};
const ok=p=>{const gx=Math.floor(p.x/cell),gy=Math.floor(p.y/cell);
for(let y=Math.max(0,gy-2);y<=Math.min(gh-1,gy+2);y++)
for(let x=Math.max(0,gx-2);x<=Math.min(gw-1,gx+2);x++){const j=grid[y*gw+x];
if(j>=0&&Math.hypot(pts[j].x-p.x,pts[j].y-p.y)<r)return false;} return true;};
add({x:U(0,W),y:U(0,H)});
while(act.length){ const ai=Math.floor(Math.random()*act.length), s=pts[act[ai]]; let hit=false;
for(let t=0;t<k;t++){ const a=U(0,2*Math.PI), d=r*(1+Math.random()),
p={x:s.x+d*Math.cos(a), y:s.y+d*Math.sin(a)};
if(p.x>=0&&p.x<W&&p.y>=0&&p.y<H&&ok(p)){add(p);hit=true;break;} }
if(!hit)act.splice(ai,1); }
return pts;
}A5. Slice / Planar / Brick(凸多边形递归二分)
// 把凸多边形沿直线 (点 p0,法线 n) 切成两半;复用 clipPoly
function splitPoly(poly, p0, n) {
const c = n.x*p0.x + n.y*p0.y;
return [ clipPoly(poly, n.x, n.y, c),
clipPoly(poly, -n.x, -n.y, -c) ].filter(p=>p.length>=3);
}
// Slice:沿 X/Y 各 N 条线切,线位置 + angleVar/offsetVar 随机扰动(对应 UE SliceCutter 的参数名)
// Brick:按行切(行高 h),每行内按砖长 L 切,奇数行偏移 L/2 即得错缝A6. 噪声边缘(midpoint displacement,对应 UE Amplitude/Persistence)
function noisyEdge(a, b, amp, rough, depth) { // rough = persistence(≈0.5);depth≈octaves
let pts=[a,b], A=amp;
for(let d=0;d<depth;d++){ const out=[];
for(let i=0;i<pts.length-1;i++){ const p=pts[i], q=pts[i+1];
const nx=-(q.y-p.y), ny=q.x-p.x, L=Math.hypot(nx,ny)||1, o=(Math.random()*2-1)*A;
out.push(p,{x:(p.x+q.x)/2+nx/L*o, y:(p.y+q.y)/2+ny/L*o}); }
out.push(pts.at(-1)); pts=out; A*=rough; }
return pts;
}
// 把两个相邻 cell 共享边都替换成同一条 noisyEdge(一方正向、一方反向),即得无缝噪声边缘A7. 构建 Connection Graph(邻接 = 共享边)
// cells: 每个 cell 的顶点环。用"量化后的边键"找共享边。
function buildConnections(cells, q=1e-3){
const key=(p)=>Math.round(p.x/q)+','+Math.round(p.y/q);
const edgeOwner=new Map(), bonds=[];
cells.forEach((poly,i)=>poly.forEach((p,k)=>{
const r=poly[(k+1)%poly.length], a=key(p), b=key(r), e=a<b?a+'|'+b:b+'|'+a;
if(edgeOwner.has(e)){ const j=edgeOwner.get(e);
bonds.push({a:j, b:i, alive:true, len:Math.hypot(p.x-r.x,p.y-r.y), health:1}); }
else edgeOwner.set(e,i); }));
return bonds; // bond.len 可当作 Blast 的 area,用于缩放强度
}A8. 连通分量剪断(并查集 + 锚点)
class UF{ constructor(n){this.p=[...Array(n).keys()];this.r=Array(n).fill(0);}
find(x){while(this.p[x]!==x){this.p[x]=this.p[this.p[x]];x=this.p[x];}return x;}
union(a,b){a=this.find(a);b=this.find(b);if(a===b)return;
if(this.r[a]<this.r[b])[a,b]=[b,a]; this.p[b]=a; if(this.r[a]===this.r[b])this.r[a]++;} }
// 返回"悬空"的碎块组(没有任何一个成员连到锚点)
function floatingPieces(n, bonds, anchored){
const uf=new UF(n);
for(const e of bonds) if(e.alive) uf.union(e.a,e.b);
const grounded=new Set(); for(let i=0;i<n;i++) if(anchored[i]) grounded.add(uf.find(i));
const g=new Map(); for(let i=0;i<n;i++){const r=uf.find(i); (g.get(r)||g.set(r,[]).get(r)).push(i);}
return [...g].filter(([r])=>!grounded.has(r)).map(([,m])=>m);
}
// 自检:链 0-1-2-3-4,锚点=0,断 (1,2) → 返回 [[2,3,4]]
// 每次有 bond 断裂后重算一次;悬空组转为独立刚体(用你自己的 2D 物理或简单重力下落)。
// 对应:Bullet btUnionFind、Blast island detection、Müller flood fill(UE 内部实现未验证)A9. BFS 伤害传播(距离衰减 + 下限剪枝)
function bfsDamage(adj, src, dmg0, decay, minDmg){
const dmg=new Map([[src,dmg0]]), q=[src];
while(q.length){ const u=q.shift(), d=dmg.get(u)*decay;
if(d<minDmg) continue;
for(const v of adj[u]) if(!dmg.has(v)||dmg.get(v)<d){ dmg.set(v,d); q.push(v); } }
return dmg; // Map(node -> 到达该节点的伤害)
}
// 自检(8 节点链,src=3, dmg0=100, decay=0.6, min=5):
// 0:21.6 1:36.0 2:60.0 3:100.0 4:60.0 5:36.0 6:21.6 7:13.0
// 若按几何距离衰减而非跳数衰减:d = dmg0 * falloff(dist/radius),falloff 可选 Linear/Inverse/Square/Log(对应 UE Field Falloff)A10. Strain 累计到阈值破碎 + 分层级阈值(对比"瞬时 External Strain"与"累积 Health")
// 模型 A:累积(Blast bond health / UE Internal Strain 的思路)
function applyHitAccum(pieces, hit, radius, impulse, falloff){
for(const p of pieces){ if(p.broken) continue;
const d=Math.hypot(p.x-hit.x,p.y-hit.y); if(d>radius) continue;
p.strain += impulse*falloff(d/radius); // falloff(t)=1-t 为线性
if(p.strain >= p.threshold) p.broken = true; }
}
// 模型 B:瞬时(UE External Strain,CEDEC 描述:不累积,破开阈值 ≤ 该值的一层)
function applyHitInstant(clusters, hit, radius, strain, falloff, numHits=1){
for(const c of clusters){ const d=Math.hypot(c.x-hit.x,c.y-hit.y); if(d>radius) continue;
const s=strain*falloff(d/radius);
let levels=0;
while(levels<numHits && c.level<c.maxLevel && s>=c.threshold[c.level]){ c.level++; levels++; } } // Num Strain Hits 可连破多层
}
// 层级:threshold = [5000, 500, 50] 之类,level 0->1->2 逐级拆开;maxLevel 对应 Max Cluster Level
// 自检(瞬时模型,threshold=[5000,500,50],maxLevel=3):strain 1000 → 不破;strain 6000、1 hit → level 1;3 hits → level 3
// 自检(累积模型,5 块、阈值 50、冲量 30、半径 35、线性衰减、命中 3 次于原点):
// strain = 60,64,39,13,0 -> 前两块破(11000)A11. O'Brien-Hodgins 的 2D 版"最大拉应力 → 裂纹方向"(用于解释 stress-based)
// 对称 2×2 张量 [[a,b],[b,c]]
function eig2(a,b,c){
const m=(a+c)/2, d=Math.hypot((a-c)/2,b), l1=m+d, l2=m-d;
const th=0.5*Math.atan2(2*b,a-c); // l1 对应特征向量的角度
return {l1, l2, v1:{x:Math.cos(th),y:Math.sin(th)}};
}
// 判据:若 l1 > toughness → 在该点开裂,裂面法线 = v1,即裂纹方向 ⟂ v1(垂直于最大拉应力方向)
// 压缩(l<0)不开裂。教学页可画张量椭圆 + 可拖动加载点。
// 注:真实论文的 separation tensor 作用在"节点"上、由相邻单元的拉/压分量合成,此处只演示"特征分解→方向"这一核心。A12. 最小"悬空判定 + 体积塌落"联动(stress-lite,教学用,非 Blast 实现)
// 自顶向下的载荷累积:每个未锚定节点把自身 + 其上方子节点载荷,平均分给"距锚点更近一层"的邻居
function loadAccumulate(adj, weight, anchored){
const n=adj.length, dist=Array(n).fill(Infinity), q=[];
anchored.forEach((a,i)=>{ if(a){ dist[i]=0; q.push(i);} });
while(q.length){ const u=q.shift(); for(const v of adj[u]) if(dist[v]===Infinity){ dist[v]=dist[u]+1; q.push(v);} }
const order=[...Array(n).keys()].sort((a,b)=>dist[b]-dist[a]); // 最远的先传
const load=weight.slice(), bondLoad=new Map();
for(const u of order){ if(dist[u]===0||dist[u]===Infinity) continue;
const sup=adj[u].filter(v=>dist[v]===dist[u]-1);
for(const s of sup){ const share=load[u]/sup.length; load[s]+=share; bondLoad.set(u+'-'+s,share); } }
return {load, bondLoad}; // bondLoad > 极限 ⇒ 断
}
// 自检(5 节点链,锚点=0,权重各 1):4-3:1, 3-2:2, 2-1:3, 1-0:4 —— 越靠近锚点承载越大
// 这是"塔楼自重压垮底部"的最小解释;Blast 的 Stress Solver 是迭代求解整体力平衡,
// 区分 tension / compression / shear,请勿把本函数等同于它。A13. 每帧"破碎预算 / 节流 / 睡眠回收"(对应 PerAdvanceBreaksAllowed、Remove on Sleep)
const BUDGET_PER_FRAME = 8; // ≈ p.Chaos.Clustering.PerAdvanceBreaksAllowed
function step(world, dt){
let used=0;
while(world.breakQueue.length && used<BUDGET_PER_FRAME){ breakCluster(world.breakQueue.shift()); used++; }
// 剩余留到下一帧(≈ RescheduleLimit:超过 N 帧仍未处理则强制处理或丢弃,由你决定)
for(const b of world.bodies){
if(b.speed < SLEEP_V) b.sleepT += dt; else b.sleepT = 0;
if(b.sleepT > REMOVE_AFTER_SLEEP){ b.scale -= dt/FADE; if(b.scale<=0) world.recycle(b); } // Remove on Sleep + Scale on Removal
}
// 额外:同时存活刚体数 > MAX 时,按 "benefit/cost" 升序移除(APEX LOD:benefit = wD·D(r)+wA·A(t)+bias)
}A14. 1D Sweep and Prune(碎片 broadphase 演示)
function sweepAndPrune(boxes){ // boxes: {minX,maxX,minY,maxY}
const idx=boxes.map((_,i)=>i).sort((a,b)=>boxes[a].minX-boxes[b].minX), pairs=[];
for(let i=0;i<idx.length;i++) for(let j=i+1;j<idx.length;j++){
const A=boxes[idx[i]], B=boxes[idx[j]]; if(B.minX>A.maxX) break; // 提前退出
if(A.minY<=B.maxY && B.minY<=A.maxY) pairs.push([idx[i],idx[j]]); }
return pairs;
}
// 自检:300 个 20×20 盒在 1000×1000 中,SAP=79 对,与 O(n²) 暴力一致A15. 体积最大化近似凸包(Müller Algorithm 1)与 k-DOP 拟合
// 输入:网格顶点 v1..vn;N = 允许使用的顶点数
C ← 凸包初始化 { argmax_vi vi.x }
C ← C ∪ { argmax |C - vi| } ; C ← C ∪ { argmax area(C,vi) } ; C ← C ∪ { argmax volume(C,vi) }
createConvex(C)
for each vi ∉ C: V_i = Σ_j max( vol(vi, f_j), 0 ) // vi 加入后会增加的体积;f_j 为可见面
while |C| < N:
k = argmax_i V_i ; C ← C ∪ { v_k } ; updateConvex(v_k)
for each vi ∉ C: V_i -= Σ_{f_j removed} max(vol(vi,f_j),0) ; V_i += Σ_{f_j added} max(vol(vi,f_j),0)
// 随 N→顶点数收敛到真凸包,体积从上方逼近。k-DOP:沿固定方向把平面从外向内推到碰到顶点再裁剪。
// 2D 教学版:用 Andrew monotone chain 求凸包 + 取"面积增量最大"的点逐步加入。8. 与 UE Chaos 的对应关系总表
| 通用概念 | UE Chaos 对应 | 依据 | 确认度 |
|---|---|---|---|
| 预破碎碎片集合 | Geometry Collection(Fracture Mode) | Epic 文档 | 官方文档 |
| 碎裂层级 | Fracture Level / Cluster Level;Max Cluster Level | Epic 文档 | 官方文档 |
| Voronoi 站点分布 | Uniform / Clustered / Radial;Dataflow VoronoiFracture | Epic 文档 | 摘要+官方文档 |
| Planar / Slice / Brick | Planar / Slice / Brick;SliceCutter | Epic 文档 | 官方文档 |
| 噪声边缘 | Amplitude / Frequency / Persistence / Lacunarity / OctaveNumber / PointSpacing | 节点参考 | 官方文档 |
| 内部面 | 偶数材质槽;AutoUV;SplitIslands | CEDEC 幻灯片 | 官方培训材料 |
| 簇合并质量惯量 | Clustering model | destruction-overview | 官方文档 |
| Connection graph / support graph | Connection Graph(开始时按最近邻建立,link 带 strain) | destruction-overview | 官方文档 |
| 锚定 / support chunk | Anchor Field(Add Field Command)、World Support / Kinematic、State 工具 | 官方文档 | 官方文档 |
| 断裂判据(冲量/力) | link 冲量 > link 限值;Damage Threshold(按 Level) | 官方文档 | 官方文档 |
| 瞬时伤害 | External Strain(不累积,Num Strain Hits) | CEDEC | 官方培训材料 |
| 累积/衰减 | Internal Strain(降阈值)、Decay Amount/Delay | Fields 指南 | 官方文档 |
| 距离衰减 | Strain/Velocity Falloff Type:Linear/Inverse/Square/Logarithmic | Fields 指南 | 官方文档 |
| 伤害传播 | GDC 2025:break propagation / shock propagation | GDC Vault 摘要 | 仅摘要,细节未验证 |
| 连通分量 | Chaos 内部 island 检测 | —— | 未验证 |
| 碎屑 LOD | Remove on Sleep / Remove on Break / Scale on Removal;Sleep/Disable/Kill Field | 官方优化页(摘要)+ Fields 指南 | 摘要 |
| 破碎节流 | p.Chaos.Clustering.PerAdvanceBreaksAllowed、...PerAdvanceBreaksRescheduleLimit | Epic 优化页摘要 + hzFishy | 二手+摘要 |
| 碰撞代理 | Implicit(Analytic box/sphere/capsule)/ Level Set;Collision Particles Fraction | 官方文档 | 官方文档 |
| 求解器 | PBD-based;Iterations 3/2/2、PushOut 1/1/0(UE 4.27 默认) | Epic 工程师、Python API | 官方 |
| 渲染 | 1 GC = 1 draw call/材质;Nanite;Root Mesh Proxy / ISM | CEDEC、优化页摘要 | 官方培训材料+摘要 |
| 粒子替换 | Niagara Chaos Destruction Data Interface;Niagara Data Channels | CEDEC、路线图 | 官方培训材料 |
| 回放 | Chaos Cache(录制/回放) | CEDEC | 官方培训材料 |
| 音频 | Sound Concurrency(Max Count / Resolution Rule) | 官方文档 | 官方文档 |
9. 未验证 / 未找到 / 来源矛盾的清单
- Chaos 的 island 构建、broadphase、连通分量算法的内部实现;
Cluster Connection Type各枚举的精确语义。 - UE 5.5 "break propagation / shock propagation" 的具体算法。
p.Clustering.ParticleReleaseThrottlingMinCount/MaxCount的存在性与默认值(只有单一摘要来源)。- Epic 优化页中 "cook 时剥离几何属性减小 95%" 的原话与适用条件(摘要)。
- Control 破坏的池化/休眠/碎屑预算;Digital Dragons 关于破坏预算的演讲(未找到)。
- Red Faction Guerrilla 是否使用 Havok;Geo-Mod 2.0 内部结构应力实现细节(访谈有矛盾说法)。
- Teardown 体素体分裂(flood fill)与小碎片清理的官方描述(官方博客与访谈均未涉及)。
- Peridynamics 在商用游戏实时破碎中的实际应用。
- Bridson 论文推荐的 k=30(来自记忆)。
- O'Brien–Hodgins 1999 式 (32) 的符号(PDF 文本抽取乱码,需对照原图)。
- Unity DOTS 的专用破碎方案;Godot 4 运行时 Voronoi 破碎插件。
- Fortune 1987 Algorithmica 期刊版信息。
- Brick 工具的完整参数。
- 音频节流的"推荐搭配方式"属于我的建议,非官方指南。
10. 来源索引
论文 / 原文(我读到全文)
- [S1] Müller, Chentanez, Kim. Real Time Dynamic Fracture with Volumetric Approximate Convex Decompositions, ACM TOG 32(4), 2013. https://matthias-research.github.io/pages/publications/fractureSG2013.pdf ;https://dl.acm.org/doi/10.1145/2461912.2461934 ;视频 https://youtu.be/eB2iBY-HjYU
- [S2] Parker & O'Brien. Real-Time Deformation and Fracture in a Game Environment, SCA 2009. http://graphics.berkeley.edu/papers/Parker-RTD-2009-08/Parker-RTD-2009-08.pdf ;https://dl.acm.org/doi/10.1145/1599470.1599492
- [S3] O'Brien & Hodgins. Graphical Modeling and Animation of Brittle Fracture, SIGGRAPH 99. http://graphics.berkeley.edu/papers/Obrien-GMA-1999-08/Obrien-GMA-1999-08.pdf
- [S4] Müller, McMillan, Dorsey, Jagnow. Real-Time Simulation of Deformation and Fracture of Stiff Materials, 2001. https://matthias-research.github.io/pages/publications/fracture.pdf
- [S5] Macklin et al. Small Steps in Physics Simulation, SCA 2019. https://mmacklin.com/smallsteps.pdf ;DOI https://doi.org/10.1145/3309486.3340247
- [S6] El Mansouri. Rendering Rainbow Six | Siege, GDC 2016 幻灯片. https://media.gdcvault.com/gdc2016/Presentations/El_Mansouri_Jalal_Rendering_Rainbow_Six.pdf
- [S7] Kihl. Destruction Masking in Frostbite 2 using Volume Distance Fields, SIGGRAPH 2010. https://www.ea.com/frostbite/news/destruction-masking-in-frostbite-2-using-volume-distance-fields ;https://www.slideshare.net/slideshow/siggraph10-arrdestruction-maskinginfrostbite2/4883521
源码(我读到全文)
- [S8] Blender Cell Fracture. https://github.com/blender/blender-addons/blob/main/object_fracture_cell/fracture_cell_calc.py ;
fracture_cell_setup.py同目录 - [S9] Bullet FractureDemo. https://github.com/bulletphysics/bullet3/blob/master/examples/FractureDemo/btFractureDynamicsWorld.cpp ;
btFractureBody.cpp同目录
Epic / UE
- [E1] Destruction Overview. https://dev.epicgames.com/documentation/en-us/unreal-engine/destruction-overview
- [E2] Chaos Destruction Overview (4.27). https://dev.epicgames.com/documentation/en-us/unreal-engine/chaos-destruction-overview?application_version=4.27
- [E3] Chaos Fields User Guide. https://dev.epicgames.com/documentation/en-us/unreal-engine/chaos-fields-user-guide-in-unreal-engine
- [E4] Cluster Geometry Collections User Guide. https://dev.epicgames.com/documentation/en-us/unreal-engine/cluster-geometry-collections-user-guide-in-unreal-engine
- [E5] Fracturing Geometry Collections User Guide(仅摘要). https://dev.epicgames.com/documentation/en-us/unreal-engine/fracturing-geometry-collections-user-guide
- [E6] Dataflow nodes
SliceCutter/VoronoiFracture. https://dev.epicgames.com/documentation/unreal-engine/node-reference/Dataflow/SliceCutter ;https://dev.epicgames.com/documentation/unreal-engine/node-reference/Dataflow/VoronoiFracture - [E7] Dataflow for Destruction Quickstart. https://dev.epicgames.com/documentation/unreal-engine/dataflow-for-destruction-quickstart
- [E8] Chaos Destruction Optimization(仅摘要). https://dev.epicgames.com/documentation/en-us/unreal-engine/chaos-destruction-optimization
- [E9] GDC 2025 Dynamic Destruction in UE5 with the Chaos Destruction System(VanAllen, Caillaud). https://gdcvault.com/play/1035357/Dynamic-Destruction-in-UE5-with
- [E10] CEDEC 2022 UE5 Chaos Destruction 入門. https://www.docswell.com/s/EpicGamesJapan/5V3PXZ-UE_CEDEC2022_ChaosDestruction
- [E11] SolverIterations Python API (4.27). https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/class/SolverIterations?application_version=4.27
- [E12] ChaosSolverConfiguration (5.0). https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/class/ChaosSolverConfiguration?application_version=5.0
- [E13] Ask Unreal Anything: Physics. https://forums.unrealengine.com/t/ask-unreal-anything-physics-september-28-2022-at-1pm-edt/654248?page=4
- [E14] Physics in Unreal Engine. https://dev.epicgames.com/documentation/en-us/unreal-engine/physics-in-unreal-engine
- [E15] Sound Concurrency Reference Guide. https://dev.epicgames.com/documentation/unreal-engine/sound-concurrency-reference-guide
- [E16] GDC 2024 Chaos Physics in LEGO Fortnite. https://gdcvault.com/play/1034659/Chaos-Physics-in-LEGO-Fortnite
- [E17] GeometryCollection Python API. https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/class/GeometryCollection?application_version=4.27
- [E18] Niagara Data Channels. https://portal.productboard.com/epicgames/1-unreal-engine-public-roadmap/c/1484-niagara-data-channels-experimental
NVIDIA / 其它引擎中间件
- [N1] APEX Destruction. https://archive.docs.nvidia.com/gameworks/content/gameworkslibrary/physx/apexsdk/APEX_Destruction/Destruction_Module.html ;DestructibleParameters https://archive.docs.nvidia.com/gameworks/content/gameworkslibrary/physx/apexsdk/_static/build_framework/structnvidia_1_1apex_1_1_destructible_parameters.html
- [N2] Blast SDK introduction. https://docs.omniverse.nvidia.com/kit/docs/blast-sdk/latest/docs/api/introduction.html ;Stress Solver https://docs.omniverse.nvidia.com/kit/docs/blast-sdk/latest/docs/api/extensions/ext_stress.html ;Release 5.1.0 https://github.com/NVIDIAGameWorks/Blast/releases/tag/v5.1.0
- [N3] Jolt Physics Architecture. https://jrouwe.github.io/JoltPhysics/
- [N4] V-HACD. https://github.com/kmammou/v-hacd ;CoACD paper https://arxiv.org/abs/2205.02961 ;repo https://github.com/SarahWeiii/CoACD
- [N5] Houdini Voronoi Fracture. https://www.sidefx.com/docs/houdini/nodes/sop/voronoifracture.html ;RBD Configure https://www.sidefx.com/docs/houdini/nodes/sop/rbdconfigure.html ;Houdini–UE Chaos https://www.sidefx.com/docs/houdini/unreal/chaosintegration.html
- [N6] Havok Destruction 发布报道. https://www.gamedeveloper.com/programming/havok-announces-havok-cloth-havok-destruction
业界案例
- [I1] Teardown:80.lv https://80.lv/articles/teardown-developer-breaks-down-multiplayer-and-voxel-destruction-tech ;官方博客 https://blog.voxagon.se/ 、https://blog.voxagon.se/2024/12/29/year-summary.html ;渲染分析 https://juandiegomontoya.github.io/teardown_breakdown.html
- [I2] Control:https://www.gdcvault.com/play/1026820/Destructible-Environments-in-Control-Lessons ;https://80.lv/articles/gdc-talk-destructible-environments-in-control ;Northlight https://www.remedygames.com/northlight
- [I3] Rainbow Six Siege:https://www.gdcvault.com/play/1023003/The-Art-of-Destruction-in ;访谈 https://news.ubisoft.com/en-us/article/4GHX2yepSaKkflLjLAlpwO/the-art-of-destruction-in-rainbow-six-siege-an-interview-with-julien-lheureux
- [I4] Red Faction:https://www.redfactionwiki.com/wiki/Geo-Mod_2.0 ;https://www.cbsnews.com/news/red-faction-guerrilla-q-a/ ;https://www.gamespot.com/articles/red-faction-guerrilla-destructability-impressions/1100-6187787/
- [I5] Space Marine 2:https://www.thegamer.com/warhammer-40k-space-marine-2-chose-tyranids-world-war-z-engine/
- [I6] Unity 开源 Voronoi:https://github.com/sinanata/unity-mesh-fracture ;Niagara 破坏驱动 https://github.com/eanticev/niagara-destruction-driver ;hzFishy 笔记 https://notes.hzfishy.fr/Unreal-Engine/Physics/Types/Geometry-Collection
- [I7] Godot:https://docs.godotengine.org/en/stable/classes/class_rigidbody3d.html
基础算法与理论
- Bowyer–Watson https://en.wikipedia.org/wiki/Bowyer%E2%80%93Watson_algorithm ;Fortune https://en.wikipedia.org/wiki/Fortune%27s_algorithm ;Codeforces 教程 https://codeforces.com/blog/entry/85638
- Lloyd https://en.wikipedia.org/wiki/Lloyd%27s_algorithm ;Du/Faber/Gunzburger 1999 https://people.sc.fsu.edu/~mgunzburger/files_papers/gunzburger-cvt-siamreview.pdf
- Voro++ https://pubs.aip.org/aip/cha/article/19/4/041111/135670/VORO-A-three-dimensional-Voronoi-cell-library-in-C
- Bridson Poisson-disk https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf
- Disjoint-set https://en.wikipedia.org/wiki/Disjoint-set_data_structure
- Fracture Modes https://arxiv.org/abs/2111.05249 ;https://dl.acm.org/doi/10.1145/3549540
- Su/Schroeder/Fedkiw 2009 https://dl.acm.org/doi/10.1145/1599470.1599491
- Silling 2000 https://www.sciencedirect.com/science/article/abs/pii/S0022509699000290
- Mandelbrot–Passoja–Paullay 1984 https://www.nature.com/articles/308721a0
- Wwise 播放限制 https://www.audiokinetic.com/library/edge/?source=Help&id=limiting_object_playback_instances
