外观
渲染、生命周期与预算
约 12928 字大约 43 分钟
2026-10-14
基线:UE 5.7(
E:\Main\Engine,源码只读)。全部结论来自直接读源码;【推论】 标记的是由代码结构推导出的经验判断,不是源码明文。 行号为本次阅读时的行号,路径相对E:\Main\Engine,下文用固定前缀缩写(机械替换即可还原):
| 缩写 | 展开 |
|---|---|
GCE | Source/Runtime/Experimental/GeometryCollectionEngine |
CH | Source/Runtime/Experimental/Chaos |
ISM | Source/Runtime/Experimental/ISMPool |
CSE | Source/Runtime/Experimental/ChaosSolverEngine |
ENG | Source/Runtime/Engine |
PCO | Source/Runtime/PhysicsCore |
PLG | Plugins/Experimental |
0. 一页总览
0.1 帧内数据流与成本落点
Physics Thread (PT) Game Thread (GT) Render Thread
─────────────────── ──────────────── ─────────────
Solver Advance PullPhysicsStateForEachDirtyProxy
├ Island/Sleep、Collision、Solve └ FGeometryCollectionPhysicsProxy::
├ AdvanceClustering (破碎预算) PullFromPhysicsState (容差判变、插值)
└ BufferPhysicsResults_Internal └ PostPhysicsSyncCallback
仅处理"含脏粒子"的 GC proxy └ UGC Component::OnPostPhysicsSync
→ 写 FGeometryCollectionResults ├ UpdateBrokenAndDecayedStates
(只含"未 Disabled"的粒子) ├ Removal 计时 (Tick, O(N))
├ RefreshEmbeddedGeometry
├ RefreshCustomRenderer ─→ ISMPool / RootProxy
└ MarkRenderDynamicDataDirty ─→ SendDynamicData ─→ 骨骼矩阵 SRV / Nanite 实例数据0.2 三条渲染路径速查
| 路径 | 触发条件 | 一次破碎状态更新的单位 | 典型成本 |
|---|---|---|---|
Native(FGeometryCollectionSceneProxy) | 无 CustomRenderer、EnableNanite=false | 整个 GC 的骨骼矩阵 SRV(N×64 B) | 每个脏 GC 每帧全量 N 个矩阵拷贝+上传 |
Native Nanite(FNaniteGeometryCollectionSceneProxy) | EnableNanite && HasNaniteData && r.GeometryCollection.Nanite=1 | 每块刚体=一个 Nanite 实例,重建全部实例数据 | 每脏帧重建 N 个实例的 InstanceToPrimitiveRelative+Bounds |
CustomRenderer:UGeometryCollectionISMPoolRenderer / UGeometryCollectionRootProxyRenderer | CustomRendererType 有效且 p.Chaos.GC.UseCustomRenderer=1 | ISM 实例(共享池)/ 整体代理 StaticMesh | 无 GC SceneProxy;渲染成本变为 ISM 实例更新 |
0.3 5.7 版本的"名词对照"(避免照着旧文档找不到)
- 题设里的
RenderingMode / bUseISMPool在 5.7 源码中不存在。已被CustomRendererType(UClass*,需实现IGeometryCollectionExternalRenderInterface)取代:组件上GCE/Public/GeometryCollection/GeometryCollectionComponent.h:1559-1564,资产上GCE/Public/GeometryCollection/GeometryCollectionObject.h:635-637。旧字段ISMPool_DEPRECATED / bAutoAssignISMPool_DEPRECATED在PostLoad里被转换成UGeometryCollectionISMPoolRenderer(GCE/Private/GeometryCollection/GeometryCollectionComponent.cpp:7424-7430)。 UGeometryCollectionISMPoolComponent / Actor / SubSystem在 5.6 已废弃(UE_DEPRECATED(5.6, ...):GCE/Public/GeometryCollection/GeometryCollectionISMPoolComponent.h:14,349;...ISMPoolActor.h:11;...ISMPoolSubSystem.h:15)。现役实现是独立模块ISM(UISMPoolComponent / AISMPoolActor / UISMPoolSubSystem),GC 通过UGeometryCollectionISMPoolRenderer使用它。FractureEngineEmbed、GeometryCollectionEmbedNodes是空壳(见 §1.9),Embed 的真实逻辑在FractureToolEmbed.cpp+UGeometryCollectionComponent::RefreshEmbeddedGeometry。
1. 渲染
1.1 渲染路径选择逻辑(源码事实)
| 判定 | 逻辑 | 位置 |
|---|---|---|
| 是否需要创建 RenderState | !IsCustomRendererAvailable() || bCustomRendererCanUseNativeFallback || !World->IsGameWorld();注释:"Returning true here means that we pay overhead of render thread tasks… return false whenever possible" | GCE/Private/GeometryCollection/GeometryCollectionComponent.cpp:1133-1142 |
| CustomRenderer 可用 | bChaos_GC_UseCustomRenderer && CustomRenderer != nullptr && FApp::CanEverRender() | …GeometryCollectionComponent.cpp:6512-6515 |
| 正在使用 CustomRenderer | 可用 且 非(可回退且当前需回退) 且 非 bForceNativeRenderer(Fracture 编辑模式强制 Native) | …:6517-6520 |
| 创建 SceneProxy | !IsUsingCustomRenderer() 才创建;Nanite 条件 UseNanite(ShaderPlatform) && RestCollection->EnableNanite && HasNaniteData() && r.GeometryCollection.Nanite!=0;否则 HasMeshData() → 普通 proxy | …:1149-1178 |
| CustomRenderer 实例化 | bOverrideCustomRenderer ? CustomRendererType : RestCollection->CustomRendererType;NewObject(this, Type, RF_Transient|RF_DuplicateTransient) | …:3693-3717 |
要点:
- 游戏世界里,用了 ISMPool 的 GC 根本没有 GC SceneProxy(
ShouldCreateRenderState=false),渲染线程不为它维护任何 GC 专用状态。 - 编辑器(非 Game World)总是创建 RenderState,因为 Fracture 编辑需要 Native 渲染(
GeometryCollectionComponent.cpp:1141注释原文)。
1.2 IGeometryCollectionExternalRenderInterface 契约
GCE/Public/GeometryCollection/GeometryCollectionExternalRenderInterface.h
| 成员 | 作用 | 行 |
|---|---|---|
EState_Visible / EState_Broken / EState_ForcedBroken | 状态位:可见、已破碎(不再渲染 Root Proxy)、强制破碎 | :23-28 |
ShouldUseNativeFallback / CanEverUseNativeFallback | 渲染器可以在某些状态下要求回退到 Native 渲染 | :31-33 |
OnRegisterGeometryCollection / OnUnregister… | 组件注册/反注册生命周期 | :36-38 |
UpdateState(GC, ComponentTransform, Flags) | 状态切换点,渲染器据此申请/释放资源 | :44 |
UpdateRootTransform / UpdateRootTransforms | 未破碎态:只更新 Root(及多个 Root Proxy 的局部变换) | :50,58 |
UpdateTransforms(GC, TArrayView<FTransform3f>) | 已破碎态:全部骨骼的组件空间变换 | :63 |
IGeometryCollectionCustomDataInterface | SetCustomInstanceData(index/name, value)、SetRenderCustomDepthStencil | :84-97 |
驱动入口 UGeometryCollectionComponent::RefreshCustomRenderer(…GeometryCollectionComponent.cpp:6522-6590):
bIsBroken = BrokenAndDecayedStates.GetIsRootBroken() // Shipping 外可被 r.GeometryCollection.CustomRenderer.ForceBreak 覆盖
bRenderRootProxy= !bForceBrokenForCustomRenderer && !bIsBroken && RootProxyData.ProxyMeshes.Num() > 0
StateFlags = Visible(未隐藏) | (bRenderRootProxy ? 0 : Broken) | (ForceBroken ? ForcedBroken : 0)
→ 询问 ShouldUseNativeFallback → 必要时 Unregister/Register 并 MarkRenderStateDirty
→ UpdateState(...)
→ bRenderRootProxy ? UpdateRootTransform(s) : UpdateTransforms(ComponentSpaceTransforms.RequestAllTransforms())坑点:
- 没有 RootProxyData 时
bRenderRootProxy恒为 false,即从第一帧起就走 "已破碎" 分支(AutoInstanceMeshes 实例)。 p.Chaos.GC.CustomRendererHiddenActorFix(默认 true)才会把Actor->IsHidden()计入可见性(…:145,6542-6546)。IGeometryCollectionCustomDataInterface:本源码树内没有任何类实现它(Source/Runtime/Experimental、Plugins/Experimentalgrep 仅命中声明与 BlueprintLibrary 调用方),UGeometryCollectionISMPoolRenderer也只实现了 External 接口(GCE/Private/GeometryCollection/GeometryCollectionISMPoolRenderer.h:15)。即SetCustomInstanceDataByIndex/ByName/SetCustomDepthStencil(GCE/Private/GeometryCollection/GeometryCollectionBlueprintLibrary.cpp:14-46)对内置 ISMPool 渲染器是空操作,需要项目自己写 Renderer 实现该接口。
1.3 ISM Pool:把成千上万碎块变成"少量 ISM + 实例"
1.3.1 模块与对象层级(ISM/Public/ISMPool/ISMPoolComponent.h)
UISMPoolSubSystem (World) ── 每个 ULevel 一个 AISMPoolActor(RF_Transient,按需 SpawnActor) ISM/Private/ISMPool/ISMPoolSubSystem.cpp:28-54
└ UISMPoolComponent (actor 的 Root) ISMPoolComponent.h:343-401
├ FISMPool Pool :284-336
│ ├ TArray<FISMPoolISM> ISMs 每项 = 一个 UInstancedStaticMeshComponent + 实例组分配表 + InstanceIds
│ ├ TMap<FISMPoolStaticMeshInstance, ISMIndex> MeshToISMIndex ← 分组键→ISM
│ ├ FreeList (空槽,无组件) / FreeListISM (有已注册组件、待复用)
│ └ PrellocationQueue (预热队列)
└ TMap<MeshGroupId, FISMPoolMeshGroup> MeshGroups 一个 GC(或其一种状态)= 一个 MeshGroup,组内每个 mesh = 一个 FISMPoolMeshInfo{ISMIndex, InstanceGroupIndex}1.3.2 分组键(决定"多少个 ISM / 多少 Draw")
FISMPoolStaticMeshInstance(ISMPoolComponent.h:165-207)相等且 hash 相同才共享同一个 ISM:
| 键成分 | 说明 | 位置 |
|---|---|---|
StaticMesh(Weak) | 网格 | :167 |
MaterialsOverrides[] | 覆盖材质逐项比较 | :168,182-188 |
CustomPrimitiveData[] | 逐 float 比较 | :169,193-200 |
Desc(FISMPoolComponentDescription) | Flags / NumCustomDataFloats / Position / Start,EndCullDistance / MinLod / CustomDepthStencilValue / LodScale / GroupHash / Tags / StatsCategory | :101-145 |
Desc.Flags默认 =WorldPositionOffsetWritesVelocity \| EvaluateWorldPositionOffset \| AffectShadow(:119)→ 默认投影、不参与动态间接光/距离场/远阴影;StartCullDistance/EndCullDistance默认 0(即无距离剔除)。GroupHash(:126)注释:"Optional, allows identical SMs to be separated into different groups for finer grained culling"——需要更细剔除粒度时用它拆组。- GC 内置渲染器只填了
StaticMesh / MaterialsOverrides / Desc.NumCustomDataFloats(GeometryCollectionISMPoolRenderer.cpp:168-178, 217-226),其余用默认。⇒ 同一 Level 内所有用同一"网格+材质"的 GC 共享同一批 ISM(跨 GC 合批,这是它最大的收益)。 - 编辑器里每个 GC 自带一个
LocalISMPoolComponent(GeometryCollectionISMPoolRenderer.cpp:21-37,保证"编辑/运行外观一致、编辑器 hit proxy 可用"),不跨 GC 共享;运行时才用 per-level 的共享池(:116-141)。
1.3.3 实例分配(FISMPoolInstanceGroups / FISMPoolISM::AddInstanceGroup)
| 机制 | 细节 | 位置 |
|---|---|---|
| 分配范围固定 | 组范围 {Start,Count} 一旦分配不变;真实渲染 index 会被 ISM 重排,所以另存 InstanceIds[](FPrimitiveInstanceId)做映射 | ISMPoolComponent.h:17-21,276-279 |
| 复用只认相同 Count | AddGroup 先扫描 FreeList,Count == GroupRanges[id].Count 才复用(注释 todo:可分配子范围);否则追加 | GeometryCollectionISMPoolComponent.h:61-81(ISM 版结构同源,ISMPoolComponent.h:22-96) |
| 删除只是进 FreeList | 注释:"Actually removing it would require too much shuffling of the render instance index remapping." | 同上 :84-90 |
| 新组零缩放预分配 | PreAllocateInstancesMemory(Count) + AddInstancesById(ZeroScaleTransforms);之后 UpdateInstanceTransformById 才真正摆放 | ISMPoolComponent.cpp:258-293 |
| 逐实例更新 | 对每个实例调用 UpdateInstanceTransformById(Id, T, bWorldSpace, bTeleport);若 ISM 组件自身是 Identity 变换则跳过世界→局部换算;代码注释 todo:"batching… seem to be just simple loops, so probably no benefit" | ISMPoolComponent.cpp:345-401 |
| 零缩放删除(可选) | bAllowPerInstanceRemoval 时 Scale=0 → RemoveInstanceById,非 0 → 重新 Add;GC 渲染器 CreateMeshGroup() 用默认 false,所以不走此路径,衰减到 0 的碎块在 ISM 里仍占着实例槽(零缩放)直到整组释放 | ISMPoolComponent.cpp:371-392;GeometryCollectionISMPoolRenderer.cpp:152,194 |
| 组件创建参数 | SetRemoveSwap()、NoCollision、不影响导航;bUseConservativeBounds=true、bComputeFastLocalBounds=true(快速但较松的包围盒);InstancingRandomSeed=1(固定种子,避免每次运行随机不同);bDisallowNanite 仅当有 Translucent 覆盖材质时置位——Nanite 与否由 StaticMesh 自身设置决定(GC 的 EnableNanite 不管 ISM 路径) | ISMPoolComponent.cpp:131-154, 172-256 |
1.3.4 组件生命周期("池"的真正含义)
| 模式 | CVar | 行为与取舍 | 位置 |
|---|---|---|---|
| Recycle(默认) | r.ISMPool.ComponentRecycle=true | 实例清空→组件不注销、仅销毁 SceneProxy 放入 FreeListISM,换新描述可复用;省 register/unregister,但仍要重建 proxy 与 MDC 缓存;"内存成本有界" | ISMPoolComponent.cpp:38-49,481-494 |
| KeepAlive | r.ISMPool.ComponentKeepAlive=false | 实例清空也保留组件与 SceneProxy:省掉注册+建 proxy+建 MDC 的"高成本",代价是组件持有 StaticMesh 硬引用,内存高;只有 KeepAlive 下预热队列才有效 | :28-36;:524-535 |
| ClearMeshOnRecycle | r.ISMPool.ClearComponentMeshOnRecycle=true | 空闲链表里的组件清空 StaticMesh,防止悬挂引用 | :51-55,488-492 |
| FreeListTargetSize | r.ISMPool.ComponentFreeListTargetSize=50 | 空闲链表目标大小,超出每次 Tick 只释放 1 个(RemoveCountPerTick=1) | :57-64,651-661 |
| Preallocate | API UISMPoolComponent::PreallocateMeshInstance | 提前创建某描述的 ISM;每次 Tick 最多建 2 个(PreallocateCountPerTick=2)。GC 渲染器不会自动调用它(renderer 中无调用),需项目自行预热;且 RequestPreallocateMeshInstance 在 !KeepAlive 时直接忽略 | :524-535,644-649;ISMPoolComponent.h:383 |
| 池 Tick | TickInterval=0.25s,只在有预热队列或空闲超标时开启 | :668-671, 611-614, 674-679 | |
| ShadowCopyCustomData | r.ISMPool.ShadowCopyCustomData=false | 保存 custom data 副本以便"移除再加入"时还原 | :66-71,95-99 |
坑点:
- 首次破碎尖峰:未破碎→破碎时
InstancesGroup一次性AddInstancesById(NumInstances × 零缩放);若该"网格+材质"的 ISM 还不存在,还要NewObject+RegisterComponent+SetStaticMesh(含 PSO 预缓存)。预热需要KeepAlive=1才有意义。 - 这条路径下碎块的剔除/LOD 全部交给 ISM(
MinLod / LodScale / cull distance都在 Desc 里,GC 渲染器没有设置它们,需要自定义渲染器才能给碎块配距离剔除)。
1.3.5 UGeometryCollectionISMPoolRenderer 状态机
GCE/Private/GeometryCollection/GeometryCollectionISMPoolRenderer.cpp
状态(来自 UpdateState) | 动作 | 行 |
|---|---|---|
!Visible | 释放 InstancesGroup 与 MergedMeshGroup | :65-69 |
Visible && !Broken && MergedMeshGroup 为空 | 释放碎块实例 → InitMergedMesh:对 RootProxyData.ProxyMeshes[] 各加 1 个实例(材质取 MeshOverrideMaterials[i]) | :72-79,143-183 |
Visible && Broken && InstancesGroup 为空 | 释放 Merged → InitInstances:对 AutoInstanceMeshes[] 各加 NumInstances 个实例(带 CustomData;材质与 StaticMesh 默认不同才覆盖) | :81-88,185-229 |
| 每次刷新(未破碎) | UpdateMergedMeshTransforms:FTransform(LocalTransforms[i]) * Root * ComponentTransform,bWorldSpace=true,bMarkRenderStateDirty=false | :231-257 |
| 每次刷新(已破碎) | UpdateInstanceTransforms:见下 | :259-296 |
UpdateInstanceTransforms 的复杂度坑点:
for (MeshIndex : InstancesGroup.MeshIds) // M 种 AutoInstanceMesh
for (TransformIndex = 0; TransformIndex < NumTransforms; ++TransformIndex) // 全部 N 个骨骼
if (facade.GetIndex(TransformIndex)==MeshIndex && Children[TransformIndex].Num()==0) // 仅叶子
Add(FTransform(InTransforms[TransformIndex]) * InBaseTransform)⇒ O(M×N) 每次脏刷新,且为 GC 内全部叶子(含已衰减/零缩放的)提交矩阵;资产里已缓存的 AutoInstanceTransformRemapIndices(GeometryCollectionObject.cpp:376-423)在 5.7 的这个渲染器里没有被使用(全 GCE 无调用点)。【推论】 自定义渲染器可以用它把循环降为 O(N)。
1.3.6 UGeometryCollectionRootProxyRenderer("未破碎用整体网格")
GCE/Public/GeometryCollection/GeometryCollectionRootProxyRenderer.h / …Private/…RootProxyRenderer.cpp
- 为每个
RootProxyData.ProxyMeshes[i]创建一个 UStaticMeshComponent(NoCollision、不影响导航、Mobility 继承 GC,挂在 GC 上):.cpp:58-98。 UpdateState:bSetVisible = !Broken && Visible,只切换SetVisibility;UpdateTransforms()(已破碎态)什么也不做,注释 "Don't support non root proxy mesh."(.cpp:23-56)。ShouldUseNativeFallback = (Flags & EState_Broken),CanEverUseNativeFallback=true(.h:29-30):破碎后让位给 Native 渲染(含 Nanite)。这是"未破碎用 Nanite/普通静态网格、破碎后才用 GC proxy"的标准组合;Fracture 工具 "New" 且勾选 Use Root Proxies 时自动设置(PLG/ChaosEditor/Source/FractureEditor/Private/FractureToolGenerators.cpp:1004-1008,勾选框默认 true:FractureToolGenerators.h:24,.cpp:209)。
对比两种 Renderer:
| RootProxyRenderer | ISMPoolRenderer | |
|---|---|---|
| 未破碎 | StaticMeshComponent | ISM 单实例(Merged Mesh) |
| 破碎后 | 回退 Native GC proxy(可 Nanite) | ISM 实例(AutoInstanceMeshes) |
| 对碎块数量的敏感度 | 取决于 Native 路径 | 取决于 ISM 实例更新 |
| 碎块可否用 Nanite | 可(GC Nanite) | 取决于被实例化的 StaticMesh 的 Nanite 设置 |
RootProxyData(GeometryCollectionObject.h:338-365):ProxyMeshes[]、MeshTransforms[](相对 GC,缺省视为 Identity,5.6 前资产兼容)、MeshOverrideMaterials[];Dataflow 终端节点 GeometryCollectionTerminal_v2 的 RootProxyMeshes 输入写入此结构(PLG/GeometryCollectionPlugin/Source/GeometryCollectionNodes/Private/Dataflow/GeometryCollectionAssetNodes.cpp:121-171;RootProxyMesh.Transform 是组件空间,写入时转成相对 Root::158)。
1.4 Native 渲染细节(FGeometryCollectionSceneProxy / FNaniteGeometryCollectionSceneProxy)
文件:GCE/Private/GeometryCollection/GeometryCollectionSceneProxy.{h,cpp}、GeometryCollectionRenderData.cpp
| 主题 | 事实 | 位置 |
|---|---|---|
| 骨骼变换 | 每骨骼存完整 FMatrix44f(64 B,与 ISM 不同,注释:为了运行时 memcpy 简化);缓冲 = NumTransforms*4 个 FVector4f,Dynamic+SRV | SceneProxy.h:33-58 |
| 动态数据 | FGeometryCollectionDynamicData{TArray<FMatrix44f> Transforms},从 FGeometryCollectionDynamicDataPool(FreeList/UsedList + 锁)取,避免每帧分配;GT 侧 InitDynamicData 把 FTransform3f→FMatrix44f 全部转换 | SceneProxy.h:89-133;SceneProxy.cpp:1800-1831;Component.cpp:3328-3358 |
| 上传 | 三重缓冲:r.GeometryCollectionTripleBufferUploads=1 且 RHI 支持 MapWriteNoOverwrite 时一次建 4 个 buffer,用 RLM_WriteOnly_NoOverwrite 上传("much faster with large amounts of data");上一帧 buffer 作 Prev 变换 | SceneProxy.cpp:73-79,343-374 |
| 无 Shader 骨骼回退 | !bUseShaderBoneTransform 时 RT 上 CPU 蒙皮:UpdateSkinnedPositions,ParallelFor,批大小 r.ParallelGeometryCollectionBatchSize=1024(顶点),可用 ISPC(r.GeometryCollectionSetDynamicData.ISPC) | SceneProxy.cpp:65-71,376-461,126-136 |
| 骨骼索引宽度 | 顶点 BoneMap 为 uint16(BonesPerVertex) | GeometryCollectionRenderData.cpp:157,196,263 |
| Section / SubSection | Sections(按材质合并)、SectionsNoInternal、SubSections(按"变换×材质",仅 RayTracing 与 rigid-picker 用,已 cook 的包不序列化 SubSections) | RenderData.cpp:436-529,137-144;SceneProxy.cpp:463-468,826 |
| Nanite 构建 | NaniteSettings:KeepPercentTriangles=1、TrimRelativeError=0、FallbackPercentTriangles=1(无减面),TargetMinimumResidencyInKB=NaniteMinimumResidencyInKB;普通 mesh 数据仅在 !bEnableNanite || bEnableNaniteFallback 时生成;即开 Nanite 且不开 Fallback 时只有 Nanite 数据(在不支持 Nanite 的平台上 CreateSceneProxy 会因 UseNanite()==false && HasMeshData()==false 而不创建 proxy。CreateSceneProxy 虽读取了 r.Nanite.ProxyRenderMode,但在该函数内并未使用:Component.cpp:1151-1152;所以需要在 Nanite 不可用的平台上显示碎块就要开 bEnableNaniteFallback) | RenderData.cpp:640-678,689-697 |
| Nanite proxy | 每个 SimulationType==FST_Rigid 的变换 = 1 个 GPUScene 实例(InstanceToPrimitiveRelative / InstanceLocalBounds / InstanceHierarchyOffset);bAlwaysHasVelocity=true;bCanSkipRedundantTransformUpdates=false;每次 SetDynamicData 重建全部实例,Prev 取上一帧数组 | SceneProxy.cpp:1300-1312,1593-1664 |
| Nanite 限制 | 仅 Opaque/Masked 且非 SingleLayerWater,否则强制默认材质;bVisibleInLumenScene=false("Dynamic draw path without Nanite isn't supported by Lumen");bSupportsDistanceFieldRepresentation=false | SceneProxy.cpp:1314-1317,1343-1363 |
| 光追 | r.RayTracing.Geometry.GeometryCollection=0(默认不参与)、…CombinedBLAS=0(合并 BLAS 任一变换变化就整体重建;用 WPO 自动启用)、…WPO=true、…ForceUpdate=0 | SceneProxy.cpp:81-123 |
| 全局 Nanite 开关 | r.GeometryCollection.Nanite=1(改动触发全局重建 RenderState);资产 EnableNanite 默认 false | Component.cpp:475-485;GeometryCollectionObject.cpp:118 |
| 瘦身 cook | bStripOnCook:Nanite 资产→GenerateMinimalGeometryCollection;非 Nanite→CopyCollectionAndRemoveGeometry(注释:"…rendered using ISMPool or an external rendering system")。bStripRenderDataOnCook:连 mesh/nanite 渲染数据都不 cook("if the cooked build uses a custom renderer such as the ISMPool renderer") | GeometryCollectionObject.cpp:988-1006;GeometryCollectionObject.h:616-627;RenderData.cpp:714-745 |
1.5 AutoInstanceMeshes(把"按源网格复用"的碎块交给 ISM)
- 结构
FGeometryCollectionAutoInstanceMesh{Mesh, Materials[], NumInstances, CustomData[]}(GeometryCollectionObject.h:69-94);去重键 =(Mesh, Materials)(GeometryCollectionObject.cpp:1828-1831)。 - 来源:
AppendStaticMesh每追加一个源 StaticMesh,就把它产生的所有变换在FCollectionInstancedMeshFacade(属性AutoInstanceMeshIndex)上标为同一AutoInstanceMeshIndex(GeometryCollectionEngineConversion.cpp:567-588,605-610);Dataflow 路径由CreateGeometryCollectionFromSources_v2输出InstancedMeshes再经终端节点SetAutoInstanceMeshes写入(GeometryCollectionAssetNodes.cpp:140-146)。 SetAutoInstanceMeshes会去重并重映射索引,聚合NumInstances,且仅为叶子(Children.Num()==0)搬运 CustomData(GeometryCollectionObject.cpp:1857-1926)。- 消费者:只有 ISMPool 渲染器(§1.3.5)。即未破碎的碎块如果来自同一源网格,破碎后其渲染 = 同一 ISM 的多个实例,而非 GC 的合并网格。
- 取舍:ISM 路径渲染的是"源网格",所以碎片的内部断面(Internal Faces)不会被渲染——Native 路径渲染的是分割后的碎片几何(含内部面)。【推论】 要在 ISM 路径显示断面,需要用"分割后的碎片网格"作为被实例化的 Mesh,而不是原始整体网格。
1.6 EmbeddedGeometry / Embed(把小零件"嵌"进大块)
原理(源码事实)
- 编辑:Fracture 工具
Embed(选中的 StaticMesh→绑到所选骨骼)与Auto(对每个静态网格,遍历所有 Rigid 骨骼的凸包,取PhiWithNormal最小者所在骨骼)→ 都走AttachEmbeddedGeometryExemplar(mesh)(按FSoftObjectPath去重,返回 ExemplarIndex,GeometryCollectionObject.cpp:1796-1810)+FGeometryCollection::AppendEmbeddedInstance:新增一个 Transform 元素,SimulationType=FST_None,ExemplarIndex=…,Parent=所选骨骼,TransformToGeometryIndex=INDEX_NONE(CH/Private/GeometryCollection/GeometryCollection.cpp:448-465;FractureToolEmbed.cpp:53-116, 172-295)。工具提示:"passive geometry … Will be lost if GeometryCollection is Reset!"。Flush工具会移除全部嵌入并可选择拆成场景里的 StaticMeshActor(FractureToolEmbed.cpp:371-460)。 - 运行时:
InitializeEmbeddedGeometry为每个 Exemplar 建一个UInstancedStaticMeshComponent(Stationary、NoCollision、CastShadow=false、不影响导航、SetCullDistances(Exemplar.Start/EndCullDistance)),ActorOwner->AddInstanceComponent后注册(GeometryCollectionComponent.cpp:6433-6470)。 - 刷新:
RefreshEmbeddedGeometry(:1741-1819,在UpdateRenderSystemsIfNeeded里每次动态数据脏时调用,:4565-4568):对每个 Exemplar 扫描全部 N 个变换、取ExemplarIndex==i且非Hide的组件空间矩阵;实例数变化→ClearInstances + 全部 AddInstance + MarkRenderStateDirty,否则BatchUpdateInstancesTransforms(0, T, bWorldSpace=false, bMarkRenderStateDirty=true, bTeleport=false);源码 todo:等 ISMC 支持原位更新再改。
设计取舍/坑点
- 嵌入物是父骨骼的"被动子节点":随父骨骼变换(包括衰减缩放),不参与物理、不可单独破碎;父块被移除(衰减到 0)时它一起消失。
- 开销:每次脏刷新
O(Exemplars × N)扫描 + 每个 Exemplar 一次 ISM 批量更新且bMarkRenderStateDirty=true;大量 Exemplar×大 N 是 GT 热点。 - 默认
CastShadow=false、Stationary,适合"螺丝/管线/装饰小件",不适合需要投影或 Lumen 参与的物件。 FractureEngineEmbed(PLG/Fracture/Source/FractureEngine/{Public,Private}/FractureEngineEmbed.*)是空类,GeometryCollectionEmbedNodes.cpp里的GeometryCollectionEmbedNodes()是空函数 ⇒ 5.7 没有 Dataflow 版 Embed,只有上面的编辑器工具链。- 统计面板里 "Embedded" 行 =
SimulationType==FST_None的变换个数(§6.4)。
1.7 渲染最佳实践(大规模)
- 碎块数量大、碎块网格重复 →
CustomRendererType = UGeometryCollectionISMPoolRenderer+ 同时勾bStripRenderDataOnCook(不 cook GC 自带渲染数据,省内存),并准备RootProxyData(未破碎态用整体网格/Nanite)。 - 碎块是独特几何、数量中等 → Native Nanite(
EnableNanite),未破碎态用UGeometryCollectionRootProxyRenderer;注意 Nanite proxy 每脏帧 O(N) 重建实例,N 太大时拆分 GC。 - 要给 ISM 碎块加距离剔除/LOD 偏置/阴影开关 → 自定义 Renderer(实现
IGeometryCollectionExternalRenderInterface),在FISMPoolComponentDescription里设StartCullDistance/EndCullDistance/MinLod/LodScale/Flags。默认全是"不剔除、投影"。 - 预热:
r.ISMPool.ComponentKeepAlive=1+ 项目启动/关卡加载时对已知"网格+材质"调用PreallocateMeshInstance,避开首次破碎注册尖峰;代价=常驻硬引用。 - 碎片材质数量要克制:每个不同的"网格+材质覆盖+Desc"都会多一个 ISM(多一次 Draw、一个 SceneProxy)。
- 光追默认对 GC 关闭(
r.RayTracing.Geometry.GeometryCollection=0);打开后别用 CombinedBLAS 配合大 N。 - 破碎后的 Lumen 注意:Native Nanite GC
bVisibleInLumenScene=false(SceneProxy.cpp:1316)。
2. 物理线程 → 游戏线程:碎块 Transform 回传的真实开销
2.1 数据结构(CH/Public/GeometryCollectionProxyData.h:247-399)
FGeometryCollectionResults:只存"被修改的"变换
TBitArray<> ModifiedTransformIndices(:384)+ 三个紧凑数组States / Positions / Velocities(:385-387),用AddEntry(TransformIndex)追加并置位(:353-361);通过CountSetBits把 TransformIndex 映射回条目(:319-326)。- 每条:
FStateData{TransformIndex, InternalClusterUniqueIdx, HasParent, FState{DynamicState:8, DisabledState, HasDecayed, HasInternalClusterParent, DynamicInternalClusterParent, HasClusterUnionParent}}、FPositionData{X,R}(double 向量+旋转)、FVelocityData{V,W}(float,FVec3f)。 - 编辑器额外有每变换的
Damages数组(WITH_EDITORONLY_DATA,:389-393)。
2.2 PT 端:谁会被缓冲
- 以 proxy 为粒度:
FPBDRigidsSolver::BufferPhysicsResultsSingleThreaded遍历 solver 的 DirtyParticles 视图,把"含有脏粒子"的 GC proxy 收集进ActiveGC;动态内部簇会把其所有子 proxy 一并激活(CH/Private/PBDRigidsSolver.cpp:2874-2975),再对每个 ActiveGC 调BufferPhysicsResults_Internal(:3077-3086)。⇒ 整体休眠的 GC,在 PT 缓冲阶段成本为 0。 - 进入后以粒子为粒度:
BufferPhysicsResults_Internal遍历NumEffectiveParticles,对每个"未 Disabled"的粒子(或 Active 状态刚变化的)写一条 entry(GeometryCollectionPhysicsProxy.cpp:4210-4358,判定:4220-4256)。休眠但未 Disabled 的粒子照样写入;已 Disabled(已衰减/被簇吞掉的子体)的粒子才被跳过。 - 对"有内部簇父节点"的子体,要用
ChildToParent × 簇位姿现算世界位姿(:4282-4312);bGeometryCollectionEnabledNestedChildTransformUpdates(默认 true)还会更新"被驱动的禁用子体"以支持 line trace(:81-85,4314-4326)。 - 每条还要做
MassToLocal的相对变换求逆(:4336-4346)并在需要时写回PhysicsThreadCollection。
2.3 GT 端:PullFromPhysicsState
GeometryCollectionPhysicsProxy.cpp:4718-4975;由 PullPhysicsStateForEachDirtyProxy_External 驱动(CH/Public/Chaos/PhysicsSolverBaseImpl.h:46-186)。
| 阶段 | 做什么 | 位置 |
|---|---|---|
| 非插值数据 | Active/Disabled、DynamicState、HasParent、内部簇类型、HasDecayed→GTParticle.SetDisabled(true);无插值时同时 UpdateGTParticleXR/VW | :4565-4715 |
| 判变 | UpdateGTParticleXR:!NewX.Equals(OldX, GeometryCollectionPositionUpdateTolerance) 才 SetX,旋转同理;变了才 UpdateShapeBounds,返回 changed。容差默认 UE_KINDA_SMALL_NUMBER(1e-4),可调 p.GeometryCollection.{Position,Rotation,LinearVelocity,AngularVelocity}UpdateTolerance | :4394-4419;:117-139 |
| 插值(异步物理) | NextPullData 存在时在 prev/next 间 FResultInterpolator 插值 X/R/V/W(状态是阶跃函数) | :4795-4907;PhysicsSolverBaseImpl.h:51-127 |
| 局部变换重算 | 只对"本次或下次被修改过"的粒子 RebaseParticleGameThreadCollectionTransformOnNewWorldTransform_External | :4909-4943 |
| 置脏 | 任一粒子变了 → GameThreadCollection.MakeDirty() | :4961-4965 |
| 回调 | PostPhysicsSyncCallback() → UGeometryCollectionComponent::OnPostPhysicsSync | :4969-4972 |
⇒ "只传 changed bones" 的真实含义:缓冲和容差判变是按骨骼的;但一旦该 GC 有任何变化,后面的 GT 变换重算、Removal 计时、渲染提交都按整个 GC(N 个变换)全量(见 §2.4)。
2.4 OnPostPhysicsSync → 渲染(Component.cpp:4412-4587)
if DynamicCollection->IsDirty(): OnTransformsDirty() // ComponentSpaceTransforms.MarkDirty + Bounds 失效
UpdateAttachedChildrenTransform()
UpdateBrokenAndDecayedStates() // 遍历全部变换 O(N)(根未破碎前整体跳过)
if RootBroken:
有任何 Decaying → 开启组件 Tick;否则 InitializeRemovalDynamicAttributesIfNeeded
UpdateRemovalIfNeeded() // 衰减缩放,见 §3.4
CheckFullyDecayed() // 触发 OnFullyDecayedEvent
bDynamicDataIsDirty = Dynamic dirty && HasVisibleGeometry()
UpdateRenderSystemsIfNeeded(dirty): // :4546-4587
RefreshEmbeddedGeometry()
if bUpdateCustomRendererOnPostPhysicsSync (默认 true): RefreshCustomRenderer()
if SceneProxy: (Nanite→FlushGPUSceneUpdate_GameThread) MarkRenderTransformDirty + MarkRenderDynamicDataDirty
UpdateNavigationDataIfNeeded() // 每 256 帧错峰(NavmeshInvalidationTimeSliceIndex 每组件+3 错开)ComponentSpaceTransforms.RequestAllTransforms()(:6185-6256):脏时用广度优先顺序(GetBreadthFirstTransformIndices)全量计算Child×Parent链,O(N);统计STAT_GCCUGlobalMatrices。- 渲染提交
SendDynamicDataToSceneProxy(:5040-5069):每次InitDynamicData拷贝 N 个矩阵,INC_DWORD_STAT_BY(STAT_GCTotalTransforms, N)只统计 Nanite 路径。 - 组件 Tick 默认关闭(
bStartWithTickEnabled=false,:674-677),仅在有碎块处于衰减时由OnPostPhysicsSync打开(:4458-4464),无衰减后自行关闭(:3618-3622)。
2.5 异步物理 / 线程相关默认值
| 项 | 默认 | 位置 |
|---|---|---|
Tick Physics Async(bTickPhysicsAsync) | false | ENG/Private/PhysicsEngine/PhysicsSettings.cpp:24 |
AsyncFixedTimeStepSize | 1/30 s | …PhysicsSettings.cpp:25 |
MaxPhysicsDeltaTime | 1/30 s;MaxSubstepDeltaTime 1/60;MaxSubsteps 6;bSubstepping=false | …:22-27 |
DefaultThreadingModel | TaskGraph;DedicatedThread 模式 TickMode=VariableCappedWithTarget、BufferMode=Double | …:197-201 |
p.UseAsyncInterpolation / p.AsyncInterpolationMultiplier | 1 / 2.0(回看 2 个固定步长) | CH/Private/Chaos/Framework/PhysicsSolverBase.cpp:262-285 |
p.AsyncPhysicsBlockMode | 0(GT 阻塞到过往帧的物理步完成) | …:291 |
p.MaxPhysicsStepsPerGameTick | 3 | …:579 |
异步 tick 空闲关闭 p.Chaos.GC.MaxGeometryCollectionAsyncPhysicsTickIdleTimeMs | 30 | 死 CVar:全 Source/Runtime/Experimental 只有注册,无读取点 Component.cpp:125-126 |
注:GC 组件仍实现 AsyncPhysicsTickComponent(服务端 UpdateRepData,Component.cpp:3641-3661),客户端复制/外推见 §3.9。
2.6 成本模型小结(按 N=单个 GC 的变换数,K=本帧变化的碎块数)
| 阶段 | 复杂度 | 线程 | 备注 |
|---|---|---|---|
| PT 缓冲 | O(active 粒子数)(仅对含脏粒子的 proxy) | PT | 休眠整体免费 |
| GT 判变 | O(entries) | GT | 容差可调 |
| 组件空间矩阵 | O(N) 每脏帧 | GT | STAT_GCCUGlobalMatrices |
| Removal 计时 | O(N) 每 Tick(有衰减时) | GT | 代码 todo:"move removal logic on the physics thread"(:3614) |
| Embedded 刷新 | O(Exemplars×N) | GT | |
| ISM 刷新 | O(M×N) 扫描 + O(叶子数) 实例更新 | GT | UpdateInstanceTransforms |
| Native 提交 | O(N) 拷贝(64 B/个) + 上传 | GT+RT | 脏即全量 |
| Nanite 提交 | O(rigid 数) 重建实例数据 | RT | 同上 |
【推论 · 拆分 GC】:因为 PT 缓冲、GT 重算、渲染提交都以"GC 组件"为单位且只处理含脏数据的组件,把一个超大 GC 按空间/功能拆成多个 GC,可让"远处/静止的那部分"零成本;代价是簇连通性/伤害传播不能跨 GC(同一 ClusterGroupIndex 的多个 GC 才会合成一个簇,GeometryCollectionComponent.h:1004-1006)、多一份 proxy 与组件开销。
3. 碎片生命周期与成本控制
3.1 状态机
Intact(未破碎, Root 为 Kinematic/Static/Dynamic 之一)
│ strain ≥ DamageThreshold(碰撞/Field/ApplyExternalStrain)
▼
Broken ── 子块以 Dynamic 释放(进入 Island,参与碰撞)
│ │ (可选) ClusteringParticleReleaseThrottling:释放即被禁用(直接消失,无衰减动画)
├── Sleeping(Island 休眠;仍在 PT 缓冲/GT 同步里,粒子未 Disabled)
│
├── [RemoveOnBreak] BreakTimer ≥ PostBreakDuration → Decay 0→1 (或 ClusterCrumbling)
├── [RemoveOnSleep] SleepTimer ≥ MaxSleepTime → Decay 0→1 (或 AutomaticCrumblePartialClusters)
▼
Decaying:GT 把变换缩放到 (1-Decay)(bScaleOnRemoval),Decay≥1 → DisableParticles_External
▼
Decayed(粒子 Disabled,PT/GT 不再同步);全部衰减完 → OnFullyDecayedEvent由 FBrokenAndDecayedStates(Component.cpp:4292-4399)跟踪:IsBroken/HasDecayed 位数组 + NumDecaying 计数,HasFullyDecayed = !HasAnyDecaying && HasDecayed 全置位。
3.2 资产级移除参数(UGeometryCollection,GeometryCollectionObject.h:788-814;默认值 GeometryCollectionObject.cpp:137-143)
| 属性 | 含义 | 默认 |
|---|---|---|
bScaleOnRemoval | 移除时碎块缩小(Remove on Sleep 与 on Break 共用);否则 Decay 仍计,仅在 ≥1 时直接消失 | true |
bRemoveOnMaxSleep(UI: Remove on Sleep) | 休眠超时后移除 | false |
bAutomaticCrumblePartialClusters | 与 RemoveOnSleep 联用:把"部分簇"自动碎成更小片 | true |
MaximumSleepTime(UI: Sleep Min Max,FVector2D) | 休眠多久后开始移除,每个变换在 [min,max] 内随机 | (5.0, 10.0) s |
RemovalDuration(FVector2D) | 移除过程时长(线性 Decay 0→1),同样逐变换随机 | (2.5, 5.0) s |
bSlowMovingAsSleeping | 非休眠但慢速的块也算"休眠"(治抖动) | true |
SlowMovingVelocityThreshold | 慢速阈值(cm/s) | 1 |
组件级开关:bAllowRemovalOnSleep=true、bAllowRemovalOnBreak=true(GeometryCollectionComponent.h:1061-1066;ctor :617-618),运行时也可开:初始化动态属性时不检查 bAllowRemovalOnSleep,以便运行时开启(Component.cpp:3765)。
3.3 RemoveOnSleep 的判定流程(IncrementSleepTimer,Component.cpp:6679-6745)
仅在 RestCollection->bRemoveOnMaxSleep && bAllowRemovalOnSleep 时运行;对每个变换:
- 有内部簇父节点:若其父(原始父)的移除启用且为动态内部簇 →
UpdateDecay(UE_SMALL_NUMBER, crumble=bAutomaticCrumblePartialClusters)(注释:因为要碎簇,只需传一个 >0 的小值)。 - 已 BrokenOff 且启用移除且非 Root:
- 若已开始衰减(Decay>0)计时继续;否则若
bSlowMovingAsSleeping:用ComputeSlowMovingState(瞬时速度 = |ΔPos|/dt < 阈值); ShouldUpdateTimer || IsSleeping→SleepTimer += dt;Decay = clamp((SleepTimer - MaxSleepTime) / RemovalDuration, 0, 1)(RemovalDuration<ε则直接 1):GeometryCollectionEngineRemoval.cpp:222-229。
- 若已开始衰减(Decay>0)计时继续;否则若
- 初始化:
FGeometryCollectionRemoveOnSleepDynamicFacade::SetAttributeValues用FMath::RandRange给每个变换抽签(GeometryCollectionEngineRemoval.cpp:172-196)——非确定性(对比 RemoveOnBreak 用种子随机,见下)。
3.4 RemoveOnBreak("Timer 范围 (min,max)")
数据:资产里每个变换一个 FVector4f(FCollectionRemoveOnBreakFacade,CH/Public/GeometryCollection/Facades/CollectionRemoveOnBreakFacade.h:15-72;打包规则 CH/Private/GeometryCollection/Facades/CollectionRemoveOnBreakFacade.cpp:8-110):
| 分量 | 含义 | 编码 |
|---|---|---|
| X | Break Timer min(破碎后多久开始移除) | 符号位 = Enabled(负=禁用,-FLT_EPSILON 表示 0 且禁用) |
| Y | Break Timer max | |
| Z | Removal Timer min | 符号位 = ClusterCrumbling(负=启用崩解) |
| W | Removal Timer max | |
| 默认值 | DisabledPackedData = (-1,0,0,0)(默认禁用) | …Facade.h:18 |
编辑入口默认值:Fracture 模式工具 RemoveOnBreak:Enabled=true、PostBreakTimer=(0,0)、ClusterCrumbling=false、RemovalTimer=(2,3)(PLG/ChaosEditor/Source/FractureEditor/Private/FractureToolProperties.h:74-96);Dataflow 节点 RemoveOnBreak:bEnabledRemoval=true、PostBreakTimer=(0,0)、RemovalTimer=(0,1)、bClusterCrumbling=false(PLG/GeometryCollectionPlugin/Source/GeometryCollectionNodes/Public/Dataflow/GeometryCollectionNodes.h:1180-1209)。
运行时(FGeometryCollectionRemoveOnBreakDynamicFacade,GeometryCollectionEngineRemoval.cpp:24-117):
- 每变换动态属性:
BreakTimer(初始 0)、PostBreakDuration、BreakRemovalDuration;常量DisabledBreakTimer=-1, DisabledPostBreakDuration=-1, CrumblingRemovalTimer=-1(GeometryCollectionEngineRemoval.h:23-26)。 - 抽签用
FRandomStream(Seed = DynamicCollection.GetNumTransforms()),注释:"generate random value consistently between client and server"(:59-62)→ 服务端/客户端一致;PostBreak = FRandRange(max(0,min), max(min,max));Removal 同理;ClusterCrumbling 的簇写BreakRemovalDuration = -1(:81-83)。 - 更新:
BreakTimer += dt(PostBreakDuration ≥ 0);BreakTimer ≥ PostBreakDuration后Decay = clamp((t-PostBreak)/RemovalDuration,0,1);RemovalDuration<ε→ 立即 1(:98-117)。 - 消费:
IncrementBreakTimer(Component.cpp:6747-6824):对 BrokenOff 的变换,若UseClusterCrumbling:Decay 超过 0 时不是缩放,而是PhysicsProxy->BreakClusters_External(把整个簇崩成子块)并把 Decay 复位为 0、标记HasDecayed(UpdateDecay,:6636-6677);非崩解且 Decay≥1 →DisableParticles_External+SetHasDecayedRecursive。 - 复制客户端:
ReplicationAbandonAfterLevel以内的簇在客户端不独立崩解(:6760-6810)。
3.5 Decay 的渲染表现(UpdateRemovalIfNeeded,Component.cpp:4606-4719)
- 每个 Decay∈(0,1] 的变换:
Scale = 1 - Decay;Scale < ε→ 变换直接置 0 缩放;否则(bScaleOnRemoval)围绕"内切球中心"缩放:CalculateInnerSphere求半径→把缩放中心沿局部 -Z 偏移ShrinkRadius(让碎块"向地面缩"),其中源码 todo 注释:"find a faster way to do that (precompute the data ?)"(:4686-4689)→ 每个衰减中碎块每帧一次 InnerSphere 计算。 - 缩放结果写回
DynamicCollection变换并MakeDirty→OnTransformsDirty(:4712-4716);因此衰减动画会让整个 GC 持续"脏",直到最后一片衰减结束。 - 倍率:
p.Chaos.GC.RemovalTimerMultiplier(默认 1.0):组件 Tick 用max(mult,1e-4)*dt推进计时(:3613,CVar 注册:129)——>1 更快移除(CVar 帮助文本两个 ">1" 是笔误)。 - 修复开关
p.Chaos.GC.RemoveOnBreakDecayFix(默认 true):避免"崩解簇里还有模拟子体时整簇消失几帧"(:150-153,4650-4668)。 - ISM 路径下,衰减到 0 的碎块实例不被删除而是零缩放保留(见 §1.3.3)。
3.6 其它"碎片预算"旋钮(谁限制了动态碎块的总量)
| 旋钮 | 作用 | 默认 | 位置 |
|---|---|---|---|
MaxClusterLevel(资产/组件) | 簇破碎的最大层级;层级 ≥ min(MaxClusterLevel, GlobalMaxSimulatedLevel, MaxSimulatedLevel) 的伤害阈值=FLT_MAX(不可再破) | 100 | Component.cpp:611;PhysicsProxy.cpp:1946-1955 |
MaxSimulatedLevel + p.gc.GlobalMaxSimulatedLevel | 超过该层级的变换根本不创建粒子(CalculateEffectiveParticles:Level <= min(Global, MaxSimulatedLevel) || !EnableClustering)。头文件示例:设 1 时叶子永不进入 solver | 100 / 100 | Component.cpp:612;GeometryCollectionComponent.h:1012-1020;PhysicsProxy.cpp:3190-3221, 1877-1878 |
OneWayInteractionLevel | 层级 ≥ 此值的刚体变成 one-way(像 debris:不对非 debris 施力);-1 关闭 | -1 | Component.h:982-985;PhysicsProxy.cpp:1752,1760 |
p.Chaos.Collision.EnableOneWayInteraction / OneWayStiffness / p.Chaos.Solver.OneWayPairCollisionMode | 打开 one-way 才生效;one-way 对 one-way 默认用球体碰撞(SphereCollision) | true / 0.5 / 2 | CH/Private/Chaos/Collision/PBDCollisionConstraint.cpp:59-65;PBDRigidsEvolutionGBF.cpp:179 |
PerAdvanceBreaksAllowed / PerAdvanceBreaksRescheduleLimit(FChaosSolverDestructionSettings) | 每次 AdvanceClustering 的破裂数上限与"顺延到下一帧"数量;超出的簇按时间戳顺序留在 TopLevelClusterParentsStrained;CVar p.Chaos.Clustering.PerAdvance* 取 Min(设置, CVar) | INT_MAX / INT_MAX | CH/Public/ChaosSolverConfiguration.h:26-31;PBDRigidClustering.cpp:1656-1662,1846-1878,3133-3134 |
ClusteringParticleReleaseThrottlingMin/MaxCount | 当 solver 内 休眠+动态 GC 粒子数 = N,每次释放的新粒子里 floor(n × clamp((N-Min)/(Max-Min),0,1)) 个被直接禁用(Min,Max 都 ≥0 才启用;CVar 与设置取 Max)。→ 硬性"总量上限阀门",但表现是直接消失(无衰减) | -1 / -1 | ChaosSolverConfiguration.h:33-39;PBDRigidClustering.cpp:141-189,1344,3094-3107,3135-3136 |
bOptimizeForRuntimeMemory | 根未破碎时不建 GC 子体物理数据(子体几何/GT 粒子/dynamic collection implicits),首次破碎再分配:省内存、首次破碎有尖峰;旧 CVar p.gc.BuildGeometryForChildrenOnPT/GT、p.gc.CreateGTParticlesForChildren、p.gc.RemoveImplicitsInDynamicCollections 已标 DEPRECATED,改用此项目设置 | false | ChaosSolverConfiguration.h:41-47;PhysicsProxy.cpp:632-684 |
p.ComputeClusterCollisionStrains / p.chaos.MinContactSpeedForStrainEval / p.chaos.MinImpulseForStrainEval | 是否用碰撞算应变;忽略静置接触(接触速度<1 或冲量<≈65.3 不累积) | 1 / 1.0 / 980·2/30 | PBDRigidClustering.cpp:52,2217-2223 |
p.chaos.clustering.breakonlystrained | 只处理"受应变"的簇(关掉=每帧遍历所有簇) | 1 | :1653 |
3.7 休眠体系(Chaos 层,决定"碎块何时算睡着"→ 才会触发 RemoveOnSleep)
| 层 | 默认 | 说明 | 位置 |
|---|---|---|---|
| 阈值来源 | 有物理材质→用材质的 Sleep 线速/角速/计数阈值;无材质→用 CVar 默认;再乘 BodyInstance.SleepThresholdMultiplier;SleepType==NeverSleep 的永不睡 | CH/Private/Chaos/Island/IslandManager.cpp:224-279 | |
UPhysicalMaterial 默认 | SleepLinearVelocityThreshold=1.0、SleepAngularVelocityThreshold=0.05、SleepCounterThreshold=4 | GC 使用组件/资产的 PhysicalMaterial(bDensityFromPhysicalMaterial、PhysicsMaterial) | PCO/Private/PhysicalMaterial.cpp:50-52 |
| 无材质兜底 CVar | p.Chaos.Solver.Sleep.Defaults.SleepCounterThreshold=20、LinearSleepThreshold=0.001、AngularSleepThreshold=0.0087 | IslandManager.cpp:122-131 | |
| Island 休眠总开关 | p.Chaos.Solver.Sleep.Enabled=true | :52-53 | |
| Partial Island Sleep | 项目设置 bPartialIslandSleep=false(CVar p.Chaos.Solver.Sleep.PartialIslandSleep);MomentumPropagation=0.3、PostStepWakeThreshold=20、MinimumMotionless=0.1、LinearWake/AngularWakeThresholdMultiplier=10 | 注释:"CPU optimization for large simulations with many motionless bodies (e.g. structures stacks and unstructured piles at rest)",EXPERIMENTAL | ENG/Classes/PhysicsEngine/PhysicsSettings.h:375-414;PhysicsSettings.cpp:32-37;IslandManager.cpp:56-115 |
p.DisableThreshold2 | 5 帧 | Disable ≠ Sleep:Disable 用材质的 DisabledLinear/AngularThreshold(默认 0,只有 Field 的 DisableThreshold 命令会设置),低于阈值连续 N 帧→粒子被禁用(从 solver 移除) | PBDRigidsEvolutionGBF.cpp:58,1392;IslandManager.cpp:3871-3970;Defines.h:134-135 |
| 孤立粒子 | IsolatedParticle.CounterMultiplier=1 / Linear=1.0 / Angular=1.0 | IslandManager.cpp:140-150 |
提示:Partial Island Sleep 默认关闭;对"一大堆静置碎石堆"这类场景源码注释明确说它是为此设计。
3.8 碰撞成本(组件/资产层)
Size Specific Data(GeometryCollectionObject.h:231-314,选择逻辑 CH/Private/GeometryCollection/GeometryCollectionSizeSpecificUtility.cpp:20-35,调用 PhysicsProxy.cpp:5113-5127)
- 选取依据:优先使用变换的
Size属性(RelativeSize),缺失时用包围盒体积;数组要求按MaxSize升序;从后往前找首个value < MaxSize的条目。每个条目含CollisionShapes[](取[0]建 implicit)与DamageThreshold。 - 新建资产默认:ctor 里
SizeSpecificData.AddDefaulted():MaxSize=99999.9,CollisionShapes[0]={Volumetric, Convex, Reduction 0, Margin 0, CollisionParticles{Fraction 1.0, Max 60}, LevelSet{5,10,25,50}},DamageThreshold=5000(GeometryCollectionObject.cpp:162,168-210)。另一个GeometryCollectionSizeSpecificDataDefaults()(Capsule,Reduction 1.0,Margin=UPhysicsSettings::SolverOptions.CollisionMarginFraction)仅在数组为空/缺默认条目时回填(:292-312,315-352,696-704)。 - 最佳实践(分级):按大小分档——大块 Convex、小碎渣 Box/Sphere/Capsule 或 Reduction、甚至更小直接用 One-Way(§3.6);避免对小块用 LevelSet(
ECollisionTypeEnum/EImplicitTypeEnum;LevelSet 分辨率默认 min/max=5/10,簇 25/50)。
Collision Particles(仅 Chaos_Surface_Volumetric 才有意义,GeometryCollectionObject.h:161-179,211)
- 每条目
CollisionParticlesFraction(默认 1.0)、MaximumCollisionParticles(默认 60);运行时再乘 组件CollisionSampleFraction(默认 1.0)×p.CollisionParticlesPerObjectFractionDefault(默认 1.0),按重要度排序后截断(PhysicsProxy.cpp:363-366, 745)。 - 全局开关
p.Chaos.GC.EnableCollisionParticles=true;仿真采样的简化p.CollisionParticlesMin=10 / Max=2000 / SpatialDivision=10 / UseImplicitCulling=0(CH/Private/GeometryCollection/GeometryCollectionCollisionStructureManager.cpp:25-34;GeometryCollection.cpp:25)。
质量
Mass=2500(bMassAsDensity=true则单位 kg/m³)、MinimumMassClamp=0.1(GeometryCollectionObject.cpp:132-134);solver 侧夹紧区间[MinimumMassClamp, MaximumMassClamp=1e5](CH/Public/GeometryCollection/GeometryCollectionSimulationCoreTypes.h:93-103,使用PhysicsProxy.cpp:450-453, 5610-5639)。→ 极小碎渣质量被抬到 ≥0.1 kg(比物理真实更"重",撞击力偏大)。- 密度来源:
bDensityFromPhysicsMaterial(组件/资产)。
阻尼/迭代
- 阻尼:组件
BodyInstance.LinearDamping/AngularDamping→ 粒子LinearEtherDrag/AngularEtherDrag(Component.cpp:4037-4038;PhysicsProxy.cpp:1762-1763);运行时可SetLinearDamping/…PerTransform(PhysicsProxy.cpp:3925-4030)。 - GC 粒子迭代设置默认 8/1/1(Position/Velocity/Projection;BodyInstance 未覆盖时),而 solver 全局默认 8/2/1:
Component.cpp:4031-4036;PBDRigidsEvolutionGBF.h:62-64。
碰撞过滤
CollisionGroup(组件,默认 0):0=与一切碰撞,INDEX_NONE(-1)=禁用碰撞,两者都≠0 且不相等=禁用(CH/Public/Chaos/Collision/CollisionFilter.h:72-80;簇CollisionGroup=min(children):PhysicsProxy.cpp:2244-2257)。→ 可用来让同一栋建筑的碎块互不碰撞(省接触)。CollisionProfilePerLevel[]/CollisionProfilePerParticle[]:按层级给碎块不同碰撞配置(数组短于层级数则取最后一项):Component.cpp:4881-4998、定义GeometryCollectionComponent.h:1632-1638。→ 例:叶子层用NoCollision/只与 WorldStatic 碰撞。- 默认组件对象类型
ECC_Destructible(Component.cpp:690-691);bSimulatePhysics=true(:694);bEnableDamageFromCollision=true(:616)。 p.GeometryCollectionCollideAll=false(调试:绕过碰撞矩阵)。
Convex 优化
- 资产
bOptimizeConvexes(默认=CVars::bChaosConvexSimplifyUnion,即p.Chaos.Convex.SimplifyUnion=false):注释"Enable p.Chaos.Convex.SimplifyUnion cvar to be able to use it (experimental)"(GeometryCollectionObject.h:766-772,ctor.cpp:136)。 - 编辑器 Fracture 模式生成凸包的默认值:
ConvexCanExceedFraction 0.5、SimplificationDistanceThreshold 10、RemoveOverlaps=All、FractionAllowRemove 0.5、Proximity=Precise(PLG/ChaosEditor/Source/FractureEditor/Public/FractureModeSettings.h:56-100)。
3.9 事件与复制的成本开关
- 组件事件默认全部关:
bNotifyBreaks / Collisions / Trailing / Removals / Crumblings及Global*均为 false(Component.cpp:628-639);Solver 级bGenerateCollisionData / BreakData / TrailingData默认 false(CH/Private/ChaosSolverConfiguration.cpp:19-21)。 - 过滤器(Solver 与 Cache 都有):碰撞(MinMass/MinSpeed/MinImpulse)、破裂(MinMass/MinSpeed/MinVolume)、拖尾、移除(MinMass/MinVolume),默认
FilterEnabled=false,均为 0:CH/Public/SolverEventFilters.h:12-122,实现CH/Private/PhysicsProxy/SolverEventFilters.cpp:12-80。 - 全局
MinDeltaVelocityForHitEvents(默认 0):PhysicsSettings.h:367-369,PhysicsSettings.cpp:31。 - GT 事件派发由
UChaosGameplayEventDispatcher完成(CSE/Private/Chaos/ChaosGameplayEventDispatcher.cpp:47-90,569-713),统计:STAT_DispatchCollisionEvents/BreakEvents/CrumblingEvents。 - 复制(仅网络游戏):
bEnableReplication=false、bEnableAbandonAfterLevel=true、ReplicationAbandonAfterLevel=0、ReplicationMaxPositionAndVelocityCorrectionLevel=100(Component.cpp:653-658);说明:大于该层级的碎块不再发送纠正、可换用AbandonedCollisionProfileName(Component.h:1536-1555,1640-1655);调参 CVarp.GeometryCollectionRep*(Component.cpp:2625-2646)。
4. Solver 配置
4.1 配置来源与优先级
UPhysicsSettings(Project Settings → Physics → Chaos → Solver Options : FChaosSolverConfiguration) PCO/Public/PhysicsSettingsCore.h:121-123
│ 创建 solver 时应用(p.Chaos.Simulation.ApplySolverProjectSettings=1) PCO/Private/ChaosScene.cpp:56
▼
默认世界 Solver ──覆盖→ p.Chaos.Solver.* CVar(值≥0/非-1 才覆盖,如 Iterations.Position) CH/Private/PBDRigidsSolver.cpp:331-351
AChaosSolverActor.Properties(独立 Solver,GC 组件 `ChaosSolver` 属性指向它) CSE/Public/Chaos/ChaosSolverActor.h:106-114; Component.h:693
单体覆盖:BodyInstance 迭代数/睡眠阈值倍数/阻尼 → 每个 GC 粒子 SetIterationSettings Component.cpp:4031-4040; PhysicsProxy.cpp:1745-17724.2 FChaosSolverConfiguration(CH/Public/ChaosSolverConfiguration.h:50-130;默认值 CH/Private/ChaosSolverConfiguration.cpp:8-24、CH/Public/Chaos/PBDRigidsEvolutionGBF.h:62-70)
| 项(UI 分类) | 默认 | 含义 |
|---|---|---|
PositionIterations(Iterations) | 8 | 约束求解位置迭代 |
VelocityIterations | 2 | 速度迭代(旧名 PushOutIterations) |
ProjectionIterations | 1 | 投影迭代 |
CollisionMarginFraction(Collision) | 0.05 | 凸体/盒子的碰撞边距占尺寸比例 |
CollisionMarginMax | 10 | 边距上限 |
CollisionCullDistance | 3 | 两形状相距超过此值不求最近特征 |
CollisionMaxPushOutVelocity | 1000 | 起始穿插分离的最大速度(0=不限) |
CollisionInitialOverlapDepenetrationVelocity | -1 | 初始重叠时的分离速度(-1=无穷;可被 BodyInstance.MaxDepenetrationVelocity 覆盖) |
ClusterConnectionFactor(Clustering) | 1.0 | 簇连接距离因子(内部:AlwaysAcceptBelowDistSqThreshold = 50²·100·Factor,PBDRigidClustering.cpp:2909) |
ClusterUnionConnectionType | DelaunayTriangulation | 簇联合的连接生成方式(枚举 EClusterUnionMethod,ChaosSolverConfiguration.h:9-18) |
DestructionSettings.PerAdvanceBreaksAllowed / RescheduleLimit | INT_MAX / INT_MAX | 每帧破裂预算(§3.6) |
DestructionSettings.ClusteringParticleReleaseThrottlingMin/MaxCount | -1 / -1 | 活动碎块总量阀门(§3.6) |
DestructionSettings.bOptimizeForRuntimeMemory | false | 延迟创建子体物理数据(§3.6) |
bGenerateCollisionData / BreakData / TrailingData(GeneratedData) | false | Solver 级事件数据生成开关(+各自 Filter 设置) |
没有 名为 "MaxCollisionPairs / MaxBatch / Max Particles" 的 Solver 设置(grep ChaosSolverConfiguration.h 无此类字段)。与"配对/粒子上限"最接近的控制:p.Chaos.Collision.MaxShapePairs=100(超过后换通用 midphase,CH/Private/Chaos/Collision/ParticlePairMidPhase.cpp:63-64)、MaxManifoldPoints(-1 不限)、bOptimizeForRuntimeMemory、Throttling、One-Way,以及并行批大小 CVar(§5.7)。
4.3 PhysicsSettings / 引擎默认(ENG/Private/PhysicsEngine/PhysicsSettings.cpp:14-39)
bTickPhysicsAsync=false、AsyncFixedTimeStepSize=1/30、MaxPhysicsDeltaTime=1/30、bSubstepping=false、MaxSubsteps=6、MaxSubstepDeltaTime=1/60。Config/BaseEngine.ini:[/Script/Engine.PhysicsSettings]只有DefaultGravityZ=-980.0、bEnable2DPhysics=false(Config/BaseEngine.ini:3406-3408);[/Script/ChaosSolverEngine.ChaosSolverSettings]仅DefaultChaosSolverActorClass=/Script/ChaosSolverEngine.ChaosSolverActor(:3720-3721)。Config/ConsoleVariables.ini仅p.chaos.AllowCreatePhysxBodies=1(:115);Chaos/GeometryCollection 在BaseScalability/BaseDeviceProfiles里没有默认 CVar。⇒ 所有破碎相关性能默认值来自代码而非 ini。
5. 重要 CVar 表
默认值:读注册行的变量初始值(与源码核对)。
[Dbg]=仅CHAOS_DEBUG_DRAW(非 Shipping/Test 或 Editor)。个别 CVar 的帮助文本与实际默认值不一致,已在"含义"里标注。
5.1 簇 / 破碎 / 应变
| CVar | 默认 | 含义 | 位置 |
|---|---|---|---|
p.Chaos.Clustering.PerAdvanceBreaksAllowed | INT_MAX | 每次 AdvanceClustering 最多破裂数(与项目设置取 Min) | CH/Private/Chaos/PBDRigidClustering.cpp:1657 |
p.Chaos.Clustering.PerAdvanceBreaksRescheduleLimit | INT_MAX | 超预算后顺延到下一帧的簇数(>0 才顺延) | :1661 |
p.Clustering.ParticleReleaseThrottlingMinCount | -1 | 活动 GC 粒子数超过它开始按比例禁用新释放碎块(与设置取 Max) | :73 |
p.Clustering.ParticleReleaseThrottlingMaxCount | -1 | 达到它则全部新释放碎块立即禁用 | :76 |
p.chaos.clustering.breakonlystrained | 1 | 只处理受应变的簇 | :1653 |
p.Chaos.Clustering.DumpClusterAndReleaseStats | 0 | 每帧 log 处理簇数与释放碎块数 | :1665 |
p.ClusterDistanceThreshold | 100 | 簇子体距接触点多近会被打掉(cm) | :40 |
p.UseConnectivity | 1 | 拆簇用连接图 | :43 |
p.ComputeClusterCollisionStrains | 1 | 是否用碰撞累积应变(0=碰撞不再触发破碎) | :52 |
p.chaos.MinImpulseForStrainEval | 980·2/30≈65.3 | 累积冲量低于此不计应变 | :2217 |
p.chaos.UseContactSpeedForStrainEval | true | 用接触速度而非冲量丢弃静置接触 | :2220 |
p.chaos.MinContactSpeedForStrainEval | 1.0 | 接触速度低于此不计应变 | :2223 |
p.RestoreBreakingMomentumPercent | 0.5 | 簇破碎时向接触物体补偿的动量比例 | :70 |
p.GraphPropagationBasedCollisionImpulseProcessing / …Factor | 0 / 1 | 碰撞应变沿连接图向邻块传播 | :64,67 |
p.DeactivateClusterChildren | 0 | 子体并入其它簇时是否停用 | :55 |
p.Chaos.CheckForInterclusterEdgesOnRelease | true | 释放时检查簇间边以便重新并入 Cluster Union | :46 |
p.Chaos.ClusterUnion.GenerateInterclusterEdges | true | 加入/移出 Cluster Union 时自动生成/移除簇间边 | CH/Private/Chaos/ClusterUnionManager.cpp:21 |
p.gc.GlobalMaxSimulatedLevel | 100 | 全局最大模拟层级(与资产取 Min) | CH/Private/PhysicsProxy/GeometryCollectionPhysicsProxy.cpp:1878 |
p.GeometryCollection.AreaBasedDamageThresholdMode | 0 | 面积阈值:0 求和/1 最大/2 最小/3 平均 | …PhysicsProxy.cpp:100 |
5.2 GC 组件 / 物理代理
| CVar | 默认 | 含义 | 位置 |
|---|---|---|---|
p.GeometryCollection.PositionUpdateTolerance / RotationUpdateTolerance / LinearVelocityUpdateTolerance / AngularVelocityUpdateTolerance | 1e-4 | PT→GT 同步判变容差(越大→被判"没变"越多,GT/渲染越省,可能抖/停顿) | …PhysicsProxy.cpp:118-136 |
p.GeometryCollection.TransformTolerance | 0.001 | 判变换是否改变 | :112 |
p.GeometryCollection.EnabledNestedChildTransformUpdates | true | 更新被驱动的禁用子体(用于 trace) | :82 |
p.GeometryCollection.UseRootBrokenFlag | true | 根破碎后禁用 GT 粒子(failsafe) | :142 |
p.GeometryCollection.PropagateInternalClusterDisableFlagToChildren | true | 禁用的内部簇把 disabled 传给子体 | :148 |
p.GeometryCollection.AlwaysGenerateGTCollisionForClusters | true | 始终为簇建 GT 碰撞 | :88 |
p.GeometryCollection.EmptyUniqueIndicesOnGT | false | 粒子初始化后立即释放 unique indices | :160 |
p.GeometryCollection.UpdateGTParticlesOnWorldTransformUpdate | false | SetWorldTransform_External 立即更新 GT 粒子 | :166 |
p.GeometryCollection.ForceOverrideGCCollisionSetupForTraces | -1(非 Shipping) | 强制 trace 用 SM(0)/GC(1) 碰撞 | :181 |
p.gc.BuildGeometryForChildrenOnPT / OnGT, p.gc.CreateGTParticlesForChildren | true(均 DEPRECATED) | 初始化即建所有子体 PT/GT 几何与 GT 粒子;改用 bOptimizeForRuntimeMemory | :633,641,649 |
p.gc.RemoveImplicitsInDynamicCollections | false(DEPRECATED) | 不预建子体几何时清除 dynamic collection implicits | :657 |
p.CollisionParticlesPerObjectFractionDefault | 1.0 | 每对象 collision particles 占比 | :63 |
p.Chaos.GC.EnableCollisionParticles | true | 碰撞是否使用 collision particles | CH/Private/GeometryCollection/GeometryCollection.cpp:25 |
p.CollisionParticlesMin / Max / SpatialDivision / UseImplicitCulling | 10 / 2000 / 10 / 0 | collision particles 采样简化 | …CollisionStructureManager.cpp:25-34 |
p.GeometryCollectionDisableGravity / p.GeometryCollectionCollideAll | false / false | 调试:关重力/绕过碰撞矩阵 | :69,75 |
p.GeometryCollectionNavigationSizeThreshold | 20 cm | 叶子包围盒对角线小于它不导出到导航 | GCE/Private/GeometryCollection/GeometryCollectionComponent.cpp:490 |
p.Chaos.GC.CreatePhysicsStateInEditor | false | 编辑器(非 PIE)建 GC 物理状态 | …Component.cpp:136 |
p.Chaos.GC.DestroyProxyOnSetSimulatePhysicsFalse | false | SetSimulatePhysics(false) 时销毁 proxy | :148 |
p.Chaos.GC.EmitRootBreakingEvent | false | 根破碎也发 Breaking 事件("临时 cvar") | :133 |
p.Chaos.GC.MaxGeometryCollectionAsyncPhysicsTickIdleTimeMs | 30 | 死 CVar(无读取点) | :126 |
p.ReportTooManyChildrenNum | -1 | 单簇子体过多时告警 | …PhysicsProxy.cpp:1124 |
p.gc.ReportHighParticleFraction / ReportNoLevelsetCluster | -1 / 0 | 报告高粒子比例对象 / 无 levelset 的簇 | :630,1875 |
p.GeometryCollectionAssetForceStripOnCook | false | 刚体仅缓存回放时跳过模拟属性构造 | GCE/…/GeometryCollectionObject.cpp:59 |
p.GeometryCollectionAlwaysRecreateSimulationData | false | 即使未脏也重建模拟数据(编辑器有成本) | :71 |
5.3 渲染 / Nanite / ISM Pool
| CVar | 默认 | 含义 | 位置 |
|---|---|---|---|
r.GeometryCollection.Nanite | 1 | GC 用 Nanite 渲染(改动重建 RenderState) | GCE/…/GeometryCollectionComponent.cpp:476 |
p.Chaos.GC.UseCustomRenderer | true | 是否使用指定的 CustomRenderer | :117 |
p.Chaos.GC.CustomRendererHiddenActorFix | true | CustomRenderer 考虑 Actor 隐藏 | :145 |
r.GeometryCollection.CustomRenderer.ForceBreak | -1(非 Shipping) | 强制 N 个变换以"已破碎"方式单独渲染(压测碎块渲染用) | :548 |
p.Chaos.GC.InitConstantDataUseParallelFor / …ParallelForBatchSize | true / 5000 | 初始化常量数据并行拷贝及批大小 | :120,123 |
r.GeometryCollectionTripleBufferUploads | 1 | 骨骼缓冲三重缓冲 | GCE/…/GeometryCollectionSceneProxy.cpp:74 |
r.ParallelGeometryCollectionBatchSize | 1024 | CPU 蒙皮每批顶点数 | :66 |
r.GeometryCollection.UseCachedMDCs | true | 走缓存 MDC 路径 | :59 |
r.GeometryCollectionSetDynamicData.ISPC / p.Chaos.BoxCalcBounds.ISPC | 宏默认 1(非 Shipping 且 INTEL_ISPC 才注册) | ISPC 加速 | :135;Component.cpp:113 |
r.RayTracing.Geometry.GeometryCollection / .WPO / .CombinedBLAS / .ForceUpdate | 0 / true / 0 / 0 | GC 光追相关 | SceneProxy.cpp:82,94,112,105 |
r.Nanite.ProxyRenderMode | 0 | 引擎通用:Nanite 不被支持时 0=回退渲染 Nanite 代理网格,1/2=不渲染;GC 的 CreateSceneProxy 读取了它但未使用 | ENG/Private/Rendering/NaniteResources.cpp:215-221 |
r.ISMPool.ComponentRecycle | true | 回收空 ISM 组件到空闲链表 | ISM/Private/ISMPool/ISMPoolComponent.cpp:45 |
r.ISMPool.ComponentKeepAlive | false | 空 ISM 也保留组件(预热前提) | :32 |
r.ISMPool.ComponentFreeListTargetSize | 50 | 空闲链表目标大小 | :60 |
r.ISMPool.ClearComponentMeshOnRecycle | true | 空闲组件清空 Mesh 引用 | :52 |
r.ISMPool.ShadowCopyCustomData | false | 保留 CustomData 副本 | :68 |
r.ISMPool.DebugStats / r.ISMPool.DebugDraw | 0 / 0 | 屏幕显示 ISM 池统计 / 调试绘制 | ISM/Public/ISMPool/ISMPoolDebugDrawComponent.cpp:25,32 |
(废弃)r.GC.ISMPool.*、p.Chaos.GC.ISMPoolDebug* | 同上 | 仅影响 5.6 起废弃的 UGeometryCollectionISMPoolComponent | GCE/Private/GeometryCollection/GeometryCollectionISMPoolComponent.cpp:17-44 |
5.4 移除 / 衰减
| CVar | 默认 | 含义 | 位置 |
|---|---|---|---|
p.Chaos.GC.RemovalTimerMultiplier | 1.0 | 移除计时倍率(>1 更快移除) | Component.cpp:129 |
p.Chaos.GC.RemoveOnBreakDecayFix | true | 修复崩解簇带模拟子体时整簇消失数帧 | :153 |
5.5 休眠 / Island
| CVar | 默认 | 含义 | 位置 |
|---|---|---|---|
p.Chaos.Solver.Sleep.Enabled | true | 开启 Island 休眠 | CH/Private/Chaos/Island/IslandManager.cpp:53 |
p.Chaos.Solver.Sleep.Defaults.SleepCounterThreshold / LinearSleepThreshold / AngularSleepThreshold | 20 / 0.001 / 0.0087 | 无物理材质时的默认阈值 | :123,127,131 |
p.Chaos.Solver.Sleep.AngularSleepThresholdSize | 0 | 按尺寸缩放角速度阈值(0=关) | :138 |
p.Chaos.Solver.Sleep.PartialIslandSleep | false | [实验] 部分 Island 休眠 | :57 |
…PartialIslandSleep.MinimumMotionlessRatio / MomentumPropagation / PostStepWakeThreshold / Linear/AngularWakeThresholdMultiplier / SleepModeSwitchCounterThreshold | 0.1 / 0.3 / 20 / 10 / 10 | 部分休眠细节 | :103,88,84,111,115,107 |
p.DisableThreshold2 | 5 | 低速连续 N 帧才 Disable(需材质设置 Disabled 阈值) | CH/Private/Chaos/PBDRigidsEvolutionGBF.cpp:58 |
p.Chaos.Solver.ComputeIterationSettings.Enabled | true | Island 变化时重算迭代设置 | IslandManager.cpp:154 |
5.6 碰撞 / 事件
| CVar | 默认 | 含义 | 位置 |
|---|---|---|---|
p.EnableCollisions | 1 | 开关 Chaos 求解器碰撞 | CH/Private/Chaos/PBDCollisionConstraints.cpp:60 |
p.Chaos.Solver.Collision.CullDistance / MaxPushOutVelocity / DepenetrationVelocity | -1 / -1 / -1 | ≥0 时覆盖项目设置 | CH/Private/PBDRigidsSolver.cpp:372,385,388 |
p.Chaos.Solver.Collision.VelocityBoundsMultiplier / MaxVelocityBoundsExpansion | 1.0 / 3.0 | 按速度膨胀 bounds 及上限 | :379-380 |
p.Chaos.Solver.Collision.UseManifolds / AllowManifoldUpdate | 1 / 1 | manifold 及跨帧复用 | :398,402 |
p.Chaos.Solver.Collision.SolverType | -1 | -1 默认(Gauss-Seidel)/0 GS/1 GS SOA/2 Partial Jacobi | PBDRigidsEvolutionGBF.cpp:75 |
p.Chaos.Collision.MaxShapePairs / EnableShapePairs | 100 / true | 少形状粒子对用预展平 shape-pair | Collision/ParticlePairMidPhase.cpp:63-64 |
p.Chaos.Collision.MaxManifoldPoints | -1 | 每 contact 的 manifold 点数上限 | Collision/PBDCollisionConstraint.cpp:72 |
p.Chaos.Collision.EnableOneWayInteraction / OneWayStiffness | true / 0.5 | one-way 交互 | :59,65 |
p.Chaos.Solver.OneWayPairCollisionMode / OneWayPairSphereRadiusAlpha | 2(Sphere) / 1.0 | one-way 对 one-way:0 忽略/1 正常/2 球体 | PBDRigidsEvolutionGBF.cpp:179;ParticlePairMidPhase.cpp:76 |
p.Chaos.Collision.SimpleAssignContact | true | 不检查已有 contact 直接赋值(更快更易休眠) | PBDCollisionConstraint.cpp:78 |
p.Chaos.Solver.UseCCD / UseMACD | true / true | 全局 CCD / 运动感知 CD | PBDRigidsSolver.cpp:406,410 |
p.Chaos.Collision.UnionBVH.Enabled / NumShapes / MaxDepth | true / 10 / 14 | 层级形状 ≥10 个时包 BVH | CH/Private/Chaos/ImplicitObjectUnion.cpp:20-28 |
p.Chaos.Convex.SimplifyUnion / KinematicMode / DynamicMode | false / 2 / 2 | 层级凸体简化(0 单凸/1 每子体/2 合并相连) | CH/Private/Chaos/ConvexOptimizer.cpp:16-24 |
p.Chaos.EventManager.DispatchEmptyEvents | true | 事件缓冲为空也派发 | CH/Private/EventManager.cpp:11 |
5.7 Solver / 迭代 / 异步 / 并行 / 加速结构
| CVar | 默认 | 含义 | 位置 |
|---|---|---|---|
p.Chaos.Solver.Iterations.Position / Velocity / Projection | -1 / -1 / -1 | ≥0 覆盖项目设置(默认 8/2/1) | PBDRigidsSolver.cpp:337-343 |
p.Chaos.Solver.Collision.Position/VelocityFrictionIterations, …ShockPropagationIterations | -1 | ≥0 覆盖摩擦/冲击传播迭代 | :331-334 |
p.HackMaxAngularVelocity / p.HackMaxVelocity2 | 1000 rad/s / -1 | 速度上限 | PBDRigidsEvolutionGBF.cpp:51,54 |
p.Chaos.Solver.PersistentGraph | true | 约束图跨 tick 保留 | :174 |
p.ChaosRigidsEvolutionApplyAllowEarlyOut / …PushoutAllowEarlyOut | 1 / 1 | 迭代已解决时提前退出 | CH/Private/Chaos/PBDRigidsEvolution.cpp:15,18 |
p.UseAsyncInterpolation / p.AsyncInterpolationMultiplier / p.AsyncPhysicsBlockMode | 1 / 2.0 / 0 | 异步插值、回看倍数、GT 阻塞模式 | CH/Private/Chaos/Framework/PhysicsSolverBase.cpp:263,285,292 |
p.MaxPhysicsStepsPerGameTick | 3 | 每游戏 tick 最大物理步数 | :579 |
p.Chaos.DedicatedThreadEnabled / p.Chaos.Thread.DesiredHz | 1 / 60 | 专用物理线程及目标频率 | CH/Private/ChaosSolversModule.cpp:22,29 |
p.Chaos.DisablePhysicsParallelFor / DisableParticleParallelFor / DisableCollisionParallelFor / SingleWorkerPhysics | false | 关闭并行(调试/对比) | CH/Private/Chaos/Framework/Parallel.cpp:28-37 |
p.Chaos.CollisionSmallBatchSize / GlobalSmallBatchSize / LargeBatchSize | 10 / 40 / 100 | 并行批大小 | :34-36 |
p.Chaos.NumWorkerCollisionFactor | 2 | 碰撞检测每 worker 任务数 | CH/Private/Chaos/Collision/SpatialAccelerationBroadPhase.cpp:9 |
p.Chaos.Solver.IslandGroups.ParallelMode / WorkerMultiplier / MinConstraintsPerWorker / MinBodiesPerWorker | 2(Tasks) / 1 / 50 / 50 | Island 组并行求解 | CH/Private/Chaos/Island/IslandGroupManager.cpp:34-50 |
p.Chaos.Solver.UseParticlePool / ParticlePoolNumFrameUntilShrink / ShrinkArrays | true / 30 / false | 脏粒子池与内存回收 | PBDRigidsSolver.cpp:307-320 |
p.Chaos.AccelerationStructureSplitStaticDynamic / UseDynamicTree / TimeSlicingMaxQueueSizeBeforeForce / TimeSlicingMaxBytesCopy | 1 / 1 / 1000 / 100000 | 加速结构拆分与时间切片 | PBDRigidsEvolution.cpp:41,44,54,57 |
p.aabbtree.DynamicTreeLeafCapacity / DynamicTreeBoundingBoxPadding / MaxProcessingTimePerSliceSeconds | 8 / 5.0 / 0.001 | 动态 AABB 树参数及时间切片预算 | CH/Private/Chaos/AABBTree.cpp:29,32,41 |
p.Chaos.Simulation.ApplySolverProjectSettings / P.Chaos.Simulation.Enable | 1 / 1 | 应用项目 Solver 设置 / 总开关(注意大写 P) | PCO/Private/ChaosScene.cpp:55-56 |
p.Chaos.AsyncPhysicsStateTask.TimeBudgetMS | 0(无限) | 异步物理状态任务时间预算 | ENG/Private/PhysicsEngine/Experimental/PhysScene_AsyncPhysicsStateJobQueue.cpp:13 |
5.8 Debug 绘制 / 诊断
| CVar | 默认 | 含义 | 位置 |
|---|---|---|---|
p.Chaos.DebugDraw.Enabled | false [Dbg] | 总开关 | CSE/Private/Chaos/ChaosDebugDrawComponent.cpp:29 |
p.Chaos.DebugDraw.MaxLines / Radius / SingleActor | 20000 / 3000 / false | 线数上限 / 相机半径 / 只画准星指向的 Actor | :32,35,41 |
p.Chaos.Solver.DebugDrawShapes / Collisions / Bounds / Transforms / Islands / SleepState / BVHs / Mass | 0 [Dbg] | solver 级绘制 | PBDRigidsSolver.cpp:171-180 |
p.Chaos.Solver.DebugDraw.Cluster.Constraints | 0 [Dbg] | 绘制活动簇约束 | :200 |
p.Chaos.DebugDraw.ShowIslands / ColorShapesByIsland / ColorShapesByInternalCluster / ColorShapesByClusterUnion / ConnectionGraphLevelOffset / ConnectionGraph.ShowAreas / ShowInternalStrains | true / false / false / false / 0 / false / false [Dbg] | 按 Island/内部簇/Cluster Union 着色,画连接图(层级偏移、面积、内部应变) | CH/Private/Chaos/ChaosDebugDraw.cpp:67-134 |
p.Chaos.DebugDraw.GeometryCollectionReplication | false | 画 GC 复制 delta | Component.cpp:2634 |
p.RenderInterp.DebugDraw.GC | false | GC 渲染插值调试 | CH/Private/Framework/PhysicsProxyBase.cpp:30 |
p.Chaos.VisualDebuggerEnable | 1 | 推送数据到 Chaos Visual Debugger | PBDRigidsSolver.cpp:433 |
6. Debug、统计与编辑器指标
6.1 stat 命令
| 命令 | 来源 | 内容(与本专题相关) |
|---|---|---|
stat Chaos | CH/Public/ChaosStats.h:8 | GC/簇/缓冲主统计,见下表 |
stat ChaosWide | :9(Verbose 组) | [BufferPhysicsResults] - Geometry Collection(STAT_CacheResultGeomCollection) |
stat ChaosThread | :10 | 物理线程 Time/FPS(Actual/Effective) |
stat ChaosCollision | :13 | Detect/BroadPhase/MidPhase/NarrowPhase/Apply/PushOut/SimplifyConvexes |
stat ChaosConstraintSolver / ChaosConstraintDetails | :14,20 | 约束求解阶段(IslandGroupManager.cpp:10-22) |
stat ChaosCounters | :17 | NumGeomCollBodies 等(PBDRigidsSolver.cpp:54)、Replicated Clusters/Fractures |
stat ChaosIslands | :21 | Island Merge/Split/Levels/Finalize(IslandManager.cpp:33-39) |
stat GeometryCollection、stat ChaosSolver | GCE/Private/GeometryCollection/GeometryCollectionComponentPluginPrivate.h:12;CSE/Private/Chaos/ChaosSolverEnginePluginPrivate.h:12 | 空组(只声明了 StatGroup,没有任何 stat 挂在上面) |
| ISM Pool | 无 stat | 用 r.ISMPool.DebugStats=1(§6.3) |
STATGROUP_Chaos 内与本专题最相关的项(CH/Public/ChaosStats.h):
| Stat | 含义 | 行 |
|---|---|---|
STAT_GCPostPhysicsSync | OnPostPhysicsSync(PT→GT 之后的全部 GT 处理,GC 的 GT 总成本) | :45 |
STAT_GCCUGlobalMatrices | 组件空间矩阵全量重算 | :44 |
STAT_GCCUpdateBounds | 组件 Bounds 更新 | :43 |
STAT_GCInitDynamicData | 渲染动态数据准备 | :47 |
STAT_GCTotalTransforms / STAT_GCChangedTransforms | 提交的变换总数(仅 Nanite 路径累加);Changed 仅声明 | :48-49 |
STAT_GCFullyDecayedBroadcast | 全部衰减事件广播 | :46 |
STAT_CacheResultGeomCollection / STAT_CalcParticleToWorld | PT 缓冲 GC 结果 / 粒子→世界变换 | :63,71 |
STAT_BreakingCallback / CollisionContactsCallback / TrailingCallback | 事件数据生成 | :36-38 |
STAT_DispatchBreakEvents / CollisionEvents / CrumblingEvents | GT 事件派发 | :56-58 |
STAT_AdvanceClustering / UpdateImpulseStrain / UpdateDirtyImpulses / BreakingModel / ReleaseClusterParticles_* / GenerateConnectionGraph | 破碎判定与释放 | PBDRigidClustering.cpp:210,1668-1670,1913-1914,1073,1577,2129 |
STAT_ParamUpdateField_ExternalClusterStrain / InternalClusterStrain | Field 对簇施加应变 | :120,126 |
其它可观测:CSV 类别 Chaos / PhysicsVerbose / PhysicsCounters(CH/Private/ChaosLog.cpp:21-23)、Insights 计数器 Chaos/Solver/Bodies/NumGC 等(PBDRigidsSolver.cpp:74-121)、TRACE_CPUPROFILER_EVENT_SCOPE 覆盖 FGeometryCollectionRenderData::*(构建期)。
6.2 压测/排障常用 CVar 组合
- 看破裂节奏:
p.Chaos.Clustering.DumpClusterAndReleaseStats=1 - 限制爆发:
p.Chaos.Clustering.PerAdvanceBreaksAllowed=N、…RescheduleLimit=M - 总量阀门:
p.Clustering.ParticleReleaseThrottlingMinCount/MaxCount - 渲染压测:
r.GeometryCollection.CustomRenderer.ForceBreak=<变换数>(强制 N 块按"已破碎"渲染,Shipping 无效) - 看谁在睡/按 Island 着色:
p.Chaos.DebugDraw.Enabled=1 p.Chaos.Solver.DebugDrawIslands=1 p.Chaos.Solver.DebugDrawSleepState=1 p.Chaos.DebugDraw.ColorShapesByIsland=1
6.3 ISM Pool 调试
r.ISMPool.DebugStats=1 屏幕输出(ISMPoolDebugDrawComponent.cpp:273-356):池所属 Actor 路径、Num ISMS、Num HISMS、Num Instances、每个 ISM 平均实例数、实例数的 10 档百分位;选中某 ISM 时再给 Type / Count / Bounds / CullDistance / NumLods / NumMaterials / CustomDataSize 以及各 LOD 的顶点/三角形数。r.ISMPool.DebugDraw=1 画调试图形。AISMPoolActor 在 UE_ENABLE_DEBUG_DRAWING 下自带 DebugDraw 组件(ISMPoolActor.cpp:15-22)。
6.4 FractureEditor 的统计与直方图(编辑器内"碎块预算"可视化)
SGeometryCollectionStatistics(PLG/ChaosEditor/Source/FractureEditor/Private/SGeometryCollectionStatistics.cpp:81-140,数据在 FractureEditorModeToolkit.cpp:2432-2533)
| 列/行 | 含义 |
|---|---|
| 每个 Level 一行:Num Bones | 该层级 SimulationType != FST_None 的变换数 |
| Num Convex | 该层级凸包数;"若父节点自己没有凸包,则等于其子孙凸包之和"(深度优先累加,:2483-2525) |
| 末行 Embedded | SimulationType==FST_None(即 Embed 嵌入物)的个数 |
SGeometryCollectionHistogram(SGeometryCollectionHistogram.{h,cpp}):不是分箱直方图,而是逐骨骼的条形图(可排序),按属性归一化到 [min-2.5%·range, max](.cpp:332-354):
可检视属性(EInspectedAttributeEnum,.h:27-33) | 读取的属性 |
|---|---|
Volume(默认) | Volume(transform 组) |
Level | Level |
InitialDynamicState | InitialDynamicState(No Override/Sleeping/Kinematic/Static) |
Size(显示名 RelativeSize) | Size |
过滤开关:bShowClusters / bShowRigids / bShowEmbedded、LevelView(.h:36-64,.cpp:127-175)。→ 用它找"体积过小的碎渣""层级过深"的骨骼,指导 Size-Specific 分档与 MaxSimulatedLevel。没有"碎块总数/体积直方图分箱/内存估算"之类的指标。
6.5 GC 自带 DebugDraw
AGeometryCollectionDebugDrawActor已 DEPRECATED(5.0):"Use normal debug draw Chaos Physics commands"(GCE/Public/GeometryCollection/GeometryCollectionDebugDrawActor.h:84),改用 §5.8 的p.Chaos.DebugDraw.*;GeometryCollectionDebugDraw::DrawSolid/DrawWireframe用于场景 proxy 的碰撞/线框展示(GeometryCollectionDebugDraw.h:12-15)。- Chaos Visual Debugger(
p.Chaos.VisualDebuggerEnable默认 1)可录制 solver 数据离线回放。
7. 离线烘焙 / Sequencer / 其它插件
| 项 | 5.7 状态与机制 | 位置 |
|---|---|---|
| GeometryCollectionSequencer / GeometryCollectionTracks(旧 GC Cache Track) | Section 参数:GeometryCollectionCache(UGeometryCollectionCache,标记 Experimental)、StartFrameOffset / EndFrameOffset / PlayRate;执行 Token 每帧把组件 ObjectType=Kinematic、CachePlayback=true、CacheMode=None、TargetCache=…、DesiredCacheTime=…,并在离开 Section 时还原。但 DesiredCacheTime 在 Source/Runtime/Experimental 内仅有声明(GeometryCollectionComponent.h:1293),CachePlayback 只在 UpdateBounds 被读(Component.cpp:1043) ⇒ 【推断】旧 Track 在 5.7 已基本空转,推荐使用 ChaosCaching | PLG/GeometryCollectionPlugin/Source/GeometryCollectionTracks/Private/MovieSceneGeometryCollectionTemplate.cpp:18-110;GeometryCollectionCache.h:14 |
| ChaosCaching(现役) | UChaosCache / UChaosCacheCollection / AChaosCacheManager / AChaosCachePlayer;ECacheMode{None, Play, Record};GC 适配器 FGeometryCollectionCacheAdapter:PT 里 Record_PostSolve 逐帧记录每个未 Disabled(或其内部簇父仍活动)粒子的Actor 空间变换,释放的簇记 identity+bPendingDeactivate,破裂的簇通过 FEnableStateEvent 事件记录;同时记录 Breaking/Collision/Trailing 事件。回放用 Kinematic Target(p.Chaos.Cache.GeometryCollectionPlayUseKinematicTarget=true)驱动刚体。自带 Sequencer Track/Section/SpawnableBinding(Public/Chaos/Sequencer/*) | PLG/ChaosCaching/Source/ChaosCaching/Private/Chaos/Adapters/GeometryCollectionComponentCacheAdapter.cpp:27-30,57-200,504-545;Public/Chaos/CacheManagerActor.h:24-28 |
| ChaosNiagara | "Import destruction data from Chaos into Niagara to generate secondary destruction effects."——消费 Break/Collision/Trailing 数据做粉尘/火花等次级特效;因此需要对应 bNotify* / Solver 数据生成开关,并用过滤器压量 | PLG/ChaosNiagara/ChaosNiagara.uplugin:6(未深读实现) |
| ChaosRigidAsset | "Rigid Asset plugin for creating and utilising collections of rigid bodies":模块 ChaosRigidAssetEngine/Editor/Nodes,内容是 Dataflow 化的物理资产(PhysicsAssetDataflowContent/Attachment、PreviewSceneControllerComponent),与破碎运行时/渲染预算无直接关系 | PLG/ChaosRigidAsset/ChaosRigidAsset.uplugin:6;Source/ChaosRigidAssetEngine/Private/* |
| ISMPool 的复用 | 子系统注释:"Used by geometry collection now but repurposed for more general use."(GeometryCollectionISMPoolSubSystem.h:12);本树内 ISMPool 的使用者只有 GC 渲染器 | ISM/* |
| Mass / 实例化 Actor | Plugins/Runtime/MassGameplay/Source 内对 GeometryCollection / ChaosSolver / UISMPool grep 无命中 ⇒ 源码层面 Mass 与破碎没有直接耦合;Mass 的渲染实例化(MassRepresentation)与 GC 的 ISM Pool 是各自独立的两套实例化方案 | grep 结果 |
8. 大规模破碎:预算与最佳实践(由默认值/限制逻辑归纳)
数值预算没有源码给出的"推荐值",以下只给机制 + 可调旋钮 + 观察指标;具体数字必须用
stat Chaos/Insights 在目标硬件上标定。
8.1 碎块数量预算("谁产生碎块、谁卡 PT/GT/RT")
| 预算层 | 机制与旋钮 | 观察 |
|---|---|---|
| 创建(内存) | MaxSimulatedLevel 与 p.gc.GlobalMaxSimulatedLevel 之外的层级不建粒子;bOptimizeForRuntimeMemory;bStripOnCook / bStripRenderDataOnCook;子体几何延迟创建 | stat ChaosCounters NumGeomCollBodies |
| 爆发(单帧尖峰) | PerAdvanceBreaksAllowed + PerAdvanceBreaksRescheduleLimit;ISM 预热;首次破碎的零缩放批量分配 | STAT_AdvanceClustering |
| 总量(并发动态碎块) | ParticleReleaseThrottlingMin/MaxCount(硬阀门)+ RemoveOnBreak/Sleep(软回收)+ One-Way 层级 + CollisionGroup/Profile 减接触 | NumGeomCollBodies、NumIslands、NumContacts |
| GT | 判变容差、拆分 GC、减少 Embedded/Exemplar、衰减计时(O(N) Tick) | STAT_GCPostPhysicsSync、STAT_GCCUGlobalMatrices |
| RT/GPU | 走 ISM 合批 or Nanite、减少不同"网格+材质"的 ISM 个数、关光追 | r.ISMPool.DebugStats、stat gpu |
8.2 休眠与清理
- 休眠只是"不再模拟",不是"不再同步/渲染":休眠碎块仍然是未 Disabled 粒子,会在含脏粒子的 GC 里被缓冲,且仍占渲染实例。真正释放资源要靠 Disable(RemoveOnSleep/Break →
DisableParticles_External)。 - 在 UI 里:
Remove on Sleep默认关;打开后默认休眠 5–10 s 后开始、2.5–5 s 内缩小消失;Slow-Moving as sleeping默认开(1 cm/s)——解决"抖而不睡"的碎块永远不被清理。 - 对簇:RemoveOnBreak 的
ClusterCrumbling=先崩成小块再各自被清理(把"大块直接消失"变成"碎开后消失"),bAutomaticCrumblePartialClusters对 RemoveOnSleep 同理。注意崩解会增加瞬时动态碎块数,需配合PerAdvanceBreaks*/ Throttling。 - 大规模场景建议打开 Partial Island Sleep(默认关,实验性):源码注释明确针对"大量静止刚体堆"。
8.3 碎片尺寸分级(Size-Specific + 层级)
- 用
SizeSpecificData按 RelativeSize 分档:大块 Convex(可保留 Margin)、中块 Box/Capsule、渣块 Sphere/Capsule 或更小的CollisionParticles;DamageThreshold也可按档(bUseSizeSpecificDamageThreshold)。 - 层级策略:
MaxClusterLevel控制"最多打到哪一层"(超过则不可再破,阈值FLT_MAX);OneWayInteractionLevel让最细层变 debris(不推别人,one-way 之间用球体碰撞);CollisionProfilePerLevel让细层不与某些通道碰撞。 - 工具:Fracture 统计面板 + 直方图(按 Size/Volume/Level)确认分布,确保没有"数量巨大的极小碎块"。
8.4 碰撞简化
- 最小化 Collision Particles(
Fraction、Max=60、CollisionSampleFraction),非必要不用 LevelSet; CollisionGroup把同一 GC 内部/同组 GC 互相屏蔽;- Solver:位置迭代 8/速度迭代 2 是全局默认,GC 粒子默认速度迭代 1;大场景可在
FChaosSolverConfiguration或p.Chaos.Solver.Iterations.*下调(权衡堆叠稳定性); - 事件:只在需要特效/音效的 GC 上开
bNotify*,并开 Solver 过滤器的MinImpulse/MinSpeed/MinMass/MinVolume。
8.5 渲染实例化
- 同一"碎片网格+材质"跨 GC 共享 ISM(per-Level 池);
bStripRenderDataOnCook省内存;RootProxyData使未破碎态极便宜; - 需要距离剔除/LOD 偏置/关阴影 → 自定义 Renderer;
- 避免每个 GC 使用独特的材质覆盖(每种覆盖=新 ISM)。
9. 坑点速查(可直接做"Pitfalls"页)
- 题设里的
RenderingMode / bUseISMPool在 5.7 不存在,改为CustomRendererType;UGeometryCollectionISMPool*自 5.6 废弃。 - ISM 路径下
SetCustomInstanceData*/SetCustomDepthStencil对内置 Renderer 无效(无类实现IGeometryCollectionCustomDataInterface)。 - ISMPool Renderer 的
UpdateInstanceTransforms是 O(M×N);AutoInstanceTransformRemapIndices已缓存却未被使用。 - 衰减到 0 的 ISM 实例是零缩放保留,不会减少实例数;Throttling 禁用的粒子则是直接消失。
PreallocateMeshInstance需要r.ISMPool.ComponentKeepAlive=1才有效,且 GC 渲染器不会自动调用。PerAdvanceBreaks*CVar 是与设置取 Min,Throttling CVar 取 Max——CVar 只能"收紧",不能"放宽"项目设置。p.Chaos.GC.MaxGeometryCollectionAsyncPhysicsTickIdleTimeMs是死 CVar;stat GeometryCollection/stat ChaosSolver是空组。- RemoveOnSleep 的随机抽签用全局
FMath::RandRange(非确定);RemoveOnBreak 用FRandomStream(NumTransforms)(客户端/服务端一致)。 RemovalTimerMultiplier的帮助文本两处 ">1" 是笔误:实际 >1 = 更快。- 休眠碎块仍在 PT 缓冲/GT 同步里;整体休眠的 GC 才免费。
- 衰减中的 GC 持续"脏"(每帧写回缩放变换),渲染更新会持续到最后一片消失;
bScaleOnRemoval每个衰减中碎块每帧做一次CalculateInnerSphere。 p.GeometryCollectionHardsnapThresholdMs与…HardMissingUpdatesSnapThreshold绑到同一变量;p.gc.UseLargestClusterToComputeRelativeSize绑错变量(GeometryCollectionConvexUtility.cpp:23-26)。bOptimizeForRuntimeMemory把成本移到"首次破碎",会制造破碎尖峰。- Embed 工具:重置 GC 后嵌入物丢失;Dataflow 版 Embed 在 5.7 是空实现。
- 编辑器里 ISM Pool 是 per-GC 本地池,不代表运行时的合批效果。
10. 演示页素材(一页一个要点)
- "别一块一个 Proxy":Native 每个脏 GC 每帧 N×64 B 全量上传;Nanite 重建 N 实例;ISM Pool 把全场相同"网格+材质"合成一个 ISM。
- 三层渲染架构:
Component → IGeometryCollectionExternalRenderInterface → {ISMPoolRenderer | RootProxyRenderer | Native};未破碎用 RootProxy/Merged Mesh,破碎后切 AutoInstanceMeshes / Native。 - ISM 池的数据结构:分组键(Mesh+Materials+CPD+Desc)→ FISMPoolISM → InstanceGroups(固定范围+FreeList)→ InstanceIds;零缩放预分配;Recycle/KeepAlive/Preallocate。
- PT→GT 回传:Proxy 粒度(DirtyParticles)→ 粒子粒度(未 Disabled)→ 容差判变 → 整 GC 全量重算/提交。
- 生命周期:Broken → Sleep/Break 计时(随机区间)→ Decay 缩放 → Disable(粒子移出)→ FullyDecayed 事件;ClusterCrumbling 先碎后灭。
- 预算旋钮全家福:MaxSimulatedLevel、Throttling、PerAdvanceBreaks、OneWay、CollisionGroup/Profile、SizeSpecific、事件过滤、Partial Island Sleep。
- 默认值速记:Iter 8/2/1;GC 粒子 8/1/1;Sleep 5–10s、Removal 2.5–5s;CollisionParticles 1.0/60;MinMass 0.1;DamageThreshold {500000,50000,5000};ISM FreeList 50。
- 观察工具链:
stat Chaos(STAT_GCPostPhysicsSync…)、r.ISMPool.DebugStats、ForceBreak、Fracture 统计/直方图、CVD。
