外观
Field、事件与缓存
约 9215 字大约 31 分钟
2026-10-14
范围:物理场(Field System)、破碎事件、Niagara/声音联动、缓存录制回放,以及「爆炸破碎」组合配方。 方法:全部结论来自对
E:\Main\Engine源码的实际阅读,行号为本地树实际行号。 标记:[推断] = 由代码逻辑推出、未做运行验证;[存疑] = 疑似 bug / 文档与实现不一致。
0. 约定
0.1 路径缩写(均相对 E:\Main\Engine)
| 缩写 | 展开 |
|---|---|
CH/ | Source/Runtime/Experimental/Chaos/ |
FS/ | Source/Runtime/Experimental/FieldSystem/Source/FieldSystemEngine/ |
GCE/ | Source/Runtime/Experimental/GeometryCollectionEngine/ |
CSE/ | Source/Runtime/Experimental/ChaosSolverEngine/ |
ENG/ | Source/Runtime/Engine/ |
HC/ | Source/Programs/HeadlessChaos/Private/GeometryCollection/ |
PX/ | Plugins/Experimental/ |
FN/ | Content/EditorResources/FieldNodes/(引擎自带的 Field 蓝图模板,.uasset,内容为字符串提取) |
0.2 版本注记
源码树里出现 UE_DEPRECATED(5.8, …)(CH/Public/GeometryCollection/GeometryCollectionSimulationCoreTypes.h 约 445–448 行),说明实际接近 5.8 分支;下文行号以本树为准。
0.3 一页纸结论(演示页可直接用)
- Field = GT 上构图(UObject 节点)→ 深拷贝成原生
FFieldNodeBase树 → 打包成FFieldSystemCommand→ 入队到物理线程 → 每个 solver step 开头对「采样粒子集合」求值 → 按目标类型(EFieldPhysicsType)写回粒子。 - 求值是 按粒子采样(位置 → 标量/向量/整数),没有网格;成本 ∝ 采样粒子数,不是半径(半径只做事后过滤)。
- 目标分两条通道:参数通道(step 开头:状态/应变/速度/阈值/Kill)与力通道(每个子步:LinearForce/AngularTorque)。
- 破碎 = 应变模型:
max(ExternalStrain, CollisionImpulse) ≥ 子节点 InternalStrain即释放;Field 的ExternalClusterStrain只是往这个「应变」槽里写值,真正的断裂发生在同一 step 末尾的AdvanceClustering,且每个 step 只击穿一层。 - 速度类 Field 先写到尚处于 Disabled 的子节点上,释放时被「父速度 + 自身速度」累加继承(
RemoveChildFromParent)——这是「应变 + 径向速度」同帧下发就能炸开的原因。 - 事件(Break/Collision/Trailing/Removal/Crumbling)由 PT 在 step 末尾
FillProducerData生成,经过滤器 → 双缓冲 → GT 分发;GC 组件必须勾bNotifyBreaks(或 Global 版)才会产出FBreakingData。 - Niagara
Chaos Destruction DataDI 默认 每 0.1s 处理一次、每次最多 50 条事件(均匀抽稀);这是「大规模」下特效的天然限流器。 - 缓存:5.5 起旧的
UGeometryCollectionCache/FRecordedTransformTrack录放路径已标记 No longer used;5.7 用 ChaosCaching 插件(Experimental),回放靠 Kinematic Target,且「被 Field 切成 Dynamic 的粒子不再受缓存驱动」→ 天然支持「缓存 + 实时」混合。 - 大规模破碎的三个闸门:
PerAdvanceBreaksAllowed(每步断裂数)、ClusteringParticleReleaseThrottling{Min,Max}Count(释放碎片节流)、MaxSimulatedLevel(不创建最细层物理体)。 - 用户清单里的
Field_AnchorVelocity、Field_ClusterBreak_*在 5.7 不存在;锚定走「初始化 Field(Field_DynamicState=Static)」或新式Anchored属性,断裂走Field_ExternalClusterStrain/Field_InternalClusterStrain。
1. Field System
1.1 分层与数据流
GT UFieldSystemComponent / AFieldSystemActor / UGeometryCollectionComponent::ApplyPhysicsField
│ UFieldNodeBase::NewEvaluationGraph() // UObject 图 → 原生 FFieldNodeBase 树,带环检测
│ FFieldObjectCommands::CreateFieldCommand()// 打包 FFieldSystemCommand{PhysicsType, RootNode, MetaData, BoundingBox, MaxMagnitude}
│ DispatchFieldCommand() // 深拷贝(NewCopy)后 EnqueueCommandImmediate
▼
PT FPerSolverFieldSystem {TransientCommands, PersistentCommands} // 世界/solver 级
FGeometryCollectionPhysicsProxy::FieldData.Commands // 单个 GC 级
│ BuildFieldSamplePoints() → 采样粒子 + 位置 + 索引
│ FFieldContext(时间 = SolverTime − Command.TimeCreation, MetaData)
│ RootNode->Evaluate(Context, Results)
└─ Chaos::Field{Integer|Scalar|Vector}ParameterUpdate / FieldVectorForceUpdate → 写粒子| 环节 | 位置 |
|---|---|
| 节点基类(UObject 侧) | FS/Public/Field/FieldSystemObjects.h:124-173(UFieldNodeBase/Int/Float/Vector) |
原生节点基类 FFieldNodeBase / FFieldNode<T>::Evaluate | CH/Public/Field/FieldSystem.h:403-506 |
命令 FFieldSystemCommand(拷贝构造即深拷贝,因为跨线程) | CH/Public/Field/FieldSystem.h:522-623(拷贝 556-572) |
CreateFieldCommand(含 BuildCommandBounds) | FS/Public/Field/FieldSystemObjects.h:1012-1046;ENG/Private/PhysicsField/PhysicsFieldComponent.cpp:984-996 |
| 下发 | FS/Private/Field/FieldSystemComponent.cpp:158-232(DispatchFieldCommand) |
| PT 求值 | CH/Private/PhysicsProxy/PerSolverFieldSystem.cpp:28-179;GC 版 CH/Private/PhysicsProxy/GeometryCollectionPhysicsProxy.cpp:6130-6388 |
| 写回粒子 | CH/Public/PhysicsProxy/FieldSystemProxyHelper.h:381-791 |
求值上下文 FFieldContext(FieldSystem.h:241-338):SampleIndices{Sample,Result}(:21-46,Sample=粒子下标,Result=结果槽下标,二者分离是为了 Culling 后仍能对齐)、SamplePositions、TimeSeconds、MetaData、三组结果缓冲(Vector/Scalar/Integer,FieldSystem.h:75-100 的 FFieldExecutionDatas 复用内存避免每帧分配)。GetEvaluatedSamples() 在 Culling 生效时返回裁剪后的索引(:308-318)。
1.2 一帧里 Field 落在哪个物理阶段(CH/Private/PBDRigidsSolver.cpp AdvanceOneTimeStepTask::DoWork)
:505 ApplyCallbacks_Internal // 含 GT 经 EnqueueCommandImmediate 入队的 Field 命令(AddTransient/PersistentCommand)
:513 Solver.FieldParameterUpdateCallback // ★ 参数通道:状态/应变/Kill/阈值/速度/冲量 → 先 Transient 后 Persistent
:516 对每个登记过 Field 的 GC proxy:FieldParameterUpdateCallback
for 子步 in 0..MaxSubSteps:
:575 Solver.FieldForcesUpdateCallback // ★ 力通道:LinearForce/AngularTorque (AddForce/AddTorque)
:578 GC proxies FieldForcesUpdateCallback
:583 GC proxy 列表 Reset() // "每个 step 只处理一次",避免同一批力被多个子步重复施加
:591 Evolution.AdvanceOneTimeStep(dt)
└ PBDRigidsEvolutionGBF.cpp: Integrate(:581) → UpdateConstraintPositionBasedState(:604) → 碰撞检测 → 求解
→ PostSolve → UpdateSleep/UpdateDisable(:801-804) // 睡眠/禁用阈值在此消费
→ Clustering.AdvanceClustering(:807) // ★ 应变在此消费:释放子节点
:624 EventManager.FillProducerData // Break/Collision/... 事件在此生成要点:
- Transient 命令只存活一次处理:处理后立刻从列表删除(
PerSolverFieldSystem.cpp:91-96, 161-167)。力通道随子步调用,所以 transient 力只作用于第 1 个子步。 - Persistent 命令每个 step 都重新求值(
:110-111, 176-177),直到RemovePersistentFields。FFieldContext.TimeSeconds = SolverTime − TimeCreation(:54, 140)使「随时间演化的场」(WaveScalar)成立。 - 同一帧内多条命令按入队顺序执行;不同目标之间无数据依赖,唯一的顺序依赖:
DynamicState/ActivateDisabled应先于速度类(速度写入前检查IsDynamic(),FieldSystemProxyHelper.h:616, 639, 664)。
1.3 采样粒子集合:ProcessingResolution / Filter / PositionType
BuildFieldSamplePoints(CH/Public/PhysicsProxy/FieldSystemProxyHelper.h:30-151):
| 元数据 | 默认 | 语义 | 位置 |
|---|---|---|---|
UFieldSystemMetaDataProcessingResolution | Minimal(:44-48) | Minimal=所有活动粒子 + 其直接子节点(AddClusterChildren,PerSolverFieldSystem.cpp:396-414, 424-436);DisabledParents=仅顶层簇父节点(:437-447);Maximum=solver 内全部粒子(:448-460) | 枚举 CH/Public/Field/FieldSystemTypes.h:71-79;UObject 包装 FieldSystemObjects.h:62-82 |
UFieldSystemMetaDataFilter(State/Object/Position) | 无(Filter_Max) | 给了 Filter 就忽略 Resolution 走 GetFilteredParticleHandles(:67-74;PerSolverFieldSystem.cpp:475-599)。State:Dynamic/Kinematic/Static/All/Sleeping/Disabled/DynamicAndSleeping;Object:Rigid/Cloth/Destruction/Character/All | FieldSystemTypes.h:85-113;FieldSystemObjects.h:87-119 |
| PositionType | 未设置 ⇒ 用粒子 pivot GetX()(不是质心)(:124-145) | CenterOfMass 需显式给 Filter | FieldSystemTypes.h:119-126 |
UFieldSystemMetaDataIteration | — | 源码注释「Not used anymore」,无效 | FieldSystemObjects.h:33-57 |
采样细节:
- Disabled 的簇子节点(本地坐标)会沿
ChildToParent链回溯到活动父节点,算出世界坐标再做采样(:85-112)——所以 Field 能「看见」尚未释放的子块。 FieldCommand.BoundingBox只是对已枚举粒子的过滤(:114),枚举本身仍是 O(全部非禁用粒子)(PerSolverFieldSystem.cpp:424-436)。FRadialFalloff仅当Default==0才给出有限包围盒(FieldSystemNodes.cpp:587-592);Plane/RadialVector/Uniform 等是无限包围盒。- GC 级通道(
GetRelevantParticleHandles@GeometryCollectionPhysicsProxy.cpp:2358-2432)只枚举该 GC 自己的SolverParticleHandles;solver 级通道枚举整个 solver。
1.4 Falloff 曲线(FieldSystemNodes.cpp:440-479)
设 x = 1 − d/R ∈ (0,1](d 为到中心距离,圆心处 x=1,边缘处 x=0)。节点输出 = Magnitude × (MinRange + (MaxRange−MinRange) × f(x))(ScaleFunctionResult,:440-443)。
EFieldFalloffType | f(x) | x=0.25 | 0.5 | 0.75 | 特征 |
|---|---|---|---|---|---|
None (Field_FallOff_None) | 1 | 1 | 1 | 1 | 体内恒定,边缘断崖到 Default |
Linear | x | 0.25 | 0.50 | 0.75 | 默认 |
Squared | x² | 0.0625 | 0.25 | 0.5625 | 能量集中在近处,外围衰减快 |
Inverse | 2(1−1/(x+1)) = 2x/(x+1) | 0.40 | 0.667 | 0.857 | 外围「撑得远」 |
Logarithmic | log₂(1+x) | 0.322 | 0.585 | 0.807 | 介于 Linear 与 Inverse 之间 |
注意:
Inverse的 UI 提示写「∝1/x」,实现是上面的归一化形式(:470-473),两端归一到 0 与 1。 边界:Delta < Radius严格小于才进入(:500);Radius ≤ 0时整个 Evaluator 什么都不写(:488)→ 默认Radius=0的 RadialFalloff 是惰性节点。
1.5 节点参考(UObject 默认值 ← 构造函数;求值 ← FieldSystemNodes.cpp)
| 节点 | 输出 | UObject 默认(FieldSystemObjects.h) | 求值要点(FieldSystemNodes.cpp) |
|---|---|---|---|
UUniformInteger | int | Magnitude=0(:185-188) | 所有样本写常量(:113-122) |
URadialIntMask | int | Radius=0, Interior=1, Exterior=0, Set_Always(:216-223) | 球内=Interior 否则 Exterior;Merge Interior/Exterior 依据已有结果条件覆盖(:161-204)。Radius=0+Interior=Exterior=v 可当「全体常量」用(测试 HC/…SimulationField.cpp:469-474 把 CollisionGroup 全设 −1) |
UUniformScalar | float | Magnitude=1(:277-280) | 常量 |
UWaveScalar | float | Mag=1, Wavelength=10000, Period=1, Cosine, Linear(:308-316);C++ 构造默认 Wavelength=1000 | 前沿半径 R(t)=Wavelength·t/Period(波速 = Wavelength/Period cm/s)(:314-319);Cosine/Gaussian/Falloff/Decay 四种(:326-373)。Falloff 模式 = 随 R(t) 扩张的实心球衰减场 |
URadialFalloff | float | Mag=1, Min=0, Max=1, Default=0, Radius=0, Linear(:376-385) | 见 1.4 |
UPlaneFalloff | float | Mag=1, Min0/Max1, Default0, Distance=0, Normal=(0,0,1)(:449-459) | 受影响区是平面背面(Delta=PlaneDot(p) 满足 −Distance < Delta < −ε,f=1+Delta/Distance,:612-615);与 UFUNCTION 注释「沿法线方向延伸」不一致 [存疑];Distance≤0 时不写(:601) |
UBoxFalloff | float | Mag=1, Min0/Max1, Default0, Transform=Identity(:529-537) | 单位盒 = 100cm 立方(HalfBox=50,:720),在局部空间内 f = 离最近面距离/50(面上=0,中心=1)(:729-736)。盒的尺寸 = Transform.Scale×100cm |
UNoiseField | float | Min=0, Max=1(:596-601);C++ FNoiseField 构造默认 Max=0(FieldSystemNodes.h:384)→ 直接 new FNoiseField() 恒输出 0 | Perlin;取局部坐标小数部分→ 以 Transform 的 1 个单位为周期平铺(:852-854);无空间范围限制(与 UI 提示「仅在盒内」不符 [存疑]);无 Magnitude 参数 |
UUniformVector | vec | Mag=1, Direction=(0,0,0)(:640-644)→ 默认输出零向量,必须设方向 | Mag·Dir(:919-929),方向不归一化 |
URadialVector | vec | Mag=1, Pos=0(:678-682) | Mag·(p−Pos).GetSafeNormal(),向外(:981);FieldSystemObjects.h 里类 ToolTip(:672,「from the sample position to the field one」)与 UFUNCTION 注释(:689,「from the field position to the sample」)互相矛盾,以代码为准 |
URotatedRadialVector | vec | Mag=1, Angle=0, Axis=Z(:715-721) | 径向向量绕 Axis 旋转 Angle°(:1040-1048)→ 做涡旋/切向冲量 |
URandomVector | vec | Mag=1(:767-770) | 随机单位向量·Mag(:1117-1121,FMath::RandRange,不确定性) |
UOperatorField | float 或 vec | Mag=1, Multiply(:798-804) | 运行时生成 FSumScalar/FSumVector(FieldSystemObjects.cpp:310-403)。标量×向量无论 Operation 选什么都按乘法(FSumVector 的 Scalar 分支固定 *=,FieldSystemNodes.cpp:1463-1480);Operation 只作用于 Left/Right 两个向量;Divide 无零保护(:1214, 1429) |
UToIntegerField / UToFloatField | int / float | — | FConversionField 强转(:1645-1669):float→int 为 C 截断,所以 0<v<1 变 0 |
UCullingField | 同 Input | Inside(:912-917) | 先求 Culling(float);Inside=Culling 结果==0 的样本;Outside=≠0(:1773-1786)。因此用 RadialFalloff(球外=0)做裁剪、想「只在球内生效」要选 Outside(测试用法 HC/GeometryCollectionTestFields.cpp:890)[易错]。命中子集写入 CullingIndices,下游只在该子集上写回 |
UReturnResultsTerminal | 取决于上游 | — | 读取「父节点当前结果」(FFieldSystemMetaDataResults);仅能接在 Operator 的输入下(FieldSystemObjects.cpp:518-549)。DynamicState/ActivateDisabled 的结果在求值前被预置为当前状态(FieldSystemProxyHelper.h:159-185),标量目标先清 0(PerSolverFieldSystem.cpp:74) |
环检测:同一个 UObject 节点在一条求值链里只能出现一次(NewEvaluationGraph 的 Nodes.Contains(this),如 FieldSystemObjects.cpp:55)→ 想对两路输入复用同一形状,要创建两个节点实例。
1.6 目标类型 EFieldPhysicsType(CH/Public/Field/FieldSystemTypes.h:148-173)
| 目标 | 输出类型 | 生效阶段 | 写回语义(FieldSystemProxyHelper.h) | 备注 |
|---|---|---|---|---|
Field_DynamicState | int | 参数 | 写 EObjectStateType(Sleeping=1, Kinematic=2, Static=3, Dynamic=4,CH/Public/Chaos/Particle/ObjectState.h:9-15);变化后向上更新顶层簇的 Kinematic 属性(:388-417, 270-309) | GC 级额外同步 DynamicCollection.DynamicState(GeometryCollectionPhysicsProxy.cpp:6176-6226);Dynamic 仅当质量>ε 才生效(:238-239) |
Field_ActivateDisabled | int | 参数 | Disabled() && 结果==0 时 EnableParticle+Dynamic(:418-435) | 结果预置为当前 Disabled 位,所以「写 0」= 复活(与 Kill 互逆) |
Field_CollisionGroup | int | 参数 | 仅 Clustered 粒子 SetCollisionGroup(:436-450) | 测试:置 −1 后物体穿地(HC/…SimulationField.cpp:440-502);可作为大规模碎片的「幽灵化」开关 |
Field_PositionStatic | int | 参数 | 往局部 PositionTarget 约束加点(:451-475) | solver 里 PositionTarget 注释为「Dummy for now」(PBDRigidsSolver.cpp:510),GC 里是栈上局部(GeometryCollectionPhysicsProxy.cpp:6136)→ 实质无效 [存疑](Hidden 枚举) |
Field_ExternalClusterStrain | float | 参数 | value>0 时 SetExternalStrain(max(旧,新))(:502-521) | 取 max 不累加;≤0 被忽略,所以 RadialFalloff 球外 Default=0 安全 |
Field_InternalClusterStrain | float | 参数 | InternalStrain += value(仅 Dynamic 簇,:568-583) | 累加;负值=削弱连接(测试 Magnitude=−100,HC/…SimulationField.cpp:1150-1298);Persistent 即「逐帧腐蚀」 |
Field_Kill | float | 参数 | value>0 → Evolution->DisableParticle(:522-536) | GC 专用枚举 EGeometryCollectionPhysicsTypeEnum 里没有 Kill / ActivateDisabled(GeometryCollectionSimulationTypes.h:45-63)→ 只能走 solver 级 UFieldSystemComponent |
Field_SleepingThreshold / Field_DisableThreshold | float | 参数(在 PostSolve 的 UpdateSleep/UpdateDisable 消费,PBDRigidsEvolutionGBF.cpp:801-804) | 对每个被求值样本写每粒子物理材质的线/角阈值(:317-369, 537-567) | 见坑 §1.8 #4、#5 |
Field_LinearVelocity | vec | 参数 | V += ΔV,睡眠者被唤醒(:605-627) | 与质量无关,结果最可控(BP 注释「Velocity 更统一、可预测」) |
Field_LinearImpulse(Hidden;GC 枚举有 Chaos_LinearImpulse) | vec | 参数 | LinearImpulseVelocity += Δ·InvM(:628-651) | 与质量有关:重块慢、轻块快(真动量语义) |
Field_AngularVelociy(原名拼写如此) | vec | 参数 | W += ΔW(:652-675) | |
Field_LinearForce / Field_AngularTorque | vec | 力(子步) | 需 ChaosFieldEnabled() 且非 Disabled,睡眠者被转 Dynamic 后 AddForce/AddTorque(:711-754) | 持续力(风/上升气流)。力随质量变化 |
Field_InitialLinearVelocity / InitialAngularVelocity(Hidden) | vec | 初始化 | 写入 DynamicCollection 的初速度属性,要求 InitialVelocityType=User Defined(GeometryCollectionPhysicsProxy.cpp:6234-6308) | 仅 GC 初始化命令用 |
Field_PositionTarget / PositionAnimated / DynamicConstraint | — | — | 同 PositionStatic 无效;PositionAnimated/DynamicConstraint 直接打 Error「not supported」(:476-482, 701-707) | 不要用 |
结论:5.7 可用于破碎的目标就是 8 类:DynamicState、ActivateDisabled、CollisionGroup、External/InternalClusterStrain、Kill、两个 Threshold、LinearVelocity/Impulse、AngularVelocity、LinearForce/Torque、Initial*Velocity。
1.7 三条下发通道对比
| 通道 | 入口 | 作用范围 | 备注 |
|---|---|---|---|
solver 级 UFieldSystemComponent::ApplyPhysicsField(瞬时) / AddPersistentField(持续) | FieldSystemComponent.h:202, 226;.cpp:158-232 | SupportedSolvers 为空=世界默认 solver 全体粒子(:49-74)。bIsChaosField=true(默认,.cpp:26) | bIsWorldField=false(默认,.cpp:25);置 true 则命令也推到 GPU 世界场供 Niagara/材质采样(:197-229) |
GC 级 UGeometryCollectionComponent::ApplyPhysicsField | GCE/Public/GeometryCollection/GeometryCollectionComponent.h:1141;.cpp:5971-6000 | 仅该 GC proxy 的粒子(BufferFieldCommand_External → GeometryCollectionPhysicsProxy.cpp:6104-6128) | 目标枚举是 EGeometryCollectionPhysicsTypeEnum(13 项,无 Kill) |
初始化/构造 Field AFieldSystemActor + GC 的 InitializationFields | GeometryCollectionComponent.h:965;.cpp:6002-6056 → SimulationParameters.InitializationCommands(.cpp:4070) | proxy 初始化时执行一次 | 强制改成 Resolution_Maximum(GeometryCollectionPhysicsProxy.cpp:1452-1467),且在建簇层级之前(:1493)→ 这就是「锚点」机制;AddFieldCommand=构造期命令(FieldSystemComponent.cpp:298-324) |
| 世界 PhysicsField(GPU 剪辑图) | ENG/Private/PhysicsField/PhysicsFieldComponent.cpp | Niagara DI Physics Field、材质 | r.PhysicsField.ClipmapResolution 默认 32(:83-88) |
便捷 API(均为 transient、solver 级):ApplyStrainField(RadialFalloff→ExternalStrain,Iterations 形参实际未使用,FieldSystemComponent.cpp:384-392)、ApplyRadialVectorFalloffForce(= FSumVector(Scalar=RadialFalloff, Vector=RadialVector, Multiply),:358-369——正是「径向 × 衰减」的标准做法)、ApplyStayDynamicField(RadialIntMask Interior=Dynamic/Exterior=Kinematic/IFF_NOT_Interior,:326-336)。
1.8 坑点清单
- Resolution 默认 Minimal:一次性 Field 只能碰到「活动簇 + 直接子节点」,多层级建筑需要逐层再施加或改 Maximum(见 §2.1/§5)。
- Culling 的 Inside/Outside 命名反直觉(见 1.5)。
- 阈值类 Field 必须配 Culling:
Sleeping/Disable对每个被求值样本都写值,包括数值为 0 的球外样本,会把全场阈值「踩成 0」。BP 模板注释原文:"it is critical that you apply culling outside of it… failing to cull this field would stomp any other such fields in the map"(FN/FS_SleepDisable_Generic.uasset)。Kill / ExternalStrain 因为判>0所以无此问题。 - 每粒子物理材质分配:粒子没有 per-particle 材质时,阈值 Field 会
MakeUnique<FChaosPhysicsMaterial>新建并SetPhysicsMaterial(FieldSystemProxyHelper.h:319-327, 349-358);十万级碎片会产生同量级堆分配,新建材质的 Friction=0.5/Restitution=0.1 等为默认值(CH/Public/Chaos/Defines.h:125-143)[推断:可能覆盖原材质属性,需实测]。 - [存疑]
UpdateMaterialSleepingThreshold的 else 分支比较的是DisabledLinearThreshold(FieldSystemProxyHelper.h:333)——应是 Sleeping;因 Disabled 默认为 0,当要设的睡眠阈值恰好等于该值时会被跳过。 - Sleep 阈值默认 1/1,Disable 阈值默认 0/0(=永不禁用),SleepCounterThreshold=0(
Defines.h:132-136);Field 把线速度、角速度阈值设成同一个数(:323-324, 365-366)。 - Persistent Field 会逐步全量重算,且枚举是 O(N 粒子);只在确实需要「随时间演化 / 逐帧腐蚀」时用,用完
RemovePersistentFields(FieldSystemComponent.cpp:404-407)。 - 力通道 + 子步:transient 力只作用于第 1 个子步(见 1.2);要稳定的持续力请用 Persistent。
WaveScalar在 t=0 的数值风险 [存疑]:Radius=Wavelength·t/Period(:318)未对Period=0保护;Cosine/Gaussian 分支在Radius=0时Fraction=1−d/0→Phase=−k·0·(−∞)可能出 NaN(:333-338)。Falloff 模式有Radius>0保护(:346)。RandomVector非确定(FMath::RandRange)——需要可复现/网络同步的爆炸别用,改用NoiseField或固定种子的外部生成。- 解锚不会清
IsAnchored标志 [推断]:ReportDynamicStateResult只改 ObjectState(:230-263),而UpdateKinematicProperties优先看IsAnchored()(CH/Private/Chaos/PBDRigidClusteringAlgo.cpp:165, 194)。运行时解锚请用SetAnchoredByBox(…, false)/RemoveAllAnchors()(GeometryCollectionComponent.h:752-764;实现GeometryCollectionPhysicsProxy.cpp:2640-2768)。 ApplyKinematicField(BP「Set Dynamic State」)的 Exterior=Kinematic 且IFF_NOT_Interior:球外且当前非 Dynamic 的粒子(含 Static 锚点)会被改成 Kinematic(GeometryCollectionComponent.cpp:5964-5969;语义FieldSystemNodes.cpp:188-193)。想「球内解锚、球外不动」应改用CullingField(Outside)+UniformInteger(Dynamic)。
1.9 典型参数(取自源码/示例资产,非凭空)
HeadlessChaos 单测(HC/GeometryCollectionTestSimulationField.cpp)
| 用途 | 参数 | 行 |
|---|---|---|
| 外部应变 | RadialFalloff Magnitude=1.5, Radius=100, Pos=0, Falloff=None,GC DamageThreshold={1.0} → 一次击穿一层 | 504-583 |
| 外部应变(偏心) | Mag=1.1, Radius=200, Pos=(350,0,0), Resolution_Maximum | 677-760 |
| 内部应变(弱化) | Mag=−100, Radius=1000/10000, None;DamageThreshold={91,92,93,94}/{101,103};Minimal 只影响活动簇+子节点,Maximum 影响全部 | 839-987, 1145-1298 |
| Kill | PlaneFalloff Mag=1, Distance=10, Pos=(0,0,5), Normal=Z, Linear | 361-403 |
| 线速度 | UniformVector Mag=100, Dir=(1,0,0) | 405-438 |
| 线力 | UniformVector Mag=1000, Dir=(0,1,0) | 269-312 |
| 扭矩 | UniformVector Mag=100, Dir=(0,1,0)(物体缩放 10) | 314-359 |
| 去静态 | RadialIntMask R=5, Interior=Dynamic, Exterior=Kinematic, IFF_NOT_Interior | 219-267 |
引擎自带 Field 蓝图模板(FN/,字符串自 .uasset 提取,用于「演示页写默认值」):
| 资产 | 关键提示文本 / 默认 |
|---|---|
FS_AnchorField_Generic | 「Anchor Fields set leaf nodes within their volume to Static」;必须在 Construction Script 阶段建立(因为连接图要在模拟前创建);用 Culling Field + Uniform Integer 实现;用法:Connect your Field to your Geometry Collections by adding an "Initialization Field" |
FS_MasterField / FS_BombField_Prototype | Strain 默认「非常高以确保必断」;线速度默认 1200(及 750,另一变量),角速度默认 8;噪声倍率默认 [0.5, 2.0];Falloff Min/Max 默认 0,1;体形默认 Sphere;Make Dynamic 默认关;OnTick 默认关;Decay/NumStrainHits/Delay:"Activates the strain field once per hit, allowing user to break multiple cluster levels before forces are applied. NOTE: can dramatically affect performance if too many pieces are activated simultaneously";Internal Strain (Decay):每次触发减去连接强度,配合 OnTick 逐帧弱化 |
FS_SleepDisable_Generic | 「Sleep 与 Disable 的区别:Sleep 节点可被碰撞重新唤醒;Kill 选项把节点彻底移出模拟」;"If a rigid body passes into this box while travelling below this threshold (velocity magnitude in cm/s squared), Disable";必须做 Culling |
2. 破碎判定与事件
2.1 断裂判定(应变模型)
// 每个 solver step 末尾:AdvanceClustering() PBDRigidClustering.cpp:1672-1897
if ComputeClusterCollisionStrains (p.ComputeClusterCollisionStrains=1, :51) ComputeStrainFromCollision() // 碰撞冲量 → CollisionImpulse
for (Parent in TopLevelClusterParentsStrained, 按时间戳排序): // :1762-1817
若 Parent 的某个子 InternalStrain<=0 → 强制 CollisionImpulse=FLT_MAX
若某子 ExternalStrain>0 或 CollisionImpulse>0 → 加入 ParticlesToProcess
对每个 ParticlesToProcess (受 PerAdvanceBreaksAllowed 限制): BreakingModel() → ReleaseClusterParticlesImpl() // :1841-1879
// ReleaseClusterParticlesImpl :1109-1348
for Child in Parent.Children:
Applied = max(Child.CollisionImpulses, Child.ExternalStrain) // :1178
if Applied >= Child.InternalStrains or bForceRelease:
RemoveChildFromParent(Child) // V_child += V_parent + W_parent × Δp ; W_child += W_parent // :910-958 (936-940)
SendBreakingEvent(Child) // 事件里的 Velocity 已含继承速度 // :1241
Child.ExternalStrain = 0 // :1247
传播(Damage Propagation,GC 参数 bUseDamagePropagation) // :1257-1299
BreakFactor>0 且已断: 剩余应变 (Applied−Internal)×BreakFactor / 连接数 → 邻居 ExternalStrain
否则 ShockFactor>0: Shock×Applied → 邻居
HandleConnectivityOnReleaseClusterParticle() // 按连通性把碎片重新聚成新簇(锚点相连的部分保持 Kinematic)
UpdateKinematicProperties(...) // 锚定/静态/运动学向上传播(PBDRigidClusteringAlgo.cpp:147-228)
ThrottleReleasedParticlesIfNecessary() // :1344, 见 §2.3关键推论:
- 一个 step 只击穿一层(单测 Test1:加命令→Advance 一次,簇数 3→2;
HC/…SimulationField.cpp:504-583)。 - 同一个 step 里先(step 开头)写应变/速度,后(step 末尾)断裂 → 速度类 Field 写在尚 Disabled 的子节点上,释放时叠加继承(
:936-940)。这就是爆炸「一帧同时下发应变 + 径向速度」成立的原因。 - [推断] Resolution_Maximum 会让更深层后代预先带上 ExternalStrain;被释放的子节点会重新进入
TopLevelClusterParentsStrained(UpdateTopLevelParticle,:1061-1071,:1233),于是无需每帧重发就逐层级联(每层 1 个 step)。Minimal 则只能击穿一层,要靠重复/持续下发(BP 的OnTick、NumStrainHits)。同理 Maximum 下速度类 Field 对深层后代可能被多层继承而叠加 [推断],使用前需实测。 - 未被处理的外部应变不会清零:只有被
ReleaseClusterParticlesImpl遍历到的簇才执行SetExternalStrains(0)(:1247)。PerAdvanceBreaksRescheduleLimit设小时,超出部分既不在本步处理、又被:1833的Reset()清出待处理表 [推断:会被遗漏,直到再次被应变触发]。
阈值(InternalStrain)从哪来(GeometryCollectionPhysicsProxy.cpp:1245-1292, 1946-1992)
DamageModel(GeometryCollectionSimulationTypes.h:110-120):UserDefined Damage Threshold(默认)或Material Strength And Connectivity。- UserDefined:按层级取
DamageThreshold[Level](超出长度用最后一项);Level = 层级深度 + LevelOffset,非「仅簇」模式LevelOffset=−1,即根的直接子节点为 Level 0(:1950-1979);Level ≥ min(MaxClusterLevel, 全局MaxSimulatedLevel, MaxSimulatedLevel)⇒FLT_MAX(不可断,:1952-1955);可乘材质DamageThresholdMultiplier(默认 1,Defines.h:67-74)。 - 默认值:资产与组件均
DamageThreshold = {500000, 50000, 5000}(GCE/Private/GeometryCollection/GeometryCollectionObject.cpp:108;GeometryCollectionComponent.cpp:613)。这也解释了 Master Field 蓝图里「Strain 默认非常高」——必须 > 5×10⁵ 才能击穿第一层。 bEnableDamageFromCollision默认 true(GeometryCollectionComponent.cpp:616):碎片互撞也会产生应变并继续碎裂。FGeometryCollectionDamagePropagationData默认:bEnabled=true, BreakDamagePropagationFactor=1.0, ShockDamagePropagationFactor=0.0(GCE/Public/GeometryCollection/GeometryCollectionDamagePropagationData.h:13-23)。
直接施加应变的组件 API(不经 Field,供命中点/射线类破坏):ApplyExternalStrain / ApplyInternalStrain(ItemIndex, Location, Radius, PropagationDepth, PropagationFactor, Strain)(GeometryCollectionComponent.h:714, 726;实现 GeometryCollectionPhysicsProxy.cpp:2770-2881,按连接边逐层传播并乘 PropagationFactor)、CrumbleCluster / CrumbleActiveClusters(:733, 740,内部 BreakCluster 把子节点外应变设为 FLT_MAX,PBDRigidClustering.cpp:2569-2598)、ApplyBreakingLinearVelocity/AngularVelocity(:773-782,仅作用于即将断裂的子节点)。
2.2 事件管线
PT: 释放时 SendBreakingEvent(:780-804) 仅当 SimParams.bGenerateBreakingData || bGenerateGlobalBreakingData
→ MAllClusterBreakings (FBreakingData: Proxy, Location, Orientation, Velocity, AngularVelocity, Mass, BoundingBox, TransformGroupIndex, bFromCrumble, EmitterFlag)
FillProducerData (PBDRigidsSolver.cpp:624) → EventDefaults.cpp:318-363 (Breaking)
· 若 !Solver->GetEventFilters()->IsBreakingEventEnabled() 直接 return (:330)
· 若 BreakingFilter.Enabled() 则 Pass(): MinMass / MinSpeed / MinVolume (:349-354; SolverEventFilters.cpp:51-71)
EventManager 双缓冲 FlipBuffersIfRequired → GT DispatchEvents
GT: UChaosGameplayEventDispatcher::HandleBreakingEvents (CSE/Private/Chaos/ChaosGameplayEventDispatcher.cpp:569-608)
仅 EmitterFlag & OwnDispatcher 的分发给组件 → UGeometryCollectionComponent::DispatchBreakEvent (cpp:1501-1511) → NotifyBreak() + OnChaosBreakEvent
Global 版 → UChaosEventRelay::OnBreakEvent(TArray<FChaosBreakEvent>) (ENG/Public/Physics/Experimental/ChaosEventRelay.h:15, 43)EEventType:Collision/Breaking/Trailing/Sleeping/Removal/Crumbling(CH/Public/EventManager.h:34-42)。
开关(默认全部 false,GeometryCollectionComponent.cpp:628-638)
| 属性 | 含义 | 头文件行 |
|---|---|---|
bNotifyBreaks / bNotifyCollisions / bNotifyTrailing / bNotifyRemovals / bNotifyCrumblings | 本组件事件(走 UChaosGameplayEventDispatcher → 组件委托) | GeometryCollectionComponent.h:1446-1462 |
bCrumblingEventIncludesChildren | Crumbling 事件附带被释放子节点索引 | :1466 |
bNotifyGlobalBreaks / Collisions / Removals / Crumblings / Trailings(+bGlobalCrumblingEventIncludesChildren) | 走世界事件中继 UChaosEventRelay(批量数组,适合声音/全局特效聚合) | :1470-1490 |
| 运行时开关 | SetNotifyBreaks/Removals/Crumblings/Trailings/Global* | :1228-1260 |
| 委托 | OnChaosBreakEvent / OnChaosRemovalEvent / OnChaosCrumblingEvent,OnChaosPhysicsCollision | :1269-1275, 1436 |
solver 侧过滤器(CH/Public/SolverEventFilters.h;AChaosSolverActor::Properties,CH/Public/ChaosSolverConfiguration.h:105-130;默认 FilterEnabled=false、阈值全 0)
| 事件 | 字段 | 判定(SolverEventFilters.cpp) |
|---|---|---|
Breaking(:71-98) | MinMass, MinSpeed, MinVolume | 质量 < MinMass 丢弃;` |
Collision(:41-69) | MinMass, MinSpeed, MinImpulse | 两体都小于才丢;AccumulatedImpulse 小于 MinImpulse 丢弃(:12-27) |
Trailing(:12-39) | MinMass, MinSpeed, MinVolume | :29-49 |
Removal(:100-122) | MinMass, MinVolume | :73-88 |
- 没有
MaxNumberOfEvents之类的 solver 侧上限;限流发生在消费端(Niagara DI 的MaxNumberOfDataEntriesToSpawn、缓存的*DataSizeMax)。 - 碰撞事件另需形状带
ContactNotify标志才会产生(EventDefaults.cpp:150-158)。 - [存疑]
EventDefaults.cpp:329注释:Solver Actor 上设置的事件参数「可能没生效,因为实际在跑的是另一个 solver」。对默认世界 solver,以组件侧开关为准;Solver Actor 的过滤器需实测。 FSolverEventFilters容器与SetGenerate*Data(SolverEventFilters.h:192-239;PBDRigidsSolver.h:203-213:SetGenerateBreakingData同时打开簇断裂记录SetGenerateClusterBreaking)。
事件结构体字段
| 结构 | 字段 | 位置 |
|---|---|---|
PT FBreakingData | Proxy, Location, Orientation, Velocity, AngularVelocity, Mass, BoundingBox, TransformGroupIndex, bFromCrumble(+EmitterFlag) | CH/Public/Chaos/ExternalCollisionData.h:212-242 |
GT FChaosBreakEvent | Component, Location, Orientation, Velocity, AngularVelocity, Extents, Mass, Index, bFromCrumble | ENG/Public/Physics/Experimental/ChaosEventType.h:82-127 |
FChaosCrumblingEvent | Component, Location, Orientation, LinearVelocity, AngularVelocity, Mass, LocalBounds, Children[] | ChaosEventType.h:149-196;PT FCrumblingData ExternalCollisionData.h:247-275;产生于 SendCrumblingEvent PBDRigidClustering.cpp:807-837 |
FChaosRemovalEvent | Component, Location, Mass | ChaosEventType.h:129-146;PT FRemovalData :469-496 |
FChaosTrailingEvent | Component, Location, Orientation, LinearVelocity, AngularVelocity, Mass, LocalBounds, Index | ChaosEventType.h:198-247;PT FTrailingData :347-387 |
| 碰撞 | PT FCollidingData(Location, AccumulatedImpulse, Normal, Velocity1/2, DeltaVelocity1/2, AngularVelocity1/2, Mass1/2, PenetrationDepth, Mat1/2, Proxy1/2…)→ GT FCollisionChaosEvent;组件级 FChaosPhysicsCollisionInfo | ExternalCollisionData.h:56-135;ChaosEventType.h:52-79;CSE/Public/Chaos/ChaosNotifyHandlerInterface.h:12-53 |
事件里的
Velocity取自RemoveChildFromParent之后(PBDRigidClustering.cpp:1232→1241),已包含 Field 写入与父节点继承的速度——所以用它直接驱动下游特效的初速是正确的。
2.3 大规模闸门(Solver 级破碎设置)
FChaosSolverDestructionSettings(CH/Public/ChaosSolverConfiguration.h:20-48)
| 参数 | 默认 | 作用 | 实现 |
|---|---|---|---|
PerAdvanceBreaksAllowed | INT_MAX(不限) | 每个 AdvanceClustering 允许的断裂簇数 | PBDRigidClustering.cpp:1841-1861;CVar p.Chaos.Clustering.PerAdvanceBreaksAllowed(:1656),与设置取 min(:3133) |
PerAdvanceBreaksRescheduleLimit | INT_MAX | 超额的待处理簇顺延到下一步的数量上限 | :1864-1878;CVar …RescheduleLimit(:1660),取 min(:3134) |
ClusteringParticleReleaseThrottlingMinCount | INDEX_NONE(关) | 活动 GC 粒子数超过它后,开始立即禁用一部分新释放的碎片 | :141-189;CVar p.Clustering.ParticleReleaseThrottlingMinCount(:72-73) |
ClusteringParticleReleaseThrottlingMaxCount | INDEX_NONE(关) | 达到它则释放即全部禁用 | 比例 r = clamp((Active−Min)/(Max−Min), 0, 1),Active = Sleeping+Dynamic GC 粒子数(:141-159);开启条件两者都 ≥0(:3096);Settings 与 CVar 取 max(:3135-3136) |
bOptimizeForRuntimeMemory | false | 根未断前不创建多余 GC 物理数据 | :41-47 |
其它同类全局:ClusterConnectionFactor(默认 1.0)、ClusterUnionConnectionType(默认 DelaunayTriangulation)(FChaosSolverConfiguration 构造:CH/Private/ChaosSolverConfiguration.cpp:8-26);RestoreBreakingMomentumPercent=0.5(断裂时把冲击方动量 50% 还给接触物,PBDRigidClustering.cpp:69);迭代数默认 Position 8 / Velocity 2 / Projection 1(CH/Public/Chaos/PBDRigidsEvolutionGBF.h:62-64)。
组件/资产级(GeometryCollectionComponent.h):EnableClustering(true)、MaxClusterLevel(100)、MaxSimulatedLevel(100;"设 1 则只创建 Root 与 Level 1 的物理体,Level 2 叶子永远不会进 solver",:1012-1020)、DamageModel/DamageThreshold/bUseSizeSpecificDamageThreshold/bUseMaterialDamageModifiers/DamagePropagationData(:1024-1051)、bAllowRemovalOnSleep/OnBreak(:1062, 1066)。
移除(Removal)(资产 GeometryCollectionObject.h:789-814,默认 .cpp:137-143):bRemoveOnMaxSleep=false、MaximumSleepTime=(5,10)s、RemovalDuration=(2.5,5)s、bSlowMovingAsSleeping=true、SlowMovingVelocityThreshold=1、bScaleOnRemoval=true、bAutomaticCrumblePartialClusters=true。驱动在 GT:IncrementSleepTimer(GeometryCollectionComponent.cpp:6679-6745)、IncrementBreakTimer(:6747-);Remove-on-break 数据打包为 FVector4f,禁用值 (−1,0,0,0)(CH/Public/GeometryCollection/Facades/CollectionRemoveOnBreakFacade.h:18)。
3. 下游联动
3.1 Niagara「Chaos Destruction Data」DI(PX/ChaosNiagara/Source/ChaosNiagara/)
类:UNiagaraDataInterfaceChaosDestruction(Classes/NiagaraDataInterfaceChaosDestruction.h:351-353);Content 模块:SpawnFromChaos / ApplyChaosData / ReadGeometryCollection / SetGeometryCollectionTransform / GeometryCollectionSize,以及 ChaosDestructionListenerActor(PX/ChaosNiagara/Content/)。
数据路径:DI 初始化时直接向 solver 的 EventManager 注册 Collision / Breaking / Trailing 处理器并 SetGenerate*Data(true)(Private/NiagaraDataInterfaceChaosDestruction.cpp:577-600);Trailing 源会同时注册 Breaking。未指定 ChaosSolverActorSet 时绑定世界 solver(:603-666);仅游戏世界生效(:612)。
限流与默认(构造 :210-259)
| 参数 | 默认 | 行为 |
|---|---|---|
DataSourceType | Collision | Collision / Breaking / Trailing(头 :362-364) |
DataProcessFrequency | 10 Hz | SolverTime − LastSpawnTime ≥ 1/Freq 才处理一次(:2467);Max(1, …)(:368-370) |
MaxNumberOfDataEntriesToSpawn | 50 | 每次处理最多取这么多条;超出则按步长 N/Max 均匀抽稀(:1744-1763, 1766-1785);≤0 直接不处理(:1922) |
MaxLatency | 1.0 s | 事件年龄 > 1s 丢弃(:1796-1797) |
SpawnMultiplierMinMax / SpawnChance | (1,1) / 1.0 | 每条事件生成粒子数 / 概率(:1799-1805) |
| 阈值(Impulse/Speed/Mass/ExtentMin/ExtentMax/Volume/SolverTime/SurfaceType + XYZ 位置过滤) | 全 −1(关) | 过滤(:1560-1649);min>0&max<0 表示「只下限」,反之「只上限」 |
DoSpatialHash + SpatialHashVolumeMin/Max/CellSize + MaxDataPerCell | 关;±100;(10,10,10);1 | 空间哈希,每格最多取 N 条(:1700-1741) |
DataSortingType | NoSorting | 随机/质量升降序 |
RandomVelocityMagnitudeMinMax / InheritedVelocityMultiplier / SpreadAngleMax | (1,2) / 1.0 / 30° | 新粒子速度 = 事件速度×Inherited + 随机方向×U(1,2) + 偏移(:1841);默认随机速度 1–2 cm/s 几乎为 0,做烟尘/碎屑必须调大 |
TrailMinSpeedToSpawn | 50 | Trailing 的速度门槛 |
关键前置 [推断,自
PBDRigidClustering.cpp:786+:801]:SendBreakingEvent只有在 GC proxy 的bGenerateBreakingData || bGenerateGlobalBreakingData为真时才产出FBreakingData。所以 Breaking 数据源要求 GC 组件勾bNotifyBreaks或bNotifyGlobalBreaks(DI 自己只开 solver 侧的SetGenerateBreakingData)。Collision 源同理需要形状ContactNotify。
3.2 Physics Field DI 与世界场
UNiagaraDataInterfacePhysicsField(Classes/NiagaraDataInterfacePhysicsField.h:49-93)提供 SamplePhysicsVector/Scalar/IntegerField,采样 世界 PhysicsField(GPU 剪辑图,默认分辨率 32³,PhysicsFieldComponent.cpp:83-88)。要让同一个爆炸 Field 同时推动 Niagara 粒子/材质,把 UFieldSystemComponent.bIsWorldField 置 true(默认 false,FieldSystemComponent.h:62)。FN/Niagara/FS_{Radial,UniformScalar,UniformVector,WaveScalar,Base}Field 是配套模板。
3.3 Geometry Collection DI
NiagaraDataInterfaceGeometryCollection(Public/NiagaraDataInterfaceGeometryCollection.h:184-231)读取 GC 变换(SourceMode 默认/源 Actor/参数绑定),配合 ReadGeometryCollection 模块可用 Niagara 渲染碎片网格(替代 GC 自身渲染)。
3.4 声音 / Gameplay
- 引擎没有内置的「Chaos 破碎 → 音频」连线(在
ChaosNiagara/ChaosCaching/GeometryCollectionPlugin/Fracture/ChaosSolverPlugin与Source/Runtime/{Engine,Experimental}中检索FChaosBreakEvent的消费者,仅 Engine 的ChaosEventRelay、ChaosGameplayEventDispatcher、GC 组件与 ChaosCaching 适配器)。声音需自行在 BP/C++ 订阅事件。 - 建议接法(工程建议,非源码):
- 批量聚合用
UChaosEventRelay::OnBreakEvent(每帧一个数组)+ 组件bNotifyGlobalBreaks,在 BP 里按空间/质量分桶,再触发 MetaSound,参数取FChaosBreakEvent.Mass / Velocity / Extents / Location; - 整栋崩塌的「大轰鸣」用
OnChaosCrumblingEvent(一个簇一次,而不是几千个 Break); - 碰撞声用
OnChaosPhysicsCollision(FChaosPhysicsCollisionInfo:Location/Normal/AccumulatedImpulse/Velocity/Mass…),先用 Solver 的 Collision 过滤器MinImpulse/MinMass/MinSpeed在源头砍量。
- 批量聚合用
3.5 Dataflow 里的 Field 节点(编辑/生成期,不是运行时命令)
PX/GeometryCollectionPlugin/Source/GeometryCollectionNodes/Public/Dataflow/GeometryCollectionFieldNodes.h:RadialFalloff(:40)、BoxFalloff(:125)、PlaneFalloff(:210)、RadialIntMask(:322)、UniformScalar/UniformVector/RadialVector/RandomVector/Noise/UniformInteger、WaveScalar(:742)、SumScalar(:842)、SumVector(:922)、FieldMakeDenseFloatArray(:996)。
- 实现是直接在 CPU 上实例化原生
FRadialFalloff等并调用Evaluate(…Private/Dataflow/GeometryCollectionFieldNodes.cpp:107-146),输入是顶点/骨骼位置数组(ContiguousIndices),输出是FieldFloatResult / FieldRemap / FieldSelectionMask(稀疏→用FieldRemap还原)。选择掩码的做法:把Default设成MinRange−1000的哨兵再比较(:169-187)。 - 用途:按场批量设置每骨骼属性(锚定、
RemoveOnBreak时序、损伤阈值等),不会产生FFieldSystemCommand。 - 与运行时的差异:Dataflow 的
SumScalar多了Min / Max,SumVector多了CrossProduct(:822-833, 904-914),运行时EFieldOperationType只有 乘/除/加/减(FieldSystemTypes.h:42-51)。 - 锚定数据层:
Chaos::Facades::FCollectionAnchoringFacade(CH/Public/GeometryCollection/Facades/CollectionAnchoringFacade.h:14-53)=InitialDynamicState(可运行时改)+ 与之正交的Anchored布尔属性。
4. 缓存 / 录制回放
4.1 现状:新旧两套
旧:UGeometryCollectionCache + FRecordedTransformTrack | 新:ChaosCaching 插件 | |
|---|---|---|
| 位置 | GCE/Public/GeometryCollection/GeometryCollectionCache.h(类标 Experimental,:14);CH/Public/GeometryCollection/RecordedTransformTrack.h | PX/ChaosCaching/Source/ChaosCaching/ |
| 状态 | SimulationParameters 里 RecordedTrack / CacheType / bClearCache… 全部 UE_DEPRECATED(5.5, "No longer used")(GeometryCollectionSimulationCoreTypes.h:454-487);组件上残留 FGeomComponentCacheParameters CacheParameters(GeometryCollectionComponent.h:1108),默认 CacheMode=None(.cpp:498-520) | AChaosCacheManager(UCLASS(Experimental),Public/Chaos/CacheManagerActor.h:187)、UChaosCache(ChaosCache.h:314)、UChaosCacheCollection、Sequencer 轨道、Take Recorder |
| 数据 | FRecordedFrame{Transforms, TransformIndices, PreviousTransformIndices, DisabledFlags, Collisions, Breakings, Trailings, Timestamp}(:205-269),GetTransformAtTime 用 FTransform::Blend 插值(:426-454),GetLinearVelocityAtTime(SampleWidth=1/120)(:509-525)。旧默认:BreakingDataSizeMax=512、SpatialHashRadius=50、TrailingMinSpeed=200、TrailingMinVolume=10000(GeometryCollectionComponent.cpp:498-520) | 每粒子轨道 ParticleTracks(变换 + 事件)、RecordedDuration/NumRecordedFrames、InterpolationMode 默认 QuatInterp(ChaosCache.h:415-422;可选 Euler / DualQuat,ChaosCacheInterpolationMode.h)、通道曲线可压缩(bCompressChannels, 默认误差 1e-5、采样率 1/30,:453-459) |
| 结论 | 不要在 5.7 上新开发 | 采用 |
4.2 ChaosCaching 的录制 / 回放机制
- 模式:
ECacheMode{None(Static Pose), Play, Record}、EStartMode{Timed, Triggered}(CacheManagerActor.h:23-36)。StartTime、bStartOnBeginPlay、bReplayEventsInStaticPoseMode(默认 true,静态姿态下也回放 Break/Collision 事件,:224-238)。 - 挂钩:PT 的
PreSolve(回放:推 Kinematic 数据) /PreBuffer(标脏) /PostSolve(录制)(HandlePreSolve/PreBuffer/PostSolve,:366-372)。适配器FGeometryCollectionCacheAdapter(Public/Chaos/Adapters/GeometryCollectionComponentCacheAdapter.h:245-301)。 - 录制(
Record_PostSolve,…CacheAdapter.cpp:57-242):每个 solver step,对每个未禁用(或其父是活动内部簇)的粒子写一个Actor 空间变换(MassToLocal⁻¹ · (R,X) · WorldToActor,:144-167);释放过的簇父节点写一次 identity 并标bPendingDeactivate(:176-193);断裂时对「同簇所有兄弟」推FEnableStateEvent(:118-142, 169-172);另录FBreakingEvent / FCollisionEvent / FTrailingEvent(:195-238,事件结构…Adapter.h:11-220)。录制要求组件bNotifyBreaks=true,管理器会暂存并强制置位(FObservedComponent.bHasNotifyBreaks注释,CacheManagerActor.h:125-131)。 - 回放(
Playback_PreSolve,…cpp:244-):用 Kinematic Target(CVarp.Chaos.Cache.GeometryCollectionPlayUseKinematicTarget默认 true,:25-30, 504-508)驱动粒子;内部簇父节点也要反推位置(:524-559);FEnableStateEvent把被预告断裂的子块切换启用(:283-300)。 - 混合的关键一行:
if (Handle->ObjectState() != Kinematic) continue;(:494-498,注释「If a field or other external actor set the particle to static or dynamic we no longer apply the cache」)——用 Field 把粒子切成 Dynamic,缓存对它立刻失效,该粒子转为实时模拟。
4.3 何时用 Cache,何时用实时
| 判据 | 选 Cache(预烘焙) | 选实时模拟 |
|---|---|---|
| 触发时刻/位置 | 固定、可预知(剧情爆破、过场) | 玩家任意位置命中 |
| 规模 | 数万~数十万碎片且每台设备必须一致;低端平台 | 规模在 solver 预算内,或有 PerAdvanceBreaksAllowed/节流兜底 |
| 一致性 | 联机观众视觉一致、可重复拍摄(Sequencer/Take Recorder) | 允许不一致 |
| 运行时开销 | 回放只做 Kinematic 目标更新 + 读轨道(O(活动轨道));没有求解/断裂判定 | 求解 + 断裂判定 + 事件生成 |
| 内存/包体 | 轨道 ∝ 粒子数×帧数×(变换+事件);需压缩、需审核大小 | 无额外资产 |
| 交互 | 默认无;需 Field 切 Dynamic 才能被撞 | 完整 |
推荐做法:远景/英雄镜头用 Cache,近处/与玩家交互的部分在回放中途用 Field 把半径内粒子切 Dynamic(Field_DynamicState),其余继续按缓存回放。注意 UpdateKinematicProperties 对簇的联动(PBDRigidClusteringAlgo.cpp:147-228)。
5. 组合配方:Anchor + Radial Strain + Radial Vector + Kill/Sleep
5.1 谁在什么阶段生效
[P0 初始化 · proxy 创建时,建簇层级之前]
Anchor Field (Field_DynamicState=Static,Resolution 被强制 Maximum) GeometryCollectionPhysicsProxy.cpp:1452-1467
└─ 之后 UpdateKinematicProperties:含锚点叶子的簇 → Kinematic(:165,194);断裂后按连通性重新分块,与锚点相连的块保持 Kinematic
[T0 引爆 · GT 一次性下发 4 条 transient 命令] ──入队顺序即执行顺序──
P1 step 开头 · 参数通道 (PBDRigidsSolver.cpp:513/516)
① Field_ExternalClusterStrain 写 ExternalStrain (取 max)
② Field_LinearVelocity / LinearImpulse 写到(仍 Disabled 的)子节点速度上
③ Field_AngularVelociy 翻滚
④ Field_Kill (可选) 体内 >0 的粒子被 DisableParticle
P2 各子步 · 力通道 Field_LinearForce (可选:持续上升气流/风)
P3 求解 / PostSolve
P4 PostSolve 末 · UpdateSleep/UpdateDisable Sleeping/Disable 阈值(持久 Field)在此消费 (PBDRigidsEvolutionGBF.cpp:801-804)
P5 AdvanceClustering ExternalStrain ≥ 子 InternalStrain → 释放;子速度 += 父速度;ExternalStrain 清零;Throttle (:807)
P6 FillProducerData → Break/Crumble/... 事件 → GT → Niagara / 声音 (PBDRigidsSolver.cpp:624)
[T0+1 …] Maximum 分辨率下的深层后代继续级联(每层 1 step);或用 Persistent / OnTick 继续击穿
[清理] Persistent 阈值/Kill 区域 + Remove-on-sleep(资产) 回收碎片5.2 各 Field 配置
| # | 作用 | 目标 | 节点图 | 关键参数 | 阶段 |
|---|---|---|---|---|---|
| A | 锚定(建筑地基/墙体不倒) | Field_DynamicState | CullingField<int32>(Culling=BoxFalloff/RadialFalloff, Input=UniformInteger(3=Static), Outside);挂到 AFieldSystemActor,GC 的 InitializationFields 引用 | 值 3;盒体贴住地基;Construction 阶段建立(BP 模板注释) | P0 |
| B | 径向应变(爆炸半径内伤害,带衰减) | Field_ExternalClusterStrain | RadialFalloff(Mag=S, Min0, Max1, Default0, Radius=R, Falloff=Squared/Linear) | S ≥ 第一层阈值(默认 5×10⁵;BP 默认「很大」),Resolution=Maximum 以便逐层级联 | P1→P5 |
| C | 径向冲量 | Field_LinearVelocity(或 Field_LinearImpulse) | OperatorField(Multiply, Right=RadialFalloff(Mag=V, Falloff), Left=RadialVector(1,center)) ≡ ApplyRadialVectorFalloffForce 的构造 | V:BP 默认 1200(或 750)cm/s;可再乘 NoiseField[0.5,2.0] 打破对称;叠 RotatedRadialVector 做涡旋 | P1 |
| D | 翻滚 | Field_AngularVelociy | RandomVector(Mag≈8)×RadialFalloff(BP 默认 8) | RandomVector 不可复现 | P1 |
| E | Kill(清理远处/地下碎片) | Field_Kill | BoxFalloff(Mag=1, Default=0) 或 PlaneFalloff(单测 Distance=10) | 值 >0 即禁用;solver 级通道(GC 枚举无 Kill) | P1(持久则每步) |
| F | Sleep/Disable 阈值(让碎片尽快静止/退出模拟) | Field_SleepingThreshold / Field_DisableThreshold | CullingField<float>(Culling=BoxFalloff(Mag=1), Input=UniformScalar(阈值), Outside) | 材质默认 Sleep=1/1、Disable=0/0;必须裁剪;会逐粒子分配材质 | P4 消费,需 Persistent |
| G | 复活/解禁(可选) | Field_ActivateDisabled | CullingField<int32>(…, UniformInteger(0)) | 值 0=复活 | P1 |
C++ 伪码(solver 级,等价于蓝图 Apply Physics Field)
// 假设已持有 UFieldSystemComponent* FSC 与爆心 C、半径 R
auto* Res = NewObject<UFieldSystemMetaDataProcessingResolution>(); Res->ResolutionType = Field_Resolution_Maximum;
auto* Fall = NewObject<URadialFalloff>(); Fall->SetRadialFalloff(/*Mag*/1.0e6f, 0.f, 1.f, /*Default*/0.f, R, C, Field_Falloff_Squared);
FSC->ApplyPhysicsField(true, Field_ExternalClusterStrain, Res, Fall); // ① 应变
auto* Fall2= NewObject<URadialFalloff>(); Fall2->SetRadialFalloff(1200.f, 0.f, 1.f, 0.f, R, C, Field_Falloff_Linear);
auto* Dir = NewObject<URadialVector>(); Dir->SetRadialVector(1.f, C);
auto* Op = NewObject<UOperatorField>(); Op->SetOperatorField(1.f, /*Left*/Dir, /*Right*/Fall2, Field_Multiply);
FSC->ApplyPhysicsField(true, Field_LinearVelocity, Res, Op); // ② 冲量(同帧,写到未释放的子节点)
// ④ Kill 区、⑤ 阈值区:用 AddPersistentField + CullingField(Outside),用完 RemovePersistentFields()若只想影响某个 GC:改用
UGeometryCollectionComponent::ApplyPhysicsField(…, EGeometryCollectionPhysicsTypeEnum::Chaos_ExternalClusterStrain/Chaos_LinearVelocity, …)——枚举里没有 Kill/ActivateDisabled。
5.3 参数推导:应变强度、衰减与「每层击穿半径」
断裂条件 S·f(1−d/R) ≥ T_层。以默认阈值 T={5×10⁵, 5×10⁴, 5×10³}、S=10⁶(取 BP 的「很大」量级)为例,各层能被击穿的最大距离占比 d/R:
| Falloff | 第 1 层(5×10⁵) | 第 2 层(5×10⁴) | 第 3 层(5×10³) |
|---|---|---|---|
| Linear | 0.50 | 0.95 | 0.995 |
| Squared | 0.293 | 0.776 | 0.929 |
| Inverse | 0.667 | 0.974 | 0.998 |
| Logarithmic | 0.586 | 0.965 | 0.997 |
读法:Squared 把大块断裂压在爆心附近、外圈只碎成小块——更像真实爆炸;Linear/Inverse 让外围也被大块断开。调 S、R、曲线即可精确控制「几层碎、碎到多远」。(表为推导值,层级到阈值索引的映射见 §2.1。)
速度同理:v(d)=V·f(1−d/R);V=1200 cm/s ≈ 12 m/s。想要质量相关的真实感用 LinearImpulse(Δv = J/m),想要整齐划一用 LinearVelocity(BP 注释)。
5.4 大规模(整栋建筑 / 倒塌)配置清单
来自源码默认值与注释的做法:
- 限制层级与物理体数量:
MaxSimulatedLevel压到够用(GeometryCollectionComponent.h:1012-1020);bOptimizeForRuntimeMemory(ChaosSolverConfiguration.h:41-47)。 - 每步断裂预算:
PerAdvanceBreaksAllowed(如数百)+RescheduleLimit;注意超额可能被丢(§2.1)。 - 碎片节流:设置
ClusteringParticleReleaseThrottlingMinCount/MaxCount,活动粒子数越多,新释放碎片被立即禁用的比例越大(线性)。 - 回收:资产开
bRemoveOnMaxSleep(睡眠 5–10s 后 2.5–5s 缩小消失),bAutomaticCrumblePartialClusters防止半截簇永不消失(默认 true)。 - 踢掉小碎片的碰撞/模拟成本:
Field_CollisionGroup=−1(幽灵化)、Field_Kill、Field_DisableThreshold;全部要带 Culling。 - 事件降噪:只开必要的
bNotify*;Breaking 过滤器MinMass/MinVolume过滤小碎片;用Crumbling取代千条 Break 做大音效;Niagara 保持 50 条/0.1s 上限并开空间哈希。 - 碰撞自损:爆后碎片互撞继续碎裂可能放大成本,必要时关
bEnableDamageFromCollision(:1058)。 - 整体坍塌(非爆炸):去锚(
SetAnchoredByBox(…, false)/RemoveAllAnchors())+ PersistentField_InternalClusterStrain负值逐帧腐蚀(BP「Internal Strain (Decay)」;单测用−100)+DamagePropagation默认值(Break=1.0)让断裂沿连接图蔓延。 - 只让需要的 Field 持续运行:Persistent 是 O(粒子数)/步;用 GC 级通道缩小枚举范围。
- 英雄镜头:整栋崩塌预烘焙进 ChaosCaching,近处再用 Field 局部切 Dynamic 混合(§4.3)。
6. 演示页素材建议
- 图 1:Field 管线图(节点图 → 命令 → PT → 采样 → 写回),配 §1.1 的 ASCII。
- 图 2:一帧时间轴(§1.2 + §5.1),标出「Field 参数通道 / 力通道 / 睡眠阈值 / 断裂」位置——这是最容易被讲错的点。
- 图 3:Falloff 四曲线 + 「每层击穿半径」表(§1.4 + §5.3)。
- 图 4:事件管线(释放 → 过滤 → 双缓冲 → GT → 组件委托/世界中继 → Niagara/声音)+ 限流点(solver 过滤器 / DI 50 条每 0.1s / 缓存上限)。
- 对比表:Cache vs 实时(§4.3)。
- 「反直觉」页:Culling Inside=结果为 0;阈值 Field 不裁剪会踩全场;Resolution 默认 Minimal 只击穿一层;Radius/Distance 默认 0 的惰性节点;UniformVector 默认方向 (0,0,0)。
7. 未核实 / 存疑清单(上线前建议实测)
- Resolution=Maximum 下深层后代的 ExternalStrain 是否逐层自动级联(§2.1,推断自
:1233/1247/1833);速度类 Field 在 Maximum 下是否被多层继承叠加。 PerAdvanceBreaksRescheduleLimit设小时被遗漏的簇是否永久丢失。- 阈值 Field 创建的 per-particle 材质是否覆盖原 Friction/Restitution。
UpdateMaterialSleepingThreshold比较 Disabled 阈值(FieldSystemProxyHelper.h:333)是否确为 bug。- 解锚:
Field_DynamicState是否能清除新式Anchored属性(未发现清除代码)。 - Solver Actor 的事件过滤设置对默认世界 solver 是否生效(
EventDefaults.cpp:329注释)。 - Niagara Breaking 源是否必须依赖组件
bNotifyBreaks/Global(推断自PBDRigidClustering.cpp:786)。 PlaneFalloff作用于平面背面而非注释所述的法线方向;NoiseField无空间限制;WaveScalar在 t=0/Period=0 的数值行为。- 蓝图模板 .uasset 中的数值(如 Sleep/Disable 默认阈值、Strain 的具体默认值)仅从二进制字符串提取了说明文字;具体数值请在编辑器里打开
FN/*核对。 Field_PositionStatic/PositionTarget在当前实现里是否确为无效(solver 的PositionTarget是 Dummy)。
