Chaos 大规模破碎:算法原理与最佳实践

点击墙面试试:一发小球只会掰下几块;一次 Field 引爆则让更深的层级一起展开。
叶子碎块 – · 当前刚体 – · 正在运动 – · 击破次数 –

一面墙在模拟里不是几千块石头,而是一个刚体

大规模破碎的整个设计,都在回答同一个问题:怎样让上万个碎块看起来都存在,却只为被打到的那一小部分付出物理和渲染的成本。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)^Exponent5 环 × 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)
BoundsOverlapFilteredDelaunayDelaunay 邻接 + 世界包围盒相交才保留用于 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 把整个资产钉死:后者会把所有变换设成同一状态,释放后的叶子仍是该状态,不会自己落下。簇要沿结构组织(每层楼一簇 → 房间/墙体 → 更小的逻辑组),大块在上、碎片在下。源码 社区

应变是一次性的载荷,不是血量

每一步,簇里每个直接子节点都拿「本步载荷」跟「脱离阈值」比一次。超过就释放,没超过就什么都不留下。没有疲劳,没有累积。

释放判定(每个活跃簇的每个直接子节点,每一步): max( CollisionImpulse , ExternalStrain ) >= InternalStrain( 层级 ) 载荷来源 : ① 碰撞接触的累积冲量 ② Field / Apply External Strain ③ 伤害传播 评估之后 : ExternalStrain 清零;碰撞冲量下一步整体重置 默认阈值 : { 500000 , 50000 , 5000 } // 越深的层级越小

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 之间切换,对比每一步击穿几层。

衰减曲线与「每层击穿半径」

Fallofff(x)x=0.250.50.75
None1111
Linearx0.250.500.75
Squaredx²0.0630.250.563
Inverse2x/(x+1)0.400.6670.857
Logarithmiclog₂(1+x)0.3220.5850.807

FieldSystemNodes.cpp:440-479 · x = 1 − d/R,圆心为 1,边缘为 0

以默认阈值 {5×10⁵, 5×10⁴, 5×10³} 与强度 10⁶ 为例,各层能被击穿的最大距离占半径的比例:

Falloff第 1 层第 2 层叶子
Linear0.500.950.995
Squared0.290.780.93
Inverse0.670.970.998
Logarithmic0.590.970.997

Squared 把大块断裂压在爆心附近、外圈只碎成小块,更像真实爆炸;Linear 与 Inverse 让外围也被大块断开。推导

一步里 Field 在哪个阶段生效

  1. 参数通道(物理步开头):写 ExternalStrain、速度、状态、Kill、阈值。Transient 命令只存活一次处理。PBDRigidsSolver.cpp:513-519
  2. 力通道(每个子步):LinearForce / AngularTorque。Transient 力只作用于第 1 个子步,要稳定的持续力请用 Persistent。PBDRigidsSolver.cpp:575-583
  3. 求解 · 睡眠 · 禁用阈值:Sleeping / Disable 阈值 Field 在步尾的 UpdateSleep / UpdateDisable 消费。PBDRigidsEvolutionGBF.cpp:801-804
  4. AdvanceClustering:载荷 ≥ 阈值则释放;子速度 += 父速度;ExternalStrain 清零;按预算限流。PBDRigidsEvolutionGBF.cpp:807 · PBDRigidClustering.cpp:1672-1897
  5. 事件:Break / Collision / Trailing 生成,经过滤器、双缓冲交给游戏线程,再到 Niagara 与声音。PBDRigidsSolver.cpp:624

速度类 Field 先写在仍然 Disabled 的子节点上,释放时与父速度相加继承。这就是「同一帧下发应变 + 径向速度」就能炸开的原因。源码

标准爆炸配方

作用目标节点要点
锚定Field_DynamicState = StaticCulling + UniformInteger(3),挂到 GC 的 Initialization Field必须在初始化阶段建立(连接图要在模拟前创建);新流程里改用 Anchored
径向应变Field_ExternalClusterStrainRadialFalloff强度 ≥ 第一层阈值(默认 5×10⁵);取 max 不累加;≤0 被忽略
径向冲量Field_LinearVelocityRadialVector × RadialFalloff(Multiply)蓝图模板默认速度 1200 cm/s;要质量相关的真实感用 LinearImpulse
清理Field_KillBoxFalloff 或 PlaneFalloff值 > 0 即禁用;GC 级通道没有 Kill,只能走 solver 级
更快入睡Field_SleepingThreshold / DisableThresholdCulling + 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 不可复现,联机或回放不要用。源码

