一面墙在模拟里不是几千块石头,而是一个刚体
大规模破碎的整个设计,都在回答同一个问题:怎样让上万个碎块看起来都存在,却只为被打到的那一小部分付出物理和渲染的成本。Chaos 的答案是:预先切好、按层级打包、用连接图决定谁挂在谁身上、只在载荷超过阈值时才逐层展开。
这页的每个演示都跑同一套规则,规则来自 UE 5.7.3 引擎源码(E:\Main\Engine)与 Epic 官方文档;为了能在浏览器里实时玩,教学版把三维简化成二维,但参数名、默认值和判定顺序保持一致。每条结论都标了出处:
源码 在引擎源码里读到,附文件与行号 官方 Epic 文档或发布说明 社区 论坛/博客,可信度较低 推断 由代码结构推出,未实测
读演示时的图例
线的粗细表示层级归属:粗线是刚体边界(一个刚体),中线是同一个簇里可以被单独掰下的子块,细线是子块内部的叶子碎块。
斜线填充是锚点 Anchored:它让所在的簇保持静止,直到与它的连接被剪断。
应变热度用冷蓝到红色表示「当前载荷 ÷ 该层阈值」,红色就是即将被掰下。
层级色固定:L0 深蓝、L1 青、L2 黄、L3 紫、更深为橙。
从资产到碎片:七个阶段
破碎
点 → Voronoi cell → 对源网格做布尔。每切一次加一层 Level。
簇化
K-Means 把相邻碎块聚成簇,强制连通;层级越深刚体越多。
邻接与凸包
Proximity 得出连接图;每个骨骼一个凸包,簇用少量凸包近似。
整体休眠态
未破碎的集合只是求解器里的 1 个刚体,所有子粒子 Disabled。
应变与释放
载荷 ≥ 阈值 → 子块脱离;剪边、做连通分量,悬空部分坠落。
回收
休眠/破碎后的计时移除,破裂数与释放数可以限流。
实例化
未破碎用整体代理,破碎后走 ISM 池或 Nanite,而不是每块一个 proxy。
PBDRigidClustering.cpp:253(子粒子 Disable)· GeometryCollectionPhysicsProxy.cpp:1648-1730(连接图读取/生成)
切割靠网格布尔,Voronoi 只负责出点
Fracture 工具看上去五花八门,落到引擎里其实只有一条路:把「点」变成一组凸多面体 cell,每个 cell 与整张源网格做一次 Intersect,再拆连通分量。
Voronoi(第三方库 voro++)只用来把站点变成 cell,源码自己假设它只占约 1% 的时间;真正的成本是每个 cell 对源网格的一次 FMeshBoolean。所有工具——Uniform、Radial、Cluster、Brick、Slice、Plane、Mesh——最后都归一到 FPlanarCells 或平面/切割网格,落地函数只有四个。源码
先用不同的站点分布看碎块长什么样
下面的 Voronoi 是真实的:每个单元由站点与所有邻居的中垂线裁出。换种分布、拖动面积阈值,注意「最大/最小」和变异系数——碎块大小越不均匀,越需要后面的 FixTinyGeo。
| 工具 | 点或平面怎么生成 | 默认值 | 要注意 |
|---|---|---|---|
| Uniform | 包围盒内 i.i.d. 均匀随机点 | 20 个点;UI 上限 5000 | 体积方差大,多出小碎块 推断;采样不看几何体内外,凹形会产生空 cell |
| Radial | 围绕冲击点的同心环 × 角度,环半径 ∝ (i+1)^Exponent | 5 环 × 5 角,半径 50 | 共圆会触发 voro++ 数值问题,源码给半径加 1e-4 抖动绕开 |
| Cluster | 先撒簇心,再在簇心周围的球壳上撒点(簇心本身不是站点) | 8 簇,每簇 2~30 点,半径 0.1~0.2 | 簇内密、簇间稀:一个大块群 + 局部碎屑 |
| Slice | 按轴均匀切片 + 角度/位置抖动,平面数是 X+Y+Z 而非乘积 | 3/3/1 → 32 块 | 每个平面作用于所有相交碎块,布尔数 ≈ 2×平面数×相交碎块数 |
| Brick | 按 Bond 规则错缝的 AABB 砖块 | 顺砖 194×57×92 cm | 最多 8192 块,超限自动放大砖尺寸 |
| Mesh | 用静态网格作切刀,可散布多个 | 散布 10 个,缩放 0.5~1.5 | 切割体必须闭合水密;不支持噪声 |
FractureToolUniform.h:19-28 · FractureToolRadial.h:36-77 · FractureToolClusterCutter.h:20-26 · FractureToolSlice.h:19-26 · FractureToolBrick.cpp:62-153 — 均在 Plugins/Experimental/ChaosEditor/Source/FractureEditor/Private
一个 Voronoi 单元是怎么算出来的
从整个包围盒出发,把其它站点按距离从近到远依次取来,用「到该站点的中垂线」把当前多边形裁小。
当某个站点的距离超过当前单元最远顶点距离的两倍,它的中垂线不可能再切到单元,后面的全部可以跳过——这就是演示里虚线圆的含义。voro++ 与 Blender 的 Cell Fracture 用的是同一个提前终止条件。源码
拖动站点,邻居数会随之变化。邻接关系就是单元的边的另一侧:连接图和 Proximity 都是从这里长出来的,不需要单独的 Delaunay。
三个会咬人的细节
- 噪声边缘在 cell 阶段烘进去。平面多边形细分到
PointSpacing后沿法线做 Perlin 位移;相邻 cell 共享同一噪声场与边界点,所以裂缝边缘保持闭合。编辑器默认 Amplitude = 0(关闭),PointSpacing = 10,开噪声时先增大间距控制三角形数。源码 - 多级破碎会保留每一级的隐藏几何。每次切割旧几何只置为不可见、不删除,内存随层数累积。推断
- Grout 对 Voronoi 只是近似。每个 cell 按最长轴等比缩小,缝宽并不精确;只有 Brick 与单平面是精确偏移。源码
多级破碎,每级少量站点,而不是一次切几万块:布尔成本随「cell 数 × 源网格复杂度」增长,且没有细粒度空间剔除;用 ChanceToFracture 让只有部分骨骼继续被切,得到疏密不均的结果;局部加密用 Cluster 或 Custom,而不是加大全局站点数。随后用 FixTinyGeo(默认相对体积 0.01,官方示例 0.04)清碎屑,再聚类、再生成凸包。源码 官方
层级决定刚体数量,连接图决定谁会掉下来
簇层级回答「同一时刻最多有多少刚体」,连接图回答「剪断之后谁还挂着」。两者都在编辑期定好,运行时只读。
簇层级:用 K-Means 把碎块打包
Auto Cluster 对某个簇的直接子节点的包围盒中心做 K-Means,再用邻接图把每个分区拆成连通分量,最后合并过小的分区。默认「强制连通」开启,避免出现被空气隔开的簇。源码 拖动层级按钮,观察每一层的刚体数:
读代码可见 AutoCluster 的连通性构建是 O(N²·平均邻居数)(对每个邻居做线性 Find),递归 DFS 在大连通分区上有栈深风险,且「是否在进步」的变量从未更新,会让 K 逐一递减、重复跑完整 K-Means。碎块上万时,分层聚类比一次聚成几十个簇更稳。均为静态阅读推断,未实测。推断
FractureEngineClustering.cpp:376(Find)· :320-330(递归 DFS)· :933, :971(PreviousPartitionCount)
连接图:支撑关系就是拓扑
连接图在编辑期由 Proximity 提升而来:叶子的邻接沿层级向上,在同父的兄弟之间建边,边上可以存接触面积。运行时只有资产里没有图,或打开 p.GeometryCollection.AlwaysGenerateConnectionGraph 时才现算。源码
| 方法 | 怎么连 | 特点 |
|---|---|---|
| PointImplicit | 把 j 的碰撞粒子变换到 i 的空间,落入 i 的隐式体内就连 | O(N²·P),依赖碰撞粒子 |
| DelaunayTriangulation | 子粒子位置做 Voronoi 邻居,全部连 | 注释标明非线程安全 |
| MinimalSpanningSubsetDelaunay(默认) | 近距离(500 cm × √ClusterConnectionFactor)的 Delaunay 边一律连;其余候选按距离排序,仅当能连通两个不同分量才连 | 名为最小生成子集,实际是「近距全连 + MST 式补桥」 |
| PointImplicitAugmentedWithMinimalDelaunay | 先 PointImplicit,再用上面的补桥 | GT 侧的生成器对它会命中 check(false) |
| BoundsOverlapFilteredDelaunay | Delaunay 邻接 + 世界包围盒相交才保留 | 用于 Cluster Union 跨 GC 的边 |
| None | 实现里被当作 MinimalSpanningSubset | 并非「不连」 |
ClusterCreationParameters.h:15-22 · PBDRigidClustering.cpp:2719-3068(各方法实现)· :2909(500 cm 常数)
剪断连接,悬空部分自己掉下来
下面的演示只做一件事:对连接图做并查集。在画布上拖动剪刀,剪断后与锚点失去连通的部分变红,随后作为独立刚体坠落。Chaos 里「结构倒塌」就是这个过程,没有应力求解。源码
运行时的三步
① 删除被释放子块的连接;② 对剩余子块做连通分量;③ 单块孤岛直接成为独立刚体,多块岛成为一个 internal cluster。岛内只要有一块 Anchored 或 Kinematic,整个岛就保持 Kinematic(挂住),否则变 Dynamic 坠落。
PBDRigidClustering.cpp:1310-1319, :1352-1575 · PBDRigidClusteringAlgo.cpp:147-228
它不会做什么
边不承载受力:悬挑、细长结构的受弯断裂不会自发出现,除非载荷真的把关键子块的阈值打穿。Epic 自己也说,Geometry Collection 不真正支持结构完整性:一整层楼可以只靠一根角柱站立,最后一根柱子没了上层才掉。社区(回复者疑似 Epic 员工)
源码里有一个按拉/压/剪应力比求解的 FClusterUnionStressSolver,但检索不到调用点,看起来是未接线的实验代码。推断
用 Anchored 表达固定,不要用 ObjectType = Static/Kinematic 把整个资产钉死:后者会把所有变换设成同一状态,释放后的叶子仍是该状态,不会自己落下。簇要沿结构组织(每层楼一簇 → 房间/墙体 → 更小的逻辑组),大块在上、碎片在下。源码 社区
应变是一次性的载荷,不是血量
每一步,簇里每个直接子节点都拿「本步载荷」跟「脱离阈值」比一次。超过就释放,没超过就什么都不留下。没有疲劳,没有累积。
PBDRigidClustering.cpp:1178-1179(判定)· :2226-2426(碰撞载荷)· :1247(清零)· GeometryCollectionObject.cpp:108(默认阈值)
点画布发射小球,或施加外部应变
演示里的阈值是教学单位,但「先掰大块、再碎小块」「一发只掰下一小块」「静止堆叠不累计」这三个行为与源码一致。试试把阈值改成「逐级递增」:大块比小块更难掰,结果会很怪。
五个问题
层级为什么省性能?
未破碎的集合只是求解器里 1 个刚体,子粒子全部 Disabled:不进宽相、不进约束图、不积分。刚体数量随「被打到的深度」增长,而不是随资产复杂度增长。源码
为什么先破大簇?
释放判定只看活跃簇的直接子节点,更深的子节点还是 Disabled,没有碰撞,收不到冲量,除非它们的父块先被释放。阈值逐级递减,则让一次撞击只掰下少数大块,大块落地再被碎开,把爆发摊成多帧。源码
「释放」到底是什么?
子粒子从父簇摘下并重新进入求解器;位姿由 ChildToParent × 父簇 还原,速度 = 父线速度 + 父角速度 × r。释放出的碎块当步就会被积分。
碰撞冲量怎么分给子块?
撞击点周围 100 cm(p.ClusterDistanceThreshold)内的子块每个都吃满这一冲量,不做分摊;开启伤害传播后碰撞只加给最近的那一块,其余靠传播扩散。静止接触(接近速度 < 1 cm/s)不计入。源码
伤害传播
默认开启:Break 系数 1.0、Shock 系数 0.0。Break 把「应变 − 阈值」的剩余部分按边数均摊传给邻居,Shock 把原始冲击的一个比例传给邻居且不均摊。邻居的新载荷下一步才结算,所以每步前进一跳,裂缝就是这样「爬」出去的。源码
想要疲劳怎么办?
默认模型没有。需要累计伤害,写一个 StrainModifier 回调(StrainModification.h)。源码
撞击者被脆弱结构「挡住」的动量,会在破裂后按 RestoreBreakingMomentumPercent(默认 0.5)补回一半,所以子弹会穿过薄墙而不是被刚体撞停。演示里的小球就带了这条规则。源码
PBDRigidClustering.cpp:69-70, :701-773
爆炸 = 一个带衰减的应变场 + 一个径向速度场
Field 在每个物理步开头对每个采样粒子求值,把结果写进应变、速度或状态。真正的断裂发生在同一步末尾,而且一步只击穿一层。
悬停画布,三个同心环就是「这一层刚好够掰下」的距离:强度 × 衰减曲线 ≥ 阈值的最大半径。切换 Falloff 看环如何移动,再点击引爆。把 Resolution 在 Minimal 与 Maximum 之间切换,对比每一步击穿几层。
衰减曲线与「每层击穿半径」
| Falloff | f(x) | x=0.25 | 0.5 | 0.75 |
|---|---|---|---|---|
| None | 1 | 1 | 1 | 1 |
| Linear | x | 0.25 | 0.50 | 0.75 |
| Squared | x² | 0.063 | 0.25 | 0.563 |
| Inverse | 2x/(x+1) | 0.40 | 0.667 | 0.857 |
| Logarithmic | log₂(1+x) | 0.322 | 0.585 | 0.807 |
FieldSystemNodes.cpp:440-479 · x = 1 − d/R,圆心为 1,边缘为 0
以默认阈值 {5×10⁵, 5×10⁴, 5×10³} 与强度 10⁶ 为例,各层能被击穿的最大距离占半径的比例:
| Falloff | 第 1 层 | 第 2 层 | 叶子 |
|---|---|---|---|
| Linear | 0.50 | 0.95 | 0.995 |
| Squared | 0.29 | 0.78 | 0.93 |
| Inverse | 0.67 | 0.97 | 0.998 |
| Logarithmic | 0.59 | 0.97 | 0.997 |
Squared 把大块断裂压在爆心附近、外圈只碎成小块,更像真实爆炸;Linear 与 Inverse 让外围也被大块断开。推导
一步里 Field 在哪个阶段生效
- 参数通道(物理步开头):写 ExternalStrain、速度、状态、Kill、阈值。Transient 命令只存活一次处理。PBDRigidsSolver.cpp:513-519
- 力通道(每个子步):LinearForce / AngularTorque。Transient 力只作用于第 1 个子步,要稳定的持续力请用 Persistent。PBDRigidsSolver.cpp:575-583
- 求解 · 睡眠 · 禁用阈值:Sleeping / Disable 阈值 Field 在步尾的 UpdateSleep / UpdateDisable 消费。PBDRigidsEvolutionGBF.cpp:801-804
- AdvanceClustering:载荷 ≥ 阈值则释放;子速度 += 父速度;ExternalStrain 清零;按预算限流。PBDRigidsEvolutionGBF.cpp:807 · PBDRigidClustering.cpp:1672-1897
- 事件:Break / Collision / Trailing 生成,经过滤器、双缓冲交给游戏线程,再到 Niagara 与声音。PBDRigidsSolver.cpp:624
速度类 Field 先写在仍然 Disabled 的子节点上,释放时与父速度相加继承。这就是「同一帧下发应变 + 径向速度」就能炸开的原因。源码
标准爆炸配方
| 作用 | 目标 | 节点 | 要点 |
|---|---|---|---|
| 锚定 | Field_DynamicState = Static | Culling + UniformInteger(3),挂到 GC 的 Initialization Field | 必须在初始化阶段建立(连接图要在模拟前创建);新流程里改用 Anchored |
| 径向应变 | Field_ExternalClusterStrain | RadialFalloff | 强度 ≥ 第一层阈值(默认 5×10⁵);取 max 不累加;≤0 被忽略 |
| 径向冲量 | Field_LinearVelocity | RadialVector × RadialFalloff(Multiply) | 蓝图模板默认速度 1200 cm/s;要质量相关的真实感用 LinearImpulse |
| 清理 | Field_Kill | BoxFalloff 或 PlaneFalloff | 值 > 0 即禁用;GC 级通道没有 Kill,只能走 solver 级 |
| 更快入睡 | Field_SleepingThreshold / DisableThreshold | Culling + UniformScalar | 必须裁剪,否则会把全场阈值踩成 0 |
GeometryCollectionPhysicsProxy.cpp:1452-1467(初始化 Field 强制 Maximum)· FieldSystemProxyHelper.h:502-583(写回语义)
Culling 的 Inside 是「结果为 0 的样本」,想只在球内生效要选 Outside。Resolution 默认 Minimal,只碰活跃簇和它的直接子节点,多层建筑要逐层再施加或改 Maximum。默认 Radius = 0 的 RadialFalloff 是惰性节点,什么都不写。UniformVector 默认方向是 (0,0,0),必须设方向。RandomVector 不可复现,联机或回放不要用。源码
Epic 的优化页指出复杂 Field 在量产里往往太贵,同时破坏大量物体会卡。更轻的做法是 Trace + Apply External Strain(可加 Apply Breaking Linear Velocity)代替 Field,Anchored/Kinematic 代替 Anchor Field,再配合碰撞,并针对高速投射物调参。5.7 还新增了 Leaf Sub-trace,可以拿到精确命中的叶子骨骼。官方
成本在「同时醒着的刚体数」,不在碎片总数
大规模破碎不是不让它碎,而是控制三件事:一帧最多碎多少、同一时刻最多有多少在动、碎完之后多久清掉。
反复引爆这面墙,观察右下的活动刚体曲线。关掉休眠,或把层级压成 1 层,看峰值怎么变;打开「每步最多击破簇数」,看爆发如何被摊开成多帧。
预算旋钮全家福
| 预算层 | 旋钮 | 默认 | 行为 |
|---|---|---|---|
| 创建(内存) | MaxSimulatedLevel / p.gc.GlobalMaxSimulatedLevel | 100 / 100 | 超过该层级的变换根本不创建粒子;设 1 则叶子永不进入 solver |
| 层级封顶 | MaxClusterLevel | 100 | 达到该层级的阈值变为 FLT_MAX,不可再破 |
| 爆发(单帧尖峰) | PerAdvanceBreaksAllowed + RescheduleLimit | INT_MAX | 每次 AdvanceClustering 最多击破的簇数;CVar 与项目设置取 Min |
| 总量(并发碎块) | ParticleReleaseThrottlingMin/MaxCount | -1(关) | 活跃粒子数超过 Min 后,按比例把新释放的碎块直接禁用(无衰减动画);CVar 与设置取 Max |
| 碎渣 | OneWayInteractionLevel | -1(关) | 该层级以下的块不对非碎渣施力,也不产生应变;碎渣之间用球体碰撞 |
| 应变来源 | bEnableDamageFromCollision | true | 装饰用集合关掉;静止堆叠接触已被 1 cm/s 过滤 |
GeometryCollectionComponent.h:1012-1020 · PBDRigidClustering.cpp:141-190, :1841-1878, :3094-3137 · ChaosSolverConfiguration.h:25-47
每步击破数的 CVar 与项目设置取 Min,释放节流的 CVar 与设置取 Max——所以用 CVar 只会比项目设置更严格。另外,超过节流上限的碎块是直接消失,没有衰减动画。源码
碎块的一生
- Intact:未破碎,Root 是 Kinematic / Static / Dynamic 之一。
- Broken:载荷 ≥ 阈值,子块以 Dynamic 释放,进入 Island,参与碰撞。
- Sleeping:Island 休眠。休眠不是不再同步,也不是不再渲染——粒子未 Disabled,在含脏粒子的集合里仍被缓冲、仍占渲染实例。真正释放资源要靠 Disable。源码
- Remove on Break / Remove on Sleep:计时在 [min, max] 内随机;Decay 0→1 期间缩放碎块(
Scale = 1 − Decay)。 - Decayed:粒子 Disabled,物理与游戏线程都不再同步;全部衰减完触发
OnFullyDecayedEvent。
| 参数 | 默认 |
|---|---|
bRemoveOnMaxSleep(Remove on Sleep) | false |
MaximumSleepTime · RemovalDuration | (5, 10) s · (2.5, 5) s |
bSlowMovingAsSleeping · SlowMovingVelocityThreshold | true · 1 cm/s(解决「抖而不睡」的碎块永不被清理) |
| Remove on Break 工具默认 | PostBreakTimer (0,0),RemovalTimer (2,3),ClusterCrumbling 关 |
p.Chaos.GC.RemovalTimerMultiplier | 1.0(>1 更快移除,可在后期统一缩放) |
| Partial Island Sleep(实验性) | 关;源码注释明确针对「大量静止刚体堆」 |
GeometryCollectionObject.h:788-814 · GeometryCollectionObject.cpp:137-143 · GeometryCollectionComponent.cpp:6679-6824 · IslandManager.cpp:56-115
特效与事件:在消费端限流
- GC 组件的
bNotifyBreaks等事件开关默认全部关。Breaking 数据只有在组件勾选了bNotifyBreaks或bNotifyGlobalBreaks时才会产出。源码 - solver 侧没有事件数量上限,限流发生在消费端:Niagara 的 Chaos Destruction 数据接口默认每 0.1 秒处理一次、每次最多 50 条(均匀抽稀)。做烟尘必须调大随机速度,默认值几乎为 0。源码
- 整栋崩塌的大音效用
OnChaosCrumblingEvent(一个簇一次),不要订阅几千条 Break。推断 - 官方的大规模示例:Chaos 事件 → Niagara Data Channel,配合 Remove on Break,用 Niagara 网格替代小碎片,105 个雕像保持在 30 FPS 以上。官方
别让每块碎片一个 proxy
一个集合里只要有一块在动,整个集合的变换都要重算并提交。所以拆分集合、用 ISM 池合批、让未破碎态用整体代理,是渲染侧三件最划算的事。
调整碎块总数、拆成几个集合与「有碎块在动」的比例,看三条路径每帧要处理的数据量。这是按源码复杂度关系推算的相对量,不是实测毫秒。
| 路径 | 触发 | 一次状态更新的单位 | 碎块网格 |
|---|---|---|---|
| Native | 无 CustomRenderer、未开 Nanite | 整个集合的骨骼矩阵(N × 64 B) | 渲染分割后的碎片,含内部面 |
| Native Nanite | EnableNanite 且有 Nanite 数据 | 每块刚体一个实例,每脏帧重建全部实例 | 同上;Lumen 不可见 |
| ISM Pool | CustomRendererType = ISMPoolRenderer | 共享池里的实例更新 | 渲染的是源网格,内部面不被渲染 |
| RootProxy | CustomRendererType = RootProxyRenderer | 未破碎:整体静态网格;破碎后回退 Native | 适合未破碎态走 Nanite/普通静态网格 |
GeometryCollectionComponent.cpp:1133-1178, :6512-6590 · GeometryCollectionSceneProxy.cpp:1593-1664 · GeometryCollectionISMPoolRenderer.cpp:65-296
ISM 池怎么合批
分组键是「静态网格 + 材质覆盖 + CustomPrimitiveData + 描述(剔除距离、LOD 偏置、阴影等)」。同一个关卡内用同一组合的所有集合共享同一批 ISM,这是最大的收益。默认描述不剔除、投影。源码
衰减到 0 的碎块在 ISM 里零缩放保留,并不会减少实例数。源码
5.7 里的名字变了
旧文档里的 RenderingMode / bUseISMPool 在 5.7 源码里不存在,被 CustomRendererType 取代;UGeometryCollectionISMPool* 自 5.6 起废弃,现役是 ISMPool 模块里的池。源码
内置 ISM 渲染器的 SetCustomInstanceData* 是空操作(没有类实现自定义数据接口);ISM 渲染器每次刷新是 O(M×N) 的扫描,资产里已缓存的 AutoInstanceTransformRemapIndices 并没有被用上;PreallocateMeshInstance 只有在 r.ISMPool.ComponentKeepAlive=1 时才有意义,且 GC 渲染器不会自动调用。源码
碎块多且网格重复 → ISMPoolRenderer + bStripRenderDataOnCook,并准备 RootProxyData 让未破碎态极便宜。碎块是独特几何、数量中等 → Native Nanite,未破碎态用 RootProxyRenderer,Nanite 剥离几何属性后文件大小约减少 95%。把大场景拆成多个集合,让整体休眠的集合在物理线程缓冲阶段成本为 0。源码 官方
大规模破碎检查清单
把前面所有章节压成可以逐项核对的做法。每条都带出处;没有出处的数值我没有写。
制作期
- 源网格水密、互不相交、模块化水密才能做可靠的布尔,相交会让求解器把物体推开。 官方
- 多级破碎,每级少量站点,用 ChanceToFracture 制造疏密每个 cell 对整张源网格做布尔,没有细粒度剔除。 源码
- 先 FixTinyGeo,再 AutoCluster,最后一次性生成凸包凸包默认每次编辑都会重算整个集合;Dataflow 下可以显式一次生成。 源码
- 簇沿结构组织,Level 1 用少量大块,不要在首次破碎里放几千块让碎片随建筑崩塌逐步脱落。 官方
- 碎块大小分档:大块 Convex,小碎渣用 Box / Sphere / Capsule 或 Reduction默认 Convex,Level Set 内存随分辨率立方增长;5.x 起改用 SizeSpecificData 配置。 官方 源码
- 碎块数量不要追求一次到位,砖墙保持 ≤ 8192超出会被自动放大砖尺寸。 源码
运行时
- 阈值逐级递减(粗簇高阈值、细碎片低阈值),必要时按尺寸分档默认 500000 / 50000 / 5000;尺寸分档配合 Shock 传播,让大块先吸收冲击。 源码 官方
- 用 Anchored / Kinematic 表达固定,不要把整个资产设成 Static后者释放后不会自动变 Dynamic。 推断
- 触发优先 Trace + External Strain + 碰撞,Field 留给需要空间控制的情形复杂 Field 在量产里往往太贵。 官方
- 装饰用集合关闭 bEnableDamageFromCollision,保持静止接触过滤碎片互撞继续碎裂会放大成本。 源码
- 用 MaxSimulatedLevel 砍掉不需要的叶子层,用 MaxClusterLevel 让最后几层不再破前者不创建粒子,后者阈值为 FLT_MAX。 源码
预算与清理
- 设置 PerAdvanceBreaksAllowed,并配 RescheduleLimit注意过小的 RescheduleLimit 可能让超额的簇被遗漏,直到再次被应变触发。 推断
- 设置 ParticleReleaseThrottlingMin/MaxCount 作为总量阀门超限碎块直接消失,没有衰减动画。 源码
- 开 Remove on Sleep / Remove on Break,并用全局倍率 CVar 后期统一缩放不用逐资产返工。 官方
- 用 OneWayInteractionLevel 把最细层变成碎渣,用 CollisionGroup 或按层级的碰撞 Profile 减少接触碎渣之间用球体碰撞,不再互相致伤。 源码
- 打开 slow-moving-as-sleeping,处理抖而不睡的碎块否则它们永远不会被回收。 源码
- 必要时开启 Partial Island Sleep(实验性)针对大量静置的无结构堆。 源码
渲染与特效
- 大场景拆成多个集合;整体休眠的集合在物理线程缓冲阶段成本为 0一块在动,整个集合都要重算。 源码
- 碎块重复 → ISM 池;独特几何 → Nanite;未破碎态用 RootProxy避免每块一个 proxy。 源码
- 控制「不同网格 + 材质覆盖」的数量每种组合多一个 ISM、多一次 Draw。 源码
- 只在需要特效/音效的集合上开 bNotify*,用 Niagara Data Channel 替换小碎片事件限流发生在消费端。 源码 官方
- 英雄镜头用 ChaosCaching 预烘焙,近处再用 Field 局部切 Dynamic 混合被切成 Dynamic 的粒子不再受缓存驱动。 源码
联机
- 不要同步每块碎片的物理,复制「哪些部分已被摧毁」,客户端自行重建视觉低带宽、后加入的玩家状态正确。 社区
- 若要复制 GC:勾 Enable Replication,关 Enable Abandon After Level,并让所属 Actor 开 Replicate Movement5.7.4 社区实测;官方没有专门文档。 社区
5.7 里变了什么,还有哪些没修
从 5.0 走到 5.7,创作路径、锚点、触发、渲染和清理都换了主角。下表只收有出处的条目。
| 方面 | 5.0–5.3 | 5.7(累积) |
|---|---|---|
| 创作 | Fracture Mode 手工为主 | Dataflow 为主推路径:变量、子图、For Each、时间轴 |
| 锚点 | Anchor Field / Initial Dynamic State = Static | Anchored / Kinematic,运行时蓝图可控制 |
| 触发 | Field 为主;5.1 起 Item Index 精确打击 | Trace + External Strain + 碰撞;5.7 Leaf Sub-trace |
| 伤害 | 阈值数组;5.1 起 Break / Shock 传播 | + 材质 Damage Model(5.3 实验)+ 材质修正 + 尺寸分档阈值 |
| 渲染 | 无 LOD;Nanite 需破碎后手动勾选 | 自动继承 Nanite、fallback、root proxy + ISM 渲染器 |
| 碰撞配置 | 每资产的 Implicit Type / Level Set 分辨率 | SizeSpecificData,旧属性 deprecated |
| 清理 | Remove on Sleep;5.1 加 Remove on Break | 全局倍率 CVar、组件级开关、automatic crumble |
| 成熟度 | 5.3 引入 Cluster Union | 5.4 起标注 Production Ready;Cluster Union 仍在开发 |
研究笔记 05-web-official-and-community.md §7 · 来源:Epic 发布说明 5.1–5.7
已知问题
- 5.7.0–5.7.2 存在相对 5.6 的 Chaos 性能回退(solver iteration count 被置为 −1),5.7.3 已修复。官方
- 5.7(至 5.7.3):Remove on Break / Sleep 计时结束后,集合仍被渲染却失去碰撞;5.7.4 热修复列表中有对应条目。社区
- Nanite + Nanite Fallback 同时开启后,GC Actor 不再收到物理更新,Epic 支持人员复现并提交了问题单(UE-302050)。社区
- Cluster Union:运行时用
AddComponentToCluster传入空 BoneIds 会得到「全有或全无」的整体破碎,且 Field 对其无效。社区 - Dataflow 在大规模块数下的运行时性能没有官方背书:论坛回复建议「块数要低」,让块保持 kinematic、被击中再切 dynamic。社区
这页是怎么做出来的,有哪些边界
检索范围
四路并行读 E:\Main\Engine(UE 5.7.3,Build.version 为 CL 298050):破碎生成、簇与应变运行时、Field 与事件缓存、渲染与预算;两路检索互联网:Epic 官方文档/发布说明/论坛,以及论文与业界方案。完整研究笔记已收录在博客的 Unreal Engine 笔记中。
教学版与 Chaos 的差别
| 方面 | 这里 | Chaos |
|---|---|---|
| 维度 | 二维凸多边形 | 三维凸体 / Level Set |
| 切割 | 半平面裁剪的 Voronoi | 网格布尔 + 内部面 + 噪声 |
| 求解 | 顺序冲量 + 位置修正 | PBD 系,GC 默认 8/1/1 迭代 |
| 阈值 | 教学单位 | 冲量(kg·cm/s) |
| 连接图 | Voronoi 邻接(边长) | 默认近距全连 + MST 补桥 |
| 撞击点 | 近似为半径内全部子块 | 碰撞点周围 100 cm 盒内 |
标「推断」的条目来自读代码,没有运行验证;各路研究的性能判断都是静态阅读所得,没有做实测。研究笔记里还各自列了未核实清单(源码疑点 16 条、Field 存疑 10 条、网络资料缺口 10 条),上线到项目前建议先实测。官方没有给出碎块数、Level 数与 Level Set 分辨率的预算建议,所以这页没有编造这类数值。