官方的建议是少用 Field

Epic 的优化页指出复杂 Field 在量产里往往太贵,同时破坏大量物体会卡。更轻的做法是 Trace + Apply External Strain(可加 Apply Breaking Linear Velocity)代替 Field,Anchored/Kinematic 代替 Anchor Field,再配合碰撞,并针对高速投射物调参。5.7 还新增了 Leaf Sub-trace,可以拿到精确命中的叶子骨骼。官方

成本在「同时醒着的刚体数」,不在碎片总数

大规模破碎不是不让它碎,而是控制三件事:一帧最多碎多少、同一时刻最多有多少在动、碎完之后多久清掉。

反复引爆这面墙,观察右下的活动刚体曲线。关掉休眠,或把层级压成 1 层,看峰值怎么变;打开「每步最多击破簇数」,看爆发如何被摊开成多帧。

预算旋钮全家福

预算层旋钮默认行为
创建(内存)MaxSimulatedLevel / p.gc.GlobalMaxSimulatedLevel100 / 100超过该层级的变换根本不创建粒子;设 1 则叶子永不进入 solver
层级封顶MaxClusterLevel100达到该层级的阈值变为 FLT_MAX,不可再破
爆发(单帧尖峰)PerAdvanceBreaksAllowed + RescheduleLimitINT_MAX每次 AdvanceClustering 最多击破的簇数;CVar 与项目设置取 Min
总量(并发碎块)ParticleReleaseThrottlingMin/MaxCount-1(关)活跃粒子数超过 Min 后,按比例把新释放的碎块直接禁用(无衰减动画);CVar 与设置取 Max
碎渣OneWayInteractionLevel-1(关)该层级以下的块不对非碎渣施力,也不产生应变;碎渣之间用球体碰撞
应变来源bEnableDamageFromCollisiontrue装饰用集合关掉;静止堆叠接触已被 1 cm/s 过滤

GeometryCollectionComponent.h:1012-1020 · PBDRigidClustering.cpp:141-190, :1841-1878, :3094-3137 · ChaosSolverConfiguration.h:25-47

CVar 只能「收紧」,不能「放宽」

每步击破数的 CVar 与项目设置取 Min,释放节流的 CVar 与设置取 Max——所以用 CVar 只会比项目设置更严格。另外,超过节流上限的碎块是直接消失,没有衰减动画。源码

碎块的一生

  1. Intact:未破碎,Root 是 Kinematic / Static / Dynamic 之一。
  2. Broken:载荷 ≥ 阈值,子块以 Dynamic 释放,进入 Island,参与碰撞。
  3. Sleeping:Island 休眠。休眠不是不再同步,也不是不再渲染——粒子未 Disabled,在含脏粒子的集合里仍被缓冲、仍占渲染实例。真正释放资源要靠 Disable。源码
  4. Remove on Break / Remove on Sleep:计时在 [min, max] 内随机;Decay 0→1 期间缩放碎块(Scale = 1 − Decay)。
  5. Decayed:粒子 Disabled,物理与游戏线程都不再同步;全部衰减完触发 OnFullyDecayedEvent。
参数默认
bRemoveOnMaxSleep(Remove on Sleep)false
MaximumSleepTime · RemovalDuration(5, 10) s · (2.5, 5) s
bSlowMovingAsSleeping · SlowMovingVelocityThresholdtrue · 1 cm/s(解决「抖而不睡」的碎块永不被清理)
Remove on Break 工具默认PostBreakTimer (0,0),RemovalTimer (2,3),ClusterCrumbling 关
p.Chaos.GC.RemovalTimerMultiplier1.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 NaniteEnableNanite 且有 Nanite 数据每块刚体一个实例,每脏帧重建全部实例同上;Lumen 不可见
ISM PoolCustomRendererType = ISMPoolRenderer共享池里的实例更新渲染的是源网格,内部面不被渲染
RootProxyCustomRendererType = 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.35.7(累积)
创作Fracture Mode 手工为主Dataflow 为主推路径:变量、子图、For Each、时间轴
锚点Anchor Field / Initial Dynamic State = StaticAnchored / 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 Union5.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 分辨率的预算建议,所以这页没有编造这类数值。