外观
破碎几何生成
约 11404 字大约 38 分钟
2026-10-14
研究对象:如何把一个网格切成成百上千个碎块,并构建层级(Geometry Collection)。 引擎版本:
Build/Build.version= 5.7.3(CL 298050)。引擎目录只读检索,未运行任何脚本。 标注约定:〔事实〕 = 直接来自源码/注释,带文件:行号;〔推断〕 = 我据代码阅读得出的判断,未实测,演示页引用时请标注「基于源码阅读」。
路径别名(均相对 E:\Main\Engine)
| 别名 | 实际路径 |
|---|---|
PC/ | Plugins/Experimental/PlanarCutPlugin/Source/PlanarCut/ |
FE/ | Plugins/Experimental/ChaosEditor/Source/FractureEditor/ |
FX/ | Plugins/Experimental/Fracture/Source/FractureEngine/ |
GN/ | Plugins/Experimental/GeometryCollectionPlugin/Source/GeometryCollectionNodes/ |
VO/ | Source/Runtime/Experimental/Voronoi/ |
CH/ | Source/Runtime/Experimental/Chaos/ |
GE/ | Source/Runtime/Experimental/GeometryCollectionEngine/ |
GC/ | Source/Runtime/GeometryCore/ |
0. 一页结论(TL;DR)
- 切割核心是「网格布尔」,不是 Voronoi 本身。 Voronoi(第三方 voro++)只负责把「点」变成一组凸多面体 cell(
FPlanarCells);真正耗时的是每个 cell 与源网格做一次FMeshBoolean(Intersect/Difference)。源码自己假设 Voronoi 只占约 1% 时间(FE/Private/FractureToolCutter.cpp:635)。 - 所有「破碎工具」归一化为同一个数据结构:Voronoi/Radial/Cluster/Custom →
FPlanarCells(Sites, Voronoi);Brick →FPlanarCells(Boxes);Plane/Slice → 一组FPlane(逐平面二分);Mesh → 切割网格(一次 Intersect + 一次 Difference)。落地函数只有 4 个:CutMultipleWithPlanarCells/CutMultipleWithMultiplePlanes/CutWithMesh/SplitIslands(PC/Public/PlanarCut.h:239-352,524)。 - 层级是「切一刀,加一层」:被切的 rigid 骨骼变
FST_Clustered,新碎块是它的FST_Rigid子骨骼(PC/Private/GeometryMeshConversion.cpp:3427-3436);旧几何只隐藏不删除(bRemoveOldGeometry=false,:2922, :3133)→ 多级破碎会保留每一级的隐藏几何(内存代价〔推断〕)。 - 噪声/Grout/内部面材质都在「切割体 cell mesh」阶段烘进去:噪声 = 对每个平面多边形用
FQueueRemesher(仅 split)细分到PointSpacing再做 3 通道 Perlin 位移,有 ≤10 轮自交修复;Grout 对 Voronoi 只是「按 cell 最长轴等比缩小」的近似(非真 offset)。 - 凸包(碰撞)= 叶子 AABB 点云凸包 + 邻接对 GJK 穿透切平面去重叠 + 自底向上生成簇凸包;默认每个 rigid 骨骼 1 个凸包,不做凸分解(凸分解默认关)。叶子-叶子去重叠是串行循环(
CH/.../GeometryCollectionConvexUtility.cpp:694-734),是超大碎块量的头号瓶颈〔推断〕。 - 邻接 (Proximity) 默认
Precise:顶点哈希 + 20 向法线分箱 + 共面反向三角形 2D 重叠;阈值写死 0.01 cm,注释明说不开放是因为会退化到 O(n²)(CH/Public/GeometryCollection/GeometryCollectionProximityUtility.h:97-99)。 - AutoCluster 用自写的 K-Means(
FVoronoiPartitioner),不是GeometryCore/Clustering/KMeans.h;后者在引擎里是 Nanite 构建器在用。AutoCluster 有若干规模化风险点(O(N²) 连通性构建、递归 DFS、「迭代收敛」变量未更新),见 §10。 - 5.7 推荐流程是 Dataflow:模板
DF_GC_Template_StaticMesh=StaticMeshToCollection_v2 → UniformFracture → GeometryCollectionTerminal_v2(从 uasset 字符串提取);Fracture 编辑器与 Dataflow 节点共用FFractureEngineFracturing等同一批函数。Terminal 的ResetFrom只在凸包缺失时才算凸包(GE/Private/GeometryCollection/GeometryCollectionObject.cpp:611-619, 441-448)。 - 规模化的硬限制(来自源码):Brick ≤ 8192 块(自动放大砖尺寸);Grid 点 ≤ 200/轴;噪声新增顶点估算 ≤ 100 万;Voronoi 线框 > 10 万 site 不绘制;site 预览 > 100 万 不绘制;UI 上 Uniform/Custom 上限 5000 site。
- 最佳实践一句话:多级(层级)破碎 + 每级少量 site,而不是一次切几万块;先 FixTinyGeo 清碎屑,再 AutoCluster,最后一次性生成凸包并按需关闭叶子-叶子去重叠。
1. 数据模型与总流程
1.1 Geometry Collection 里和「破碎」相关的数据
〔事实〕AppendToCollection 一次写入一个碎块(PC/Private/GeometryMeshConversion.cpp:3388-3479):
| Group | 属性(本研究关心的) |
|---|---|
| Transform | Parent Children SimulationType(Rigid/Clustered/None=嵌入) Transform BoneName Level(派生) |
| Geometry | TransformIndex VertexStart/Count FaceStart/Count BoundingBox Proximity(TSet,派生) |
| Faces | Indices Visible Internal MaterialID |
| Vertices | Vertex Normal TangentU/V UV[n] Color BoneMap |
| Convex | ConvexHull;TransformToConvexIndices(Transform 组) |
- 内部面判定:切割体三角形的 MaterialID 为负数(
PlaneToMaterial(p) = -(p+1),GeometryMeshConversion.h:113-125),写回时Internal = MaterialID<0,并改写成「内部材质 ID」(:3470-3477)。 - 内部材质默认值:
GetDefaultMaterialIDForGeometry= 该几何里最常见的已有内部面材质;找不到则取全集合最常见;再找不到取 0(PC/Private/PlanarCut.cpp:50-100;注释:49提到「外部材质偶数、内部=外部+1」是 Blast 的构造惯例)。UI 文案写「最后一个有效材质」(FE/Private/FractureToolCutter.h:46),与代码不一致,以代码为准。 - 编辑器指定内部材质:
PostFractureProcess→SetMaterialOnGeometryAfter(...InternalFaces...)(FractureToolCutter.cpp:245-257);Dataflow 里只有UniformFracture节点暴露InternalMaterialID(默认 0,GN/Public/Dataflow/GeometryCollectionFracturingNodes.h:1401),其余节点调用ProcessNewlyFracturedBones(..., INDEX_NONE)不改材质(FX/Private/FractureEngineFracturing.cpp:93-109, 473, 565)。
1.2 一次「切割」的数据流
选中骨骼
└─ RandomReduceSelection(ChanceToFracture) → ConvertToLeafSelection(只切 rigid 叶子)
FX/Private/FractureEngineFracturing.cpp:366-380, 186-200
└─ 生成切割器
Voronoi 系: sites → FVoronoiDiagram(Sites, Bounds, 0.1) → FPlanarCells(Sites, Voronoi)
Brick: GenerateBrickTransforms → FPlanarCells(Boxes, bResolveAdjacencies = Grout≈0)
Plane/Slice: TArray<FPlane>
Mesh: FDynamicMesh3 切割体 + 变换
└─ FDynamicMeshCollection(Source, 选中骨骼, 平移到切割原点后的变换)
GC 几何 → FDynamicMesh3(AugMesh) [PC/Private/GeometryMeshConversion.cpp:2552-2669]
└─ FCellMeshes:每个 cell/plane → 闭合 AugMesh(含噪声/Grout/UV/材质)
└─ 对每个源网格: ParallelFor(cell): FMeshBoolean(Intersect cell_i) 与 Difference("outside" cell)
[GeometryMeshConversion.cpp:3003-3153]
└─ SplitIslands(每个 boolean 结果拆连通分量)→ AppendToCollection(新 rigid 子骨骼)
└─ ReindexMaterials;旧几何置 Visible=false
└─ 编辑器:~FGeometryCollectionEdit → UpdateGeometryDependentProperties → UpdateConvexGeometry
[GE/Private/GeometryCollection/GeometryCollectionComponent.cpp:5271-5282]〔事实〕所有切割在「平移到切割原点」的局部空间进行,注释理由是「更一致的噪声 + 更好的 LWC 精度」(PlanarCut.cpp:853;Voronoi 的 sites/bounds 同样减去 Transform.GetTranslation(),FractureToolCutter.cpp:309-315)。
2. 3D Voronoi(voro++)
① 原理
- 一句话:voro++ 是 cell-based 局部算法——把站点放进均匀网格(块),每个站点的 cell 由「容器初始盒」依次被邻近站点的中垂面裁剪得到,按块的距离顺序扫描,一旦剩余块全部比 cell 最远顶点的 2 倍还远即提前结束(
VO/Private/voro++/src/v_compute.cc:73-220,if(con.r_max_add(mrs)<radp[g]) return;出现在:132,:175,:209)。 - 伪代码(UE 封装视角):
container = voro::container(Bounds.ExpandBy(extra), grid=guess_optimal(N, size), periodic=false, init_mem=10)
for i in sites: skip NaN; (若阈值>0) 若与已有最近站点 dist²<thr 则丢弃; container.put(i, x,y,z) // 越界静默丢弃
ParallelFor(块区间): compute_cell(cell, loop) → extractCellInfo(Vertices, Faces, Neighbors, Normals)- 没有显式 Delaunay:邻接 = cell 面的邻居 ID(
ComputeAllNeighbors,VO/Private/Voronoi/Voronoi.cpp:324-354;bExcludeBounds=true时剔除负 ID 即容器壁)。
② 关键参数/默认值
| 项 | 值 | 位置 |
|---|---|---|
| 第三方库 | voro++ (C. Rycroft),已改造:增加 extractCellInfo/neighborsTArray | VO/Private/voro++/;cell.cc:2052-2152 |
| 网格密度 | optimal_particles = 5.6 点/块;nx=int(size·(N/(5.6·V))^(1/3)+1) | config.hh:104;pre_container.cc:22-27 |
| 容器边界 | Bounds.ExpandBy(max(0,ExtraBoundingSpace));破碎调用方统一传 0.1 | Voronoi.cpp:134;FractureToolCutter.cpp:317,646;FX/Private/FractureEngineFracturing.cpp:137,456 |
自由函数 VoronoiNeighbors 的 slack | 0.01 | Voronoi.cpp:12,166 |
去重阈值 SquaredDistSkipPtThreshold | 默认 UE_KINDA_SMALL_NUMBER(1e-4 → 距离 0.01cm);设 0 则不去重更快 | Voronoi.h:71,81,112;Voronoi.cpp:47-78 |
| 数值容差 | tolerance=1e-11,tolerance2=2e-11 | config.hh:81,87 |
| 并行粒度 | ApproxSitesPerThread 默认 Max(150, N/64);若 ≥1.5N 则单块 | Voronoi.h:151-158;Voronoi.cpp:247-281,500 |
| 内存硬上限 | max_vertices=16777216 等(超限 bail out) | config.hh:45-67 |
| cell 提取 | 顶点 = pos + pts·0.5(voro 内部存 2× 坐标);面表 [n,v0..vn-1]*;法线取第一对不共线边的叉积(>tolerance_sq),退化面法线=0 | cell.cc:2080-2082, 2096-2137 |
③ 取舍/坑点
- 〔事实〕越界站点被静默丢弃(
container::put只在put_locate_block成功时写入,container.cc:105-112;VOROPP_REPORT_OUT_OF_BOUNDS 0,config.hh:108)。所以所有工具把 Voronoi 边界设为「几何包围盒 ∪ 站点包围盒 + (Grout + Σ|Amp·Pers^i|)」(FractureToolCutter.cpp:716-725;FX/.../FractureEngineFracturing.cpp:41-71)。 - 〔事实〕被去重/越界丢弃的站点仍占
NumSites名额(Voronoi.cpp:129),结果数组按原始站点下标对齐,对应 cell 为空——下游靠「空 cell → boolean 结果空」自然跳过。 - 〔事实〕退化输入:
compute_cell失败会ensureMsgf("…sites positioned directly on top of other sites…")(Voronoi.cpp:233,309,345)。径向图案(共圆)是已知失败场景,UE 的workaround 是给径向半径加 1e-4 噪声(FractureToolRadial.cpp:145-148;Dataflow v2 同GN/Private/Dataflow/GeometryCollectionFracturingNodes.cpp:325-328,源码 TODO 写着「修好 voro++ 后删除」)。 - 〔事实〕包围盒极大时 voro++ 网格会要求巨量内存,仅有
ensure(size.GetMax() < HUGE_VALF)(Voronoi.cpp:86-88,注释TODO: Figure out reasonable bounds)。 - 〔事实〕
FVoronoiDiagramField(Voronoi.h:173-276)是「最近站点/距 cell 壁距离」的查询类,距离壁用最近邻站点的中垂面近似(:205-235),非精确最小值;破碎主流程不用它。
④ 规模化
- Voronoi 本体线性、可并行且很便宜;UI 限制是为了可视化而非计算:线框 >100000 site 不画(
FractureToolCutter.cpp:494)、site 点 >1000000 不画(:563,注释「PDI struggles」)。 - 重点是后面的 boolean(§3.3)——站点数 ≠ 计算量上界,源网格三角数 × cell 数才是〔推断〕。
3. PlanarCut 核心(FPlanarCells → FCellMeshes → boolean)
3.1 FPlanarCells:统一的「空间划分」表示
〔事实〕PC/Public/PlanarCut.h:122-220,字段:NumCells、AssumeConvexCells、Planes[]、PlaneCells[](每个平面两侧 cell,负侧在前,外部=-1)、PlaneBoundaries[](顶点环)、PlaneBoundaryVertices[]、InternalSurfaceMaterials(全局材质 ID/可见性/UV 缩放/NoiseSettings)。
| 构造 | 用于 | 要点 | 位置 |
|---|---|---|---|
(FPlane) | Plane/Slice 逐平面 | NumCells=2,无边界环 = 无限平面 (IsInfinitePlane) | PlanarCut.cpp:145-149 |
(Sites, FVoronoiDiagram&) | Uniform/Radial/Cluster/Custom | AssumeConvexCells=true;每个共享面只由编号大的 cell 生成一次(CellIdx < NeighborIdx 跳过);法线为 0 时回退「站点差向量」;FPlane(N, N·v0) | :151-204 |
(Boxes[], bResolveAdjacencies) | Brick | 不相邻→每盒独立 6 面;相邻→八叉树找邻居,沿每轴把盒子按重叠边界细分,面用 VertsToPlane 去重共享,容差 UE_DOUBLE_KINDA_SMALL_NUMBER | :206-406 |
(Region, CubesPerAxis) | 规则立方格 | 规则格点顶点共享 | :408-479 |
(Region, Image, W, H) | 图片 mask 区域 | 并查集分块 + 轮廓链;不支持带洞区域(ensureMsgf),AssumeConvexCells=false | :481-715 |
DiscardCells(KeepFunc, bKeepNeighbors)(:718-799)仅用于预览子集(保留邻居,必要时翻转平面使「外部」恒在 Value 侧)。
3.2 FCellMeshes:把 cell 变成可做布尔的闭合网格
〔事实〕GeometryMeshConversion.cpp:
- 入口
FCellMeshes::Init(:1765-1823):DomainBounds.Expand(OnePercentExtend[+噪声幅度])(PlanarCut.cpp:972、GeometryMeshConversion.cpp:1789);bIncludeOutsideCell && !IsInfinitePlane时多一个「外部 cell」(OutsideCellIndex = NumCells,:1778-1782)。 - 无噪声(
:1910-2017):每个平面多边形同时写入两侧 cell 的 mesh(顶点各复制一份,不焊接,朝向相反);凸 cell 用扇形三角化(:1958-1978),非凸用 ConstrainedDelaunay(:1979-2015);UV = 平面局部 2D 坐标 − MinUV,乘GlobalUVScale(:1944-1950);材质 = 负平面号。 - 有噪声(
:2034-2387):见 §3.5。 - 单无限平面(
:2390-2549):用域包围盒生成一个大平面片,两侧各拉伸成一个封闭「半空间盒」(StitchSparselyCorrespondedVertexLoops)。 - 外部 cell 的 mesh = 其它所有 cell mesh 追加后参与 Difference(
ApplyGeneralGrout末尾,:1868-1884;单平面/切割网格用「同一网格复制两份」,:1660-1663)。
3.3 布尔 + 孤岛 + 回写
CutWithCellMeshes(...) // GeometryMeshConversion.cpp:3003
for Surface in 选中骨骼的 AugMesh:
ParallelFor(cell_i): // :3020
if CellBounds[i] ∩ SurfaceBounds: // :3023 ← 唯一的空间剔除(整网格 vs cell 包围盒)
FMeshBoolean(Surface, Cell_i, Intersect | Difference(外部cell))
bSimplifyAlongNewEdges=true; PreserveUVsOnlyForMesh=0; bWeldSharedEdges=false; bTrackAllNewEdges=true
if 非空结果数 > 1: // :3060 仅被真正切开才改写
每个结果: SplitIslands → AppendToCollection → 父骨骼 SimulationType=Clustered
旧几何 Visible=false (bRemoveOldGeometry=false)SplitIslands(:3202-3256):三角连通 +CloseVertexDistance(默认 1e-3,因为布尔结果未焊接) + 可选VertexToSurfaceBridgeDistance(默认 0,用 AABB 树把近距离的不同分量桥接);拆完后「包含在别的分量内部的分量重新并回去」(TMeshSpatialSort::InLargestParent,:3226-3254,处理空腔壳)。FIslandSplitSettings默认:bSplitIslands=true, CloseVertexDistance=1e-3, VertexToSurfaceBridgeDistance=0(PlanarCut.h:85-96);编辑器 UI 同值(FractureToolCutter.h:86,91,95)。- 〔事实〕
FillHoles修补功能存在但被constexpr bool bFillHoles=false关闭(:2835, :3043),TODO 写着「布尔失败不会被检测,除非焊边」(:2827)。
3.4 CutMultipleWithMultiplePlanes(平面/切片)
- 一句话:逐平面二分,每个平面作用于当前所有碎块(
GeometryMeshConversion.cpp:2772-2869)。
ToCut = 源网格副本
for plane in Planes:
Cells = FCellMeshes(FPlanarCells(plane)) // 2 个半空间 cell
for piece in ToCut (快照长度):
if !PlaneAABBIntersection(plane, piece.Bounds ⊕ NoiseAmplitude): continue // :2796
A = Boolean(piece, cell0, Intersect); B = Boolean(piece, cell1, Intersect) // 两次布尔 :2802-2825
if A或B为空: 保持原样
else piece←A, ToCut.Add(B); 对A、B各做 SplitIslands (:2851-2865)- 有 Grout 走另一条路(
:2717-2764):每个平面生成「厚度=Grout 的 slab」,FMeshSelfUnion合并成一个 grout mesh,然后CutWithCellMeshes用它做 Difference(outside cell),孤岛拆分把碎块分开。 - 〔事实〕碎块数无显式上限;n 个一般位置平面最多产生 O(n³) 块〔推断:数学上界〕。
3.5 噪声(Noise)
- 〔事实〕
FNoiseSettings(PlanarCut.h:39-79):Amplitude=2, Frequency=0.1, Octaves=4, Lacunarity=2, Persistence=0.5, PointSpacing=1(结构体默认)。NoiseVector(P) = Amplitude · ( OctaveNoise((P·f)+Off.X), …Y, …Z ),OctaveNoise = Σ PerlinNoise3D(V·f_k)·a_k(:53-78);三个Offset来自FRandomStream.VRand()·100(:17-37),种子 = 切割RandomSeed(PlanarCut.cpp:973, GeometryMeshConversion.cpp:2713)。 - 编辑器默认:
Amplitude=0(=关),Frequency=0.1, Persistence=0.5, Lacunarity=2, Octaves=4, PointSpacing=10(FractureToolCutter.h:36-41)。Dataflow 节点同(...FracturingNodes.h:546-572;PlaneCutter_v2 的 PointSpacing=20,:783)。 - 流程(有噪声的有界平面,
GeometryMeshConversion.cpp:2034-2387):- 每个平面多边形 → CDT 初始三角化 →
RemeshForNoise(FQueueRemesher,EEdgeRefineFlags::SplitsOnly, 目标边长 = Spacing,:1701-1733); ApplyNoise(:1742-1762):内部顶点只沿平面法线位移(位移向量投影到法线),边界顶点保留完整 3D 位移——相邻两个平面共享同一噪声场和同一边界点,所以裂缝边缘保持闭合;单无限平面则bProjectBoundariesToNormal=true(:2432);- 各 cell 拼装后检测自交:
FindAllSelfIntersections,对相交三角形对在「原位置↔噪声位置」之间 4 步二分找tsafe,把相关顶点(含其它 cell 同位置顶点,由TPointHashGrid3对应)回退,最多 10 轮(MaxIters=10,:2172-2320); - 重新按噪声后位置重算 UV(
:2335-2386)。
- 每个平面多边形 → CDT 初始三角化 →
- 预算保护:
GetSafeNoiseSpacing——估算总面积/MaxVerts=1,000,000得最小边长,Spacing = max(0.001, MinEdgeLen, Target),超限只打 Warning 并放大间距(:2020-2032)。 - 〔事实〕噪声不支持切割网格(
CutWithMeshensureMsgf,PlanarCut.cpp:1161;MeshCut 的bNoiseSettingsEnabled=false,FractureToolMeshCut.h:119)。 - 〔事实〕Voronoi 边界必须按「Grout + Σ|Amp·Pers^i|」外扩,保证位移后切割面仍覆盖几何(
FractureToolCutter.h:169-178)。
3.6 Grout(缝隙)
| 场景 | 实现 | 精度 | 位置 |
|---|---|---|---|
| 单平面 | 两侧 cell 沿法线各平移 Grout/2 | 精确 | GeometryMeshConversion.cpp:2503-2533 |
| 多平面 | grout slab 联合后 Difference | 精确 | :2717-2764 |
| Voronoi/一般 cell | 每个 cell 绕顶点质心按 s=(D−Grout/2)/D 等比缩小,D=cell 最长包围盒边;s≈0 则整个 cell 清空 | 近似:长轴缝≈Grout/2,短轴更小;源码 TODO「应做真 offset,但没有保 UV+保锐边的做法」 | :1826-1866(注释 :1853-1854) |
| Brick | 直接把砖盒各缩 HalfGrout=clamp(Grout/2, 0, 0.98·minHalfDim),切割时 Grout=0 | 精确 | FractureToolBrick.cpp:292-296, 309;FX/.../FractureEngineFracturing.cpp:1060-1064,1101 |
3.7 内部面:材质 / UV / 切线 / 碰撞采样
- UV 缩放:
SetUVScaleFromCollection= Σ三角边在 UV 空间长度 / Σ世界长度(PlanarCut.cpp:102-141),保证内部面纹理密度与外表面一致。 - 内部面 UV 为轴对齐平面投影,之后可用 AutoUV 重做(§5.5)。
- 切线/法线:随
AugmentedDynamicMesh顶点属性携带;RecomputeNormalsAndTangents可只重算内部面(PlanarCut.cpp:2291-2309,默认bOnlyInternalSurfaces=true、SharpEdgeAngleThreshold=60,FractureToolRecomputeNormals.h:41-50)。 - 碰撞采样点(仅给 Particle-Implicit/LevelSet 碰撞用):
AddCollisionSamplesPerComponent(GeometryMeshConversion.cpp:673-786)——每个连通分量一个TPointHashGrid3(格宽0.5·Spacing/√3),对最长边超过 Spacing 的三角形按重心插值撒点,已有点半径 Spacing/2 内跳过;新点是无三角形的孤立顶点(:772-779)。编辑器默认关(bAddSamplesForCollision=false, Spacing=50,FractureToolCutter.h:203,210);Dataflow 同(...FracturingNodes.h:580-588)。
3.8 CutWithMesh(Mesh Cut)
ConvertMeshDescriptionToCuttingDynamicMesh(PlanarCut.cpp:1034-1137):把切割 StaticMesh 的 LOD0 MeshDescription 按 (法线/切线/副切线/UV) 元素拆顶点,所有三角形 MaterialID=-1(内部)。要求「闭合、水密」(FractureToolMeshCut.h:32注释)。FCellMeshes(NumUV, SingleCutter, Materials, Transform)(GeometryMeshConversion.cpp:1634-1665):cell0=切割体 (Intersect),cell1=同一网格副本 (Difference,作为外部 cell)。- 多次摆放(
GenerateMeshTransforms,FX/.../FractureEngineFracturing.cpp:1117-1189/FractureToolMeshCut.cpp:122-181):SingleCut/UniformRandom(位置Min+FRand³·Extent) /Grid(格心(i+0.5)/N+VRand·FRand·Variability);每个摆放再RandRange(MinScale,MaxScale)缩放、随机朝向(±Pitch/Yaw/Roll)。 - 循环语义(
MeshArrayCutter:1191-1309;FMeshFractureOpFractureToolMeshCut.cpp:207-305):每次CutWithMesh后,已被切开的父骨骼从待切列表移除,新碎块加入待切列表——后续切割体会继续切碎前面的碎片;全部结束后把所有新 rigid 碎块拍平到原父节点并删除中间簇(:1276-1301)。 - 默认(
FractureToolMeshCut.h:41-86):NumberToScatter=10, Grid 5×5×5 (ClampMax 5000/轴), Variability=0, Min/MaxScale=0.5/1.5, bRandomOrientation=true, Roll/Pitch/Yaw=±180。Dataflow:Grid 默认 2×2×2、CutDistribution=SingleCut(...FracturingNodes.h:1246,1258-1266)。
3.9 小结:PlanarCut 的坑点
- 无源网格空间剔除:只有「整个源网格包围盒 vs cell 包围盒」(
:3023),每个相交 cell 都对整张源网格做布尔;头文件 TODO 承认「每次 boolean 都重建空间结构」(GeometryMeshConversion.h:69,159-160)。成本 ≈ cell 数 × 源网格复杂度〔推断〕。 - 布尔不焊接(
bWeldSharedEdges=false)→ 失败不可检测、碎块可能有裂缝;靠CloseVertexDistance判连通。 - 非流形输入:
InitTemplate遇到NonManifoldID的三角形,复制三个顶点单独成面(:2620-2641)以不丢面;孤立顶点默认删除,除非bSaveIsolatedVertices(:2658-2662)——所以「碰撞采样点」在再次进入 PlanarCut 时会被丢掉,除非上游保留(MergeBones/Normals 路径传true,PlanarCut.cpp:1739-1740, 2296)。 - Grout 是近似(§3.6)。
- 多级破碎累积隐藏几何(§0-3)。
4. 各破碎工具:站点/平面如何生成(Fracture Mode 与 Dataflow)
4.0 公共设置(所有 UFractureToolCutterBase 派生工具)
| 参数 | 默认 | 说明 | 位置 |
|---|---|---|---|
| RandomSeed | -1 | -1 → 每次交互用新随机种子(DefaultRandomSeed=FMath::Rand(),在 FractureContextChanged 时刷新,保证预览与执行一致) | FractureToolCutter.h:29-42,69-71,236-238,295;.cpp:170,239-242 |
| ChanceToFracture | 1.0 | 对选中骨骼逐个 RandomReduceSelection:rand ≥ p → 移除 | FractureToolContext.cpp:113-124 |
| bGroupFracture | true | true:整个选择共用一个图案(取选中骨骼 AABB 并集);false:每个骨骼独立图案,种子=Seed+BoneIndex | FractureToolCutter.cpp:193-220 |
| Grout | 0 | §3.6 | .h:99 |
| Split Islands / CloseVertexDistance / Bridge | true / 0.001 / 0 | §3.3 | .h:86-95 |
| Noise | Amp 0, Freq 0.1, Pers 0.5, Lac 2, Oct 4, PointSpacing 10 | §3.5 | .h:36-41 |
| 碰撞采样 | false / 50 | §3.7 | .h:203,210 |
| 内部材质 | 自动 | §1.1 | .cpp:245-257 |
Dataflow 差异:种子是普通
int32(默认 0);Uniform 节点注释写「-1 → 每次不同」(...FracturingNodes.h:1403),但代码直接FRandomStream(seed)(FX/.../FractureEngineFracturing.cpp:28),−1 只是另一个固定种子〔推断〕——Dataflow 求值需要确定性。
4.1 Uniform Voronoi(UFractureToolUniform / UniformFracture / UniformScatterPoints_v2)
- 原理:i.i.d. 均匀随机点(Poisson-Voronoi)。
rs = FRandomStream(Seed); N = rs.RandRange(Min, Max)
for i<N: site = Bounds.Min + (FRand,FRand,FRand) * Extent- 参数:
Min/Max Voronoi Sites = 20/20,UIMax 5000,ClampMin 1(FE/Private/FractureToolUniform.h:19-20,24,28);DataflowUniformScatterPoints_v220/20、seed 0(GN/Public/Dataflow/GeometryCollectionFracturingNodes.h:64-72)。执行:FE/Private/FractureToolUniform.cpp:51-65/FX/.../FractureEngineFracturing.cpp:22-39,111-164,1326-1445。 - 坑:Tooltip 称「体积近似相同」(
.cpp:28),但点是独立同分布,体积方差大〔推断〕→ 需要 FixTinyGeo;Bounds=选中骨骼世界 AABB,不是几何体积内采样(采样点可能落在凹形外面→生成空 cell,无害但浪费)。 - 规模化:N≤数千;需要上万块用多级。
4.2 Radial Voronoi
- 原理:以 Center/Normal 定义的平面内,RadialSteps 个同心环 × AngularSteps 个角度,环半径按
(i+1)^Exponent分布(首环向中心让半步,因为中心没有站点)。
pos_i = (i+1)^e (+StepOffset); pos_0 *= 0.5; StepOffset = -pos_0
pos_i *= Radius / pos_last
pos_i += FRandRange(-min(pos_i, RadialNoise), RadialNoise); sort; 强制相邻间距 ≥ RadialMinStep (首环 ×0.5)
site(i,k) = Center + (Len_i + FRand·max(1e-4(若Radius>1), RadialVariability)) · (cosθ·BX + sinθ·BY) + Up·FRandRange(-1,1)·AxialVariability
θ = AngleOffset + k·2π/AngularSteps + AngleStepOffsets[k] + FRand·AngularVariability- 默认:
AngularSteps=5, RadialSteps=5(=25 site),Radius=50, RadialStepExponent=1, RadialMinStep=1, AngleOffset/AngularNoise/RadialNoise/各 Variability=0;Center=(0,0,0)+Bounds.Center,Normal=(0,0,1)(FE/Private/FractureToolRadial.h:36-77;.cpp:66-165)。Gizmo 开启时用 gizmo 变换覆盖中心与坐标系(.cpp:79-86)。 - 坑:①
AngleOffset注释写「每个径向步的角度偏移」,代码里每一环都从同一AngleOffset起算(.cpp:151-156),实为全局旋转;② 共圆配置触发 voro++ 数值问题,故强制 1e-4 抖动(§2);③ 环半径超出几何时多出的 cell 为空。
4.3 Cluster Voronoi(ClusterScatterPoints)
- 原理:先随机若干簇心,再在每个簇心周围的球壳(随机方向、半径 = Offset + Rand(MinFrac,MaxFrac)·最大半轴)撒点——簇心本身不是站点(只有子点)。
C = RandRange(ClustersMin, ClustersMax) 个均匀点
for each center: n = RandRange(SitesMin, SitesMax)
site = center + normalize(VRand) * (Offset + FRandRange(fracMin, fracMax) * Bounds.GetExtent().GetAbsMax())- 默认:簇数
8/8,每簇点数2/30,半径分数0.1/0.2,偏移 0(FE/Private/FractureToolClusterCutter.h:20-26;.cpp:50-79;Dataflow…FracturingNodes.h:107-141)。期望 ≈ 8×16=128 site。 - 效果:簇内密、簇间稀 → 一个大碎块群 + 局部碎屑(Tooltip「additional points around a base pattern」与代码不符:没有 base pattern)。
4.4 Custom Voronoi
- 原理:用户用 Gizmo 摆「Live 站点 → Freeze」叠加多层图案;站点存于 gizmo 局部(去缩放)空间,执行时乘 gizmo 变换(可非等比缩放)。
- Pattern:
Centered(Variability>0 才复制 NumSites 个)/Uniform/Grid/MeshVertices(参考 StaticMesh LOD0 顶点,可NormalOffset)/SelectedBones(选中骨骼顶点,去重并平均法线)(FractureToolCustomVoronoi.h:12-25;.cpp:119-351)。 - 后处理:
Variability:Site += VRand·FRand·Var;SkipFraction+SkipMode:Random(随机删) /UniformSpacing(FPriorityOrderPoints::ComputeUniformSpaced,用「顶点法线偏差」作重要度,偏好锐边) /KeepSharp(ComputeDescendingImportance)(.cpp:353-401)。 - 默认:
Pattern=Uniform, SitesToAdd=20 (ClampMax 5000), Grid 5×5×5 (ClampMax 5000/轴), Variability=0, SkipFraction=0(.h:51-86)。 - 坑:
MeshVertices对高模会产出巨量 site → 必须配SkipFraction + UniformSpacing;Grid 的 UI 上限 5000/轴理论上可达 1.25e11 点,无保护(DataflowGridScatterPoints才有 200/轴 clamp,...FracturingNodes.cpp:360,注释「不要超过 1290 以免 TArray 溢出」)。
4.5 Brick
- 原理:生成一格 3D 砖块(按 Bond 规则错缝、必要时 90° 旋转为「丁砖」),每块是一个 AABB cell;再用
FPlanarCells(Boxes)。 - Bond:
Stretcher(顺砖,奇偶层/列错半砖,FX/.../FractureEngineFracturing.cpp:883-900)、Stack(竖缝对齐,:901-916)、English(顺丁交替,:917-946)、Header(全丁,:947-964)、Flemish(每层顺丁交替,:965-1002)。 - 默认:
Bond=Stretcher, BrickLength=194, BrickHeight=57, BrickDepth=92(cm),ClampMin 0.001(FractureToolBrick.h:46-76)。坐标映射:Dimensions=(Length→X, Depth→Y, Height→Z)(.cpp:99)。 - 硬限制:
NumBricksLimit = 8192;超限时先倍增尺寸找安全值,再二分 10 次最大化砖尺寸,并打 Warning(FractureToolBrick.cpp:62-153,Dataflow 副本FX/.../FractureEngineFracturing.cpp:730-824,:768)。该函数同时挡掉 int 溢出(注释提到天空球 + 1cm 砖会溢出)。 - 相邻处理:
bBricksAreTouching = Grout ≤ UE_KINDA_SMALL_NUMBER(1e-4)→FPlanarCells(Boxes, true)共享面(.cpp:314-315)。UI 命令描述写「目前只支持有 Grout」(.cpp:49),但代码已支持 Grout≈0 的共享面路径。 - 注意:
CutMultipleWithPlanarCells(..., bIncludeOutsideCell=true),砖缝/砖外的几何成为「外部 cell」碎块(灰缝网格),再由孤岛拆分。
4.6 Slice
- 原理:按轴向均匀切片 + 随机偏移/倾角;总平面数 = X+Y+Z(不是乘积),碎块数≈(X+1)(Y+1)(Z+1)。
Step = Extent / (Slices+1)
X 面: pos = (Min.X + Step.X*(i+1), Center.Y, Center.Z) + VRand*GetFraction*OffsetVar; 基向量旋转使 Z→X
再乘 RotA(绕Y, [0,AngleVar]) * RotB(绕X, [0,AngleVar]) // 角度只取正向 [0, max]
Y 面、Z 面同理; plane = FPlane(Transform.Location, Transform.UnitAxis(Z))- 默认:
SlicesX/Y/Z = 3/3/1 → 7 个平面 → 4×4×2=32 块;AngleVariation=0, OffsetVariation=0(FractureToolSlice.h:19-26;.cpp:58-113;FX/.../FractureEngineFracturing.cpp:575-637)。 - 执行:先
ClearProximity(切片使邻接失效),再CutMultipleWithMultiplePlanes(FractureToolSlice.cpp:185-188)。DataflowSliceCutter传FTransform::Identity(FX/.../FractureEngineFracturing.cpp:718),编辑器版传FractureContext.GetTransform()。 - 坑:偏移用 3D 随机向量而非沿轴;每个平面作用于所有碎块,总布尔数 ≈ 2×平面数×相交碎块数。
4.7 Plane Cut
- 原理:Gizmo 单平面,或
NumberPlanarCuts(默认 1,UI 1~20,仅 Gizmo 关闭时可多平面)个随机平面。
pos = Min + FRand³ * Extent; rotation = FRotator(Pitch=FRand*360, Yaw=FRand*360, Roll=0); plane = FPlane(pos, Z轴)- 位置
FractureToolPlaneCut.cpp:441-454;FX/.../FractureEngineFracturing.cpp:486-502。DataflowPlaneCutter_v2:CutPlanes[](Transform 数组,Z 为法线) +NumPlanes(Add Random Cuts,默认 0) +PointSpacing=20(...FracturingNodes.h:726-731,783)。 - 〔推断〕Pitch/Yaw 各自均匀并不给出球面均匀的法线(两极密集)。
4.8 Mesh Cut → §3.8。
4.9 点云来源节点(Dataflow 独有的补充)
- Poisson 圆盘采样(表面):
UniformPointSampling_v2(SamplingRadius=10,样本间距≥2r;MaxNumSamples=0=尽可能多;SubSampleDensity=10;seed 0,GN/Public/Dataflow/GeometryCollectionSamplingNodes.h:207-253)、NonUniformPointSampling_v2(半径区间 +Uniform/Smaller/Larger分布,SizeDistributionPower夹到 [1,10],FX/Private/FractureEngineSampling.cpp:77-82)、VertexWeightedPointSampling_v2(顶点权重 → 半径,WeightToRadius/FilledWeightToRadius/WeightedRandom,FractureEngineSampling.h:26-44)、FilterPointsWithMesh_v2(按 winding number/距离筛点,阈值 0.5,...SamplingNodes.h:91-135)、MeshMedialSkeletonSampling_v2(中轴球,MaxSpheres=1000,:642-690,默认值在:657)。 - 这些是表面采样,要做体积内蓝噪声需自己组合(如 Scatter + FilterPointsWithMesh)〔推断〕。
AppendPoints/TransformPoints用于组合多层图案(...FracturingNodes.h:367-409)。
4.10 Dataflow 节点 ↔ 编辑器工具 ↔ 引擎函数对照
| 编辑器工具 | Dataflow 节点(Category) | 引擎函数 |
|---|---|---|
| Uniform | UniformFracture;或 UniformScatterPoints_v2 + VoronoiFracture_v2(GeometryCollection|Fracture) | FFractureEngineFracturing::UniformFracture :1326 / VoronoiFracture :383 |
| Radial | RadialScatterPoints_v2 → VoronoiFracture_v2 | 同上 |
| Cluster | ClusterScatterPoints → VoronoiFracture_v2 | 同上 |
| Custom | GridScatterPoints/MakePoints/AppendPoints/TransformPoints → VoronoiFracture_v2 | 同上 |
| Brick | BrickCutter | BrickCutter :1013 |
| Slice | SliceCutter | SliceCutter :647 |
| Plane | PlaneCutter_v2 | PlaneCutter :504 |
| Mesh | MeshCutter | MeshArrayCutter :1191 |
| AutoCluster | AutoCluster | FFractureEngineClustering::AutoCluster FX/.../FractureEngineClustering.cpp:869 |
| FixTinyGeo | FixTinyGeo | FFractureEngineUtility::FixTinyGeo FX/.../FractureEngineUtility.cpp:227 |
| Convex | CreateLeafConvexHulls 等 | FGeometryCollectionConvexUtility::* |
| Proximity | Proximity | FGeometryCollectionProximityUtility::UpdateProximity |
〔事实〕废弃节点(5.5):UniformScatterPoints、RadialScatterPoints、VoronoiFracture、PlaneCutter v1(...FracturingNodes.h:20,157,417,624);5.8 将废弃旧版采样节点(...SamplingNodes.h:39,141,259)。
〔事实〕Dataflow VoronoiFracture 的包围盒取整个集合所有几何(FX/.../FractureEngineFracturing.cpp:407-441),而编辑器工具用选中骨骼的包围盒——图案范围不同。
5. 后处理:合并碎屑 / 孤岛 / 法线 / UV / 重采样
5.1 FixTinyGeo(MergeBones / MergeClusters)
- 原理:按体积阈值找出「过小」碎块,合并到邻居(邻接来自
Proximity)。
Volumes = FindBoneVolumes(…, scale=0.01 → m³) // FX/.../FractureEngineUtility.cpp:95,153-159
MinVolume = (MinSize·0.01)³ (SelectionMethod=VolumeCubeRoot)
= (cbrt(TotalVol)·RelativeVolume)³ (RelativeVolume) // :116-134
Small = {Volume < MinVolume}
for s in Small: 取邻居中得分最高者: LargestNeighbor=体积 | NearestCenter=1/(ε+距离²) | LargestContactArea=采样接触面积
用「RemoveGroup」合并链: 目标取组内最大者, 直到组总体积 ≥ MinVolume // PlanarCut.cpp:1479-1568, 1624-1714
把被并者的网格追加(AppendMesh, 不做布尔 union)到目标; 删除被并骨骼; 子节点改挂目标- 默认(Fracture 工具
FractureToolFixTinyGeo.h:56-102):MergeType=MergeGeometry, NeighborSelection=LargestNeighbor, bOnlySameParent=true(簇模式), bOnlyToConnected=true, SelectionMethod=RelativeVolume, RelativeVolume=0.01, MinVolumeCubeRoot=1;Dataflow 同(GN/Public/Dataflow/GeometryCollectionUtilityNodes.h:1050-1097),另有bGeometryOnlySameParent=false、bUseCollectionProximityForConnections=false(默认自己算 Precise 邻接)。 - 接触面积:
ComputeGeometryVolumeContactWeightsFromProximity默认DistanceThreshold=0.01, SamplesPerUnitArea=10, MaxSamplesPerTri=100,用 AABB 树近似接触面积(PlanarCut.cpp:1384-1435)。 - 坑:① 编辑器与 Dataflow 都传
bUnionJoinedPieces=false,注释:「Union 理论上更好,但会留裂缝和非流形垃圾」(FractureToolFixTinyGeo.cpp:312;FractureEngineUtility.cpp:286)——被并碎块只是网格拼接,内部面仍在;②Merge Clusters在「无邻居且bOnlyMergeInProximity=false」时会 O(N) 扫描所有同层簇并对每个候选递归算包围盒(PlanarCut.cpp:2005-2024)〔推断〕规模大时慢;③ 邻接缺失 →"Couldn't fix Bone %d: No neighbors found"(:1671)。
5.2 Split Islands 工具/节点
- 直接调用
SplitIslands(§3.3):CloseVertexDistance=0.001, VertexToSurfaceBridgeDistance=0(FE/Private/FractureToolSplitIslands.h:25-30;GN/.../UtilityNodes.h:1149-1154)。
5.3 Normals / Resample
RecomputeNormals:§3.7。Resample:AddCollisionSampleVertices(Spacing)→ 孤立采样点(§3.7),Spacing 默认 50(FractureToolResample.cpp:109;PlanarCut.cpp:2311-2325)。
5.4 Validate
RemoveUnreferencedGeometry=true, RemoveClustersOfOne=false, RemoveDanglingClusters=false;如脏则重算 Level 并保证单根(FX/.../FractureEngineUtility.cpp:355-434;GN/.../UtilityNodes.h:1277-1286)。
5.5 AutoUV(可选)
BoxProjectUVs、MergeUVIslands(AreaDistortionThreshold=1.5, MaxNormalDeviationDeg=45,ExpMap 重算 UV,PC/Public/FractureAutoUV.h:84-101)、UVLayout(FUVPacker打包,默认UVRes=1024, GutterSize=1,:127-136;实现PC/Private/FractureAutoUV.cpp:634-795)、TextureSpecifiedFaces(烘焙 DistanceToExternal/AO/Curvature,:188-268)。工具默认GutterSize=2(FractureToolUV.h:151)。
6. 层级构建与聚类
6.1 「Level」与基本操作(FGeometryCollectionClusteringUtility)
- Level:
Transform组的 int 属性;Level[child] = Level[parent]+1(递归,CH/Private/GeometryCollection/GeometryCollectionClusteringUtility.cpp:682-725);ClusterAllBonesUnderNewRoot会把已有 Level 全部 +1 并令新根 =0(:94-98)。每做一次破碎,叶子变簇、新碎块在 Level+1(§1)。 - 头文件的示意图:
L0 Root → L1 {A B C D};Cluster A&B→L1 {E C D}, L2 {A B},E无自身几何,只提供变换(CH/Public/GeometryCollection/GeometryCollectionClusteringUtility.h:13-40)。
| 函数 | 行为 | 位置 |
|---|---|---|
ClusterBonesUnderNewNode(Idx, Bones) | 在 Idx 的父下新建簇节点 ClusterBone_<idx>,把 Bones 挂入(ParentTransforms 保持世界位姿) | .cpp:13-64 |
ClusterBonesUnderNewNodeWithParent | 同上,直接给定新节点的父 | :22-64 |
ClusterAllBonesUnderNewRoot | 所有现有根下新建唯一根;随机骨骼颜色 (rand%100+5) | :66-151 |
ClusterBonesUnderExistingNode | 并入已有簇;多根时自动补新根 | :213-301 |
ClusterBonesByContext(MergeNode, …) | 若 MergeNode 已是簇→并入;否则以 MergeNode 为插入点新建簇 | :303-315 |
PickBestNodeToMergeTo | 在源集合中选层级最浅且有孩子者 | :394-418 |
CollapseHierarchyOneLevel / MoveUpOneHierarchyLevel / CollapseLevelHierarchy | 删簇/上移一级/拍平到某级 | :317-358, 863-889, 727-771 |
RemoveDanglingClusters / RemoveClustersOfOnlyOneChild | 清理无叶子的簇 / 单孩子簇(循环到稳定) | :984-1015, 943-982 |
FindLowestCommonAncestor | 最近公共祖先 | :892-941 |
FFractureEngineFracturing/各工具会在需要时AddSingleRootNodeIfRequired(多根→补根,FE/Public/FractureTool.cpp:140-150)。- 〔事实〕
ParentTransforms每次调用先对整个集合算一遍GlobalMatrices(CH/Private/GeometryCollection/Facades/CollectionTransformFacade.cpp:320-365),AutoCluster 每创建一个簇调用一次(FX/.../FractureEngineClustering.cpp:1005) → 总计 O(簇数×N)〔推断〕。 ValidateResults受 CVarp.fracture.ValidateResultsOfEditOperations(默认 0)控制:开启后每次编辑检查「恰一个根、面/顶点连续」,"可能很慢"(.cpp:10-11, 806-828)。
6.2 AutoCluster(FFractureEngineClustering::AutoCluster)
- 原理:对某簇的直接孩子的包围盒中心做 K-Means(Lloyd),可选用邻接图把每个分区拆成连通分量,再把单孩子/过小分区并掉,最后为每个分区创建一个簇。
AutoCluster(GC, ClusterIndex, Method, …) // FX/Private/FractureEngineClustering.cpp:869
Desired = ByNumber: SiteCount
| ByFractionOfInput: max(2, N·frac)
| BySize: max(1, Volume(整个GC包围盒)/SiteSize³) // 注意用整个集合的包围盒 :902-904
| ByGrid: |GenerateGridSites| (格心 (i+.5)/n, :1055-1075)
if bPreferConvexity: ConvexityBasedCluster(SiteCount, …); return // 注意此分支用 SiteCount,不用 Desired :912-916
if Desired<=1 or N<=1: return
loop:
P.KMeansPartition(K, iters, initialCenters=grid) // :137-155
if (bEnforceConnectivity||bAvoidIsolated): UpdateProximity()
if bEnforceConnectivity: P.SplitDisconnectedPartitions() // 用 Proximity 连通性 :281-318
if MinimumSize>0: P.MergeSmallPartitions(size³) // :196-279
if bEnforceSiteParameters && partitions>Desired && K>1: K--, repeat // :967-976
新建 ClusterBone 簇(Parent=ClusterIndex) 并 ParentTransforms; 单孩子分区(bAvoidIsolated)跳过
RemoveDanglingClusters; UpdateHierarchyLevelOfChildren; RecursivelyUpdateChildBoneNames- K-Means 细节(
FVoronoiPartitioner):质心 = 孩子(含子树)世界空间顶点 AABB 中心(:392-451);初始中心 = 按ChildrenTSet 迭代顺序、等步长抽样,不是随机/kmeans++(:453-483);初始全部归 0 号分区,首轮即为第一次赋值(:146-154);迭代到无变化或MaxIterations(+1),距离用float(:531-548);空分区保留(PartitionSize=0,不重新播种)。 - 默认(
FE/Private/FractureToolAutoCluster.h:46-112;GN/Public/Dataflow/GeometryCollectionClusteringNodes.h:34-95):ClusterSizeMethod=ByNumber, SiteCount/ClusterSites=10, SiteCountFraction=0.25(ClampMax .5), SiteSize=1cm, Grid 2×2×2, DriftIterations=0, MinimumSize=0, bPreferConvexity=false, ConcavityTolerance=0, bEnforceConnectivity=true, bEnforceSiteParameters=true, bAvoidIsolated=true;K-Means 迭代数:ByGrid ? DriftIterations : 500(FractureToolAutoCluster.cpp:213;节点GN/.../ClusteringNodes.cpp:119)。 - ConvexityBasedCluster(
:678-867):把每个孩子的子树顶点做凸包体积 →FConvexDecomposition3::InitializeFromHulls+MergeBest(TargetNumParts=SiteCount);限制「合并后体积 < 总体积/SiteCount,或其中一个 < 20% 份额」(:811-822);邻接来自 Proximity(bEnforceConnectivity)或 AABB 重叠(:795-799)。 - 坑/规模化〔推断,均据代码阅读〕:
GenerateConnectivity→CollectConnections对每个邻居调用TransformIndices.Find(...)(线性搜索,:376),总计 O(N²·平均邻居数);N=2 万孩子时约 10⁹ 量级比较。MarkVisited是递归 DFS(:320-330),单个连通分区很大时栈深 = 分区大小,有栈溢出风险。PreviousPartitionCount声明为INT_MAX后从未更新(:933, 971),「是否在进步」条件恒真 →bEnforceSiteParameters会把 K 逐一递减、每次重跑完整 K-Means+连通拆分,直到分区数 ≤ Desired 或 K=1。SplitDisconnectedPartitions对每个分区做Partitions.Find+ 全量扫描 → O(K·N);MergeSmallPartitions嵌套 O(P·N·度)(:227-278)。BySize用整个集合包围盒体积,默认 1cm 在大物体上会算出天文数字的 Desired(K-Means 里PartitionCount = Min(PartitionCount, N)兜底,:464)。
6.3 Cluster Magnet
- 原理:以选中的簇为「磁铁」,沿邻接图迭代吸附相邻兄弟节点,每个磁铁最终新建一个簇。
对选中节点按父节点分组: group.Key=父
TopNodeConnectivity = 把叶子 Proximity 通过 GetParentOfBoneAtSpecifiedLevel(Level+1) 提升到「兄弟层」
magnets = 选中者; pool = 其余兄弟
for it < Iterations: for m in magnets: 吸收 m.Connections ∩ pool, Connections ∪= 被吸收者的 Connections; 若无人吸收则 break
对 |ClusteredNodes|>1 的 m: ClusterBonesUnderNewNode- 参数:
Iterations=1(UIMax 100, ClampMax 500)(FE/Private/FractureToolClusterMagnet.h:26,30);实现FX/.../FractureEngineClustering.cpp:16-125, 1190-1259;DataflowClusterMagnet同默认(GN/.../ClusteringNodes.h:348-349)。 - 取舍:磁铁依次吸取,先处理的磁铁对共享邻居有优先权(
:1234-1237)——结果依赖选择顺序。
6.4 其他簇操作(Fracture Mode)
Flatten(拍平到 Level1:GetLeafBones→ClusterBonesUnderExistingNode,FractureToolClustering.cpp:38-87)、Cluster(选中骨骼挂到最近公共祖先下的新簇,FractureEngineClustering.cpp:1081-1115)、Uncluster、Level Up、Cluster Merge(:1117-1187)。Dataflow:Flatten/Uncluster/Cluster/ClusterMerge/ClusterMergeToNeighbors/ClusterIsolatedRoots/ClusterMagnet(GN/.../ClusteringNodes.h:117-362)。ClusterMergeToNeighbors只暴露 LargestNeighbor/NearestCenter(:211-215),默认MinVolumeCubeRoot=0, bOnlyToConnected=true, bOnlySameParent=true。
6.5 GeometryCore/Clustering 两个通用工具(和破碎的关系)
FClusterKMeans(GC/Public/Clustering/KMeans.h:125-366):通用 K-Means,MaxIterations=500,初始中心=洗牌前 K 个点(:175-193),空簇被删并重映射 ID(:309-342),可选并行(需PLATFORM_SUPPORT_FLOATING_INTERLOCKED_ADD,Windows + C++20,结果可能非确定,:20-22,159),GetUniformSpacedInitialCenters用FPriorityOrderPoints做均匀初值(:351-362)。注意:AutoCluster 并不使用它(用自写FVoronoiPartitioner);引擎内使用者是 Nanite 构建器(Source/Developer/NaniteBuilder/Private/ClusterDAG.cpp:340,860)。FaceNormalClustering(GC/Public/Clustering/FaceNormalClustering.h:14-49):按法线相似度对网格面做贪心边合并(优先队列,权重1 − n_A·n_B,面积加权更新组法线,面积 < 阈值的面总是并入邻居;NormalOneMinusCosTolerance=0.01≈8.1°,TargetMinGroups),输出 PolyGroup(实现GC/Private/Clustering/FaceNormalClustering.cpp:19-135)。破碎流水线不直接用;PolygroupsGenerator用它(Plugins/Runtime/GeometryProcessing/Source/DynamicMesh/Private/Polygroups/PolygroupsGenerator.cpp:574-577),凸包AngleTolerance简化沿用同一思路(FX/.../FractureEngineConvex.cpp:336-455)。
7. 碰撞凸包(Convex)
7.1 总管线(CreateNonOverlappingConvexHullData)
〔事实〕CH/Private/GeometryCollection/GeometryCollectionConvexUtility.cpp:1769-1818;触发:编辑器每次 FGeometryCollectionEdit 析构且 !bShapeIsUnchanged(GE/.../GeometryCollectionComponent.cpp:5276-5282 → UpdateConvexGeometry GE/.../GeometryCollectionObject.cpp:450-461),或 Dataflow Terminal 的 ResetFrom(凸包缺失时,:611-619, 441-448)。
1 GlobalTransforms
2 ComputeLeafHulls → 每个 rigid 骨骼: 世界顶点 → FilterHullPoints(间距≥SimplificationDistanceThreshold) → 以质心为枢轴缩小 Shrink% → FConvex (HullsFromGeometry :1297-1600)
3 RequireProximity(UseLeafHulls) → 邻接 (§8)
4 SetVolumeAttributes → 每骨骼 Volume(簇=子和)、Size = cbrt(Volume)/cbrt(最大叶子体积) (:2853-2930)
5 CreateNonoverlappingConvexHulls (:449-1166):
a. [仅 RemoveOverlaps==All] 叶子-叶子: 对每个 Proximity 邻对 FixCollisionWithCut (串行 :694-734)
b. 自底向上(按深度,ParallelFor 每层) 为簇生成凸包: 合并子孙已有凸包的点 → Filter → Shrink → FConvex;
若 HullVolume > ClusterVolume·(1+CanExceedFraction) → 放弃该簇凸包(改用子孙凸包之并) (:874-945)
c. 簇邻接 SameDepthClusterProximity (由叶子邻接按层级提升) → IsBoneColliding(GJK)
d. 簇间/簇-叶去重叠 CutIfOk: GJKPenetration → FindCutPlane → CutHull(两侧) → MakeHull;
若切后体积/原体积 < 1−FractionAllowRemove → 放弃: 清空这些骨骼凸包(回退用子孙) (:1014-1163)
6 TransformHullsToLocal → 写回 ConvexHull / TransformToConvexIndices; RemoveEmptyConvexHulls- 切平面选择(
FindCutPlane :624-650):候选 1 = GJK 最近点平面;候选 2 = 质心连线中垂面(仅当候选 1 得分>0);ScoreCutPlane(:559-619)= 两凸体沿法线投影区间的「交集长度/并集长度」,完全分离=0(最佳)。大的一方保留,bOneSidedCut时只切 A,B 不动。 - 点云简化:
FilterHullPoints(:192-269)先取 4 个「极点」(TExtremePoints3),再按到包围盒中心距离降序贪心加入,用TPointHashGrid3(cell=threshold)拒绝距离已选点 < threshold 者——保证覆盖体积、避免退化凸包。
7.2 参数默认值
| 参数 | 值 | 位置 |
|---|---|---|
FConvexCreationProperties(资产级,驱动 UpdateConvexGeometry) | Enable=true, FractionRemove=0.5, SimplificationThreshold=10, CanExceedFraction=0.5, RemoveOverlaps=All, OverlapRemovalShrinkPercent=0 | CH/Public/GeometryCollection/GeometryCollectionConvexPropertiesInterface.h:35-42 |
| Fracture Mode Convex 工具 | CanExceedFraction=.5, SimplificationDistanceThreshold=10, RemoveOverlaps=All, Shrink=0, Max Removal Fraction=.5 | FE/Private/FractureToolConvex.h:27,31,35,39,43 |
| 函数默认实参 | FractionAllowRemove=.3, SimplificationDistanceThreshold=0, CanExceedFraction=.5 | CH/Public/.../GeometryCollectionConvexUtility.h:123 |
Dataflow CreateNonOverlappingConvexHulls | CanExceedFraction=.5, Simplification=10, RemoveOverlaps=All, Shrink=0, Max Removal Fraction(CanRemoveFraction)=**.3**(ClampMin .01) | GN/Public/Dataflow/GeometryCollectionUtilityNodes.h:365-381 |
EConvexOverlapRemoval | None / All / OnlyClusters / OnlyClustersVsClusters | GeometryCollectionConvexUtility.h:17-27 |
| Shrink 上限 | 99.9% | GeometryCollectionConvexUtility.cpp:1754 |
⚠
FractionRemove默认值在四处不一致(.5/.5/.3/.3),演示页引用时请注明来源。
7.3 叶子凸包与凸分解(Level-set vs Convex vs Decomposition)
- 默认碰撞形状 = Convex:
FGeometryCollectionCollisionTypeData构造默认Chaos_Volumetric + Chaos_Implicit_Convex,CollisionObjectReductionPercentage=0,CollisionMarginFraction=0(GE/.../GeometryCollectionObject.cpp:184-192);FGeometryCollectionSizeSpecificData默认MaxSize=99999.9, DamageThreshold=5000(:194-210);LevelSet 分辨率5/10(叶子)与25/50(簇)、粒子Fraction=1.0, Max=60(:168-180)。LevelSet路径强制Chaos_Surface_Volumetric(Particle-Implicit)(:344-347, 540-548)。 - 凸分解默认关:
FConvexDecompositionSettings注释「默认值不做任何分解」(GeometryCollectionConvexUtility.h:179-233)——MinGeoVolumeToDecompose=0, MaxGeoToHullVolumeRatioToDecompose=1, MaxHullsPerGeometry=1, ErrorTolerance=0, MinThicknessTolerance=0, NumAdditionalSplits=4, ThickenHullOnFailure=KINDA_SMALL, bProtectNegativeSpace=false, bOnlyConnectedToHull=true, NegativeSpaceTolerance=2, NegativeSpaceMinRadius=10。触发条件MaxHulls>1 || ErrorTolerance>0 || bProtectNegativeSpace || CustomEmptySpace,再经体积过滤(MinGeoVolume,geo/hull 体积比 ≥ MaxRatio则跳过)(.cpp:1434-1461)。 - Dataflow 凸包节点(
GN/Public/Dataflow/GeometryCollectionUtilityNodes.h):MakeConvexDecompositionSettings(:100):MinSizeToDecompose=0, MaxGeoToHull=1, ErrorTolerance=0, MaxHullsPerGeometry=-1, MinThickness=0, NumAdditionalSplits=4, 负空间 2/10(注意节点默认MaxHulls=-1,而 C++ 结构默认 1)。CreateLeafConvexHulls(:157):GenerateMethod=ComputedFromGeometry(:172)、Simplification=10(:188)。SimplifyConvexHulls(:295):MeshQSlim、距离阈值 10、MinTargetTriangleCount=12(:318)、bUseExistingVertices=false;引擎层默认ErrorTolerance=5, TargetTriangleCount=20, bUseExistingVertexPositions=true(FX/Public/FractureEngineConvex.h:24-38)——节点与引擎默认不同。GenerateClusterConvexHullsFromLeafHulls(:499)/FromChildrenHulls(:620):ConvexCount=2(结构默认 4,GeometryCollectionConvexUtility.h:133)、ErrorTolerance=0、AllowMerges=ByProximity、MergeProximityFilter=None、DistanceThreshold=.1、bAllowMergingLeafHulls=false;实现用邻接限定可合并对,FConvexDecomposition3::MergeBest(ConvexCount, ErrorToleranceCm, …, EmptySpace)(.cpp:2097-2365,:2329)。MergeConvexHulls(:807):MaxConvexCount=-1;ClearConvexHulls/CopyConvexHullsFromRoot/CreateNegativeSpaceSphereCovering(默认NavigableVoxelSearch,TargetNumSamples=50,MinRadius=10,:446-470)。
FConvexDecomposition3内部(GC/Public/CompGeom/ConvexDecomposition3.h):先「切分」(沿凸边采样切平面,MaxConvexEdgePlanes=50,ConvexEdgeAngleThreshold=170°),再「合并」(MergeBest,按「合并后凸包体积增量」最小优先,BiasToRemoveTooThinParts=.1,RestrictMergeSearchToLocalAfterTestNumConnections=-1);容差在单位立方体空间(:411-451)。无 CVar(我检索ConvexDecomposition3.cpp/凸包构建文件未发现)。
7.4 与凸包/粒子碰撞相关的 CVar(均〔事实〕)
| CVar | 默认 | 位置 | 作用 |
|---|---|---|---|
p.GeometryCollectionEnableForcedConvexGenerationInSerialize | true | GE/.../GeometryCollectionObject.cpp:64-68, 1276-1292 | 旧资产加载时若无凸包且用 Convex,则强制生成(并把 Box 改 Convex) |
p.Chaos.GC.EnableCollisionParticles | true | CH/Private/GeometryCollection/GeometryCollection.cpp:24-28 | 是否使用碰撞粒子 |
p.CollisionParticlesUseImplicitCulling / SpatialDivision / Min / Max | false / 10 / 10 / 2000 | …CollisionStructureManager.cpp:24-34 | 仅粒子-隐式(LevelSet)路径的粒子裁剪 |
p.gc.UseVolumeToComputeRelativeSize | false | GeometryCollectionConvexUtility.cpp:22-23 | Size 属性是否直接用体积(否则立方根) |
p.gc.UseLargestClusterToComputeRelativeSize | false | :25-26 | 〔疑似笔误〕 该 CVar 实际绑定的是 UseVolumeToComputeRelativeSize 变量(:26),自身变量无人读写 |
p.fracture.ValidateResultsOfEditOperations | 0 | GeometryCollectionClusteringUtility.cpp:10-11 | 编辑后校验(慢) |
p.FractureEditor.UseNewDataflowEvaluator | true | FE/Private/FractureEditorModeToolkit.cpp:94-96 | 编辑器用新 Dataflow 求值器 |
7.5 ④ 规模化
- 叶子-叶子去重叠
FixCollisionWithCut在for HullIdx主循环里串行执行(:694-734,并行只在步骤 b 的每层ParallelFor,:888),对每个邻对做 1 次 GJKPenetration + 最多 2 次MakeHull〔推断〕:10 万碎块 × ~12 邻居 ≈ 60 万次 → 秒级~分钟级。 - 可选缓解(来自枚举注释/默认值):
RemoveOverlaps=OnlyClusters("忽略叶子-叶子重叠")或None;用OverlapRemovalShrinkPercent让重叠检测按缩小后的凸体进行,从而少切;SimplificationDistanceThreshold抬高(默认 10cm)减少点数;CanExceedFraction/FractionAllowRemove控制何时放弃簇凸包而用子凸包之并。 - 编辑器里每次工具执行都会重算整个集合的凸包(
ExecuteUpdatesShape()默认 true,FractureTool.h:163-166;FractureTool.cpp:386)。Dataflow 工作流下只在 Terminal 缺凸包时才算 → 大集合建议在 Dataflow 里显式一次性生成。
8. 邻接(Proximity)与连接图
8.1 方法
〔事实〕CH/Public/GeometryCollection/GeometryCollectionProximityUtility.h:21-50;实现 CH/Private/GeometryCollection/GeometryCollectionProximityUtility.cpp。
| 项 | 取值 | 说明 |
|---|---|---|
EProximityMethod | Precise(默认) / ConvexHull | FProximityProperties 默认见下 |
EProximityContactMethod | MinOverlapInProjectionToMajorAxes(默认) / ConvexHullSharpContact / ConvexHullAreaContact | 仅当 RequireContactAmount>0 才过滤 |
EConnectionContactMethod | None(默认) / ConvexHullContactArea | 仅 bUseAsConnectionGraph 有意义 |
默认 FProximityProperties:Method=Precise, DistanceThreshold=1.0, RequireContactAmount=0, ContactMethod=MinOverlapInProjectionToMajorAxes, ContactAreaMethod=None, bUseAsConnectionGraph=false(GeometryCollectionProximityPropertiesInterface.h:35-43);Fracture 工具 / Dataflow Proximity 节点默认相同(FE/Private/FractureToolProximity.h:26-47;GN/Public/Dataflow/GeometryCollectionNodes.h:1370-1397)。
8.2 Precise 算法(FGeometryCollectionProximitySpatial,:146-429)
TransformVertices (世界) ; SurfaceNormals(每面)
InitProximityFromVertices: TPointHashGrid3f(cell=tol·3); 不同骨骼的顶点距离≤tol → 直接记邻居 (:353-393)
InitGeoNormalBins: 每个几何把面按 20 个二十面体方向分箱 (:67-143)
InitCandidateContacts: FSparseDynamicOctree3 对几何 AABB(⊕tol) 做宽相 (:309-342)
ComputeCoplanarContacts(ParallelFor): 候选对未确认者: 对每个 bin 与其「对跖 bin」中的面对:
面 AABB 与对方几何 AABB 相交 && 法线反向(dot<-1+1e-4) && 共面(|dist|<tol) && 2D 三角形重叠(分离轴) → 邻接 (:164-213, 261-307)PreciseProximityThreshold = 0.01f,常量不开放,注释:「提高会退化到 O(n²),结果也会令人困惑」(ProximityUtility.h:97-99)。- 已知缺口:法线恰在 bin 边/角时可能漏配,注释「靠顶点邻接和其它面兜底」(
.cpp:100-102)。 - 仅 rigid 参与(
:366-369);ComputePreciseProximity(Collection)是不依赖集合配置的静态入口(:719-724),FixTinyGeo/MergeBones 在bUseCollectionProximity=false时使用。
8.3 ConvexHull 方法(BuildProximityFromConvexHulls,:431-517)
- 叶子凸包(不缩小,
:747)→FSparseDynamicOctree3(根尺寸Min(2·MaxHullDim, 总范围+阈值))→ AABB(厚Threshold/2) 相交 →GJKDistance,Contact/DeepContact或Separated && Distance ≤ DistanceThreshold(默认 1cm) 即邻接。
8.4 接触过滤与接触面积
RequireContactAmount>0时(UpdateProximity :767-861):- ProjectedBoundsOverlap:对三个主轴投影,任一轴 2D 重叠的
min(尺寸) > min(0.5·MinBoundsAxis, RequireContactAmount)即保留——「大于阈值或大于可能最大值的一半」以免把小碎块全滤掉; - ConvexHullSharp:移除
SharpContactWidth < Required && < 0.5·MaxSharp; - ConvexHullArea:移除
Area < Required² && < 0.5·MaxArea。
- ProjectedBoundsOverlap:对三个主轴投影,任一轴 2D 重叠的
- 接触面积
ComputeConvexGeometryContactFromProximity(:880-927):对邻接几何的所有凸包对HullIntersectionStats(…, DistanceTolerance),ContactArea = 交面积 × 0.5,MaxContactArea = 各 MaxArea 之和 × 0.5(头文件注释写「较小表面积的一半」,.h:75-76)。 - 连接图:
CopyProximityToConnectionGraph(:610-717)把几何级邻接沿层级向上提升,直到两端骨骼同父,在兄弟骨骼间建边(bAccumulateContactAreas时累加面积);bUseAsConnectionGraph=false时会ClearConnectionGraph(:863-870,TODO 注释提醒可能误删自定义图)。运行时簇破碎的应变/强度即依赖此图(跨专题,仅陈述存在)。 EnumerateNeighbors(Hierarchy, Bone, Func, bIncludeNeighborsInParentLevels, bFilterDuplicates)(:551-608):取骨骼所有 rigid 孩子的邻居并提升到同层。
8.5 失效与更新
- 切片类工具显式
ClearProximity(FractureToolSlice.cpp:186;FractureEngineFracturing.cpp:639-645,716);Proximity属性在 Geometry 组上("稳定",不随簇变化,PlanarCut.cpp:1918-1921注释)。RequireProximity仅在属性缺失时计算(:527-534)。
9. 5.7 推荐的 Dataflow 破碎流程
- 模板资产(从 uasset 字符串提取节点类型,二进制未深解析):
GeometryCollectionPlugin/Content/DF_GC_Template_StaticMesh.uasset=StaticMeshToCollection_v2 → UniformFracture → GeometryCollectionTerminal_v2;DF_GC_Template_Source.uasset=CreateGeometryCollectionFromSources_v2 → UniformFracture → Terminal_v2。 - 求值要点(〔事实〕):
- 破碎节点输出
Collection、TransformSelection(原选择)、FracturedTransformSelection(新碎块),便于链式多级破碎(GN/.../FracturingNodes.cpp:497-583, 1296-1385)。 - 未连
TransformSelection时默认SelectAll(:508-520)。 - 所有节点在集合副本上工作(
NewCopy<FGeometryCollection>)。 - Terminal
ResetFrom(GN/Private/Dataflow/GeometryCollectionAssetNodes.cpp:121-170)→UpdateConvexGeometryIfMissing;bHasInternalMaterial=false("Dataflow 不假设内部材质",:143)。
- 破碎节点输出
- 〔组合建议,非模板〕典型 DAG:
StaticMeshToCollection_v2- 第 1 级:
UniformScatterPoints_v2(20) →VoronoiFracture_v2(或直接UniformFracture) - 第 2 级:把
FracturedTransformSelection接入第二个破碎节点(ChanceToFracture控制比例) FixTinyGeo(MergeGeometry, RelativeVolume=0.01)→ 必要时SplitIslandsProximity(Precise;若运行时要连接图则bUseAsConnectionGraph=true+ContactAreaMethod=ConvexHullArea)AutoCluster/ClusterMagnet(依赖 Proximity)CreateLeafConvexHulls(需要时配MakeConvexDecompositionSettings)→GenerateClusterConvexHulls…或直接CreateNonOverlappingConvexHullsRecomputeNormals…/AutoUnwrapUV/BoxProjectUV(可选)→ValidateGeometryCollection→GeometryCollectionTerminal_v2
10. 规模化(成千上万碎块)最佳实践与风险清单
10.1 来自源码默认值/限制的建议(有依据)
| # | 建议 | 依据 |
|---|---|---|
| 1 | 多级破碎,每级 20~数百 site,而不是一次切数万 | UI 上限 5000(FractureToolUniform.h:24);每个 cell 都对整张源网格做布尔且无细粒度剔除(§3.9-1);源码假设 Voronoi 只占 ~1%(FractureToolCutter.cpp:635) |
| 2 | 用 ChanceToFracture 控制「哪些骨骼继续碎」,制造稀疏/非均匀密度 | RandomReduceSelection 逐骨骼独立抽样(FractureToolContext.cpp:113-124) |
| 3 | 需要局部加密用 Cluster/Custom(SkipFraction+UniformSpacing)而非加大全局 N | §4.3/4.4 |
| 4 | 砖墙类:保持 NumBricks ≤ 8192;超出会被自动放大砖尺寸并 Warning | FractureToolBrick.cpp:97-148 |
| 5 | 噪声:Amplitude=0 先出拓扑;开噪声时增大 PointSpacing(默认 10;PlaneCutter 20)控制三角数;总新增顶点上限 ~100 万 | GeometryMeshConversion.cpp:2020-2032;MeshCut 不支持噪声 |
| 6 | 碰撞采样点只在使用 Particle-Implicit/LevelSet 时开启;默认关 | bAddSamplesForCollision=false(FractureToolCutter.h:203);描述「仅对 particle-implicit 有用」 |
| 7 | 先 FixTinyGeo(默认 RelativeVolume 0.01)清理碎屑再聚类/生成凸包 | 默认值;bUnionJoinedPieces=false 避免裂缝 |
| 8 | 聚类保持 bEnforceConnectivity=true、bAvoidIsolated=true(默认),避免非连通簇与单孩子簇 | FractureToolAutoCluster.h:102-110 |
| 9 | 邻接用 Precise(专为本工具切出的几何设计);ConvexHull 方法仅在几何不共面时用;别指望调大 Precise 阈值 | ProximityUtility.h:24,97-99 |
| 10 | 凸包:保留默认单凸包/骨骼;大量叶子时 RemoveOverlaps=OnlyClusters/None + OverlapRemovalShrinkPercent;把凸包生成放到 Dataflow 末尾一次性做 | §7.5 |
| 11 | 大集合关闭 p.fracture.ValidateResultsOfEditOperations(默认就是 0) | CVar 描述「大集合可能很慢」 |
| 12 | 嵌入几何(FST_None)不参与凸包/破碎(SkipType),适合放装饰细节 | CreateNonoverlappingConvexHulls 的 SkipBone(:477-480, 696-700) |
10.2 我从代码读出的潜在瓶颈/风险(〔推断〕,演示时请标「源码阅读所得,未实测」)
| 风险 | 位置 | 说明 |
|---|---|---|
| 布尔成本 ∝ cell 数 × 源网格复杂度 | GeometryMeshConversion.cpp:3020-3049 | 每个 cell 对整个源网格做布尔;并行度仅限 cell 级 |
| 多级破碎隐藏几何累积 | :2922, :3133 | 旧级几何保留(Visible=false) |
| AutoCluster 连通性构建 O(N²·度) | FractureEngineClustering.cpp:376 | TArray::Find 线性搜索 |
| 递归 DFS 栈深 | :320-330 | 大连通分区风险 |
| 迭代「进步」变量未更新 | :933, 971 | K 可能逐一递减直至满足 |
每簇一次全集合 GlobalMatrices | CollectionTransformFacade.cpp:320-365,AutoCluster :1005 | O(簇×N) |
| K-Means 初始中心取决于 TSet 顺序、距离用 float | :453-483, 531-548 | 结果依赖存储顺序;超大坐标精度 |
| 叶子凸包去重叠串行 | GeometryCollectionConvexUtility.cpp:694-734 | 见 §7.5 |
| 编辑器每次操作重算全集合凸包 | FractureTool.cpp:386,GeometryCollectionComponent.cpp:5278-5280 | 交互时越来越慢 |
MergeClusters 回退分支 O(N) 扫描 | PlanarCut.cpp:2005-2024 | 无邻居的小簇 |
| 布尔失败不可见 | GeometryMeshConversion.cpp:3037-3042 | 空 TODO,无失败检测,FillHoles 关闭 |
| Voronoi 边界/极大包围盒 | Voronoi.cpp:86-88 | 仅 ensure |
10.3 数值鲁棒性备忘(给演示页「坑点」栏)
- Voronoi:越界丢点、近点去重(0.01cm)、共圆退化(径向 1e-4 抖动)、
compute_cell失败ensure。 - 布尔:未焊接 → 依赖
CloseVertexDistance=1e-3判连通;非流形三角形复制顶点;孤立顶点默认剔除。 - 噪声:自交修复至多 10 轮,4 步二分;失败则保留当前状态(无报错)。
- Tiny geo:以 m³ 单位(×0.01 缩放)判定,避免 cm³ 数值过大(
FractureEngineUtility.cpp:93-96)。
11. 默认值速查(一页)
| 模块 | 参数 | 默认 |
|---|---|---|
| 公共 | RandomSeed / Chance / GroupFracture | -1 / 1.0 / true |
| 公共 | Grout | 0 |
| 公共 | SplitIslands / CloseVertexDistance / Bridge | true / 0.001 / 0 |
| 噪声 | Amplitude / Frequency / Persistence / Lacunarity / Octaves / PointSpacing | 0 / 0.1 / 0.5 / 2 / 4 / 10(PlaneCutter_v2: 20;FNoiseSettings 结构默认 Amp=2, Spacing=1) |
| 碰撞采样 | Add / Spacing | false / 50 |
| Uniform | Min/Max Sites | 20 / 20 |
| Radial | Angular×Radial / Radius / Exponent / MinStep | 5×5 / 50 / 1 / 1 |
| Cluster | 簇数 / 每簇点 / 半径分数 / 偏移 | 8~8 / 2~30 / 0.1~0.2 / 0 |
| Custom | Pattern / SitesToAdd / Grid / Skip | Uniform / 20 / 5³ / 0 |
| Brick | Bond / L×H×D | Stretcher / 194×57×92 |
| Slice | X/Y/Z / AngleVar / OffsetVar | 3/3/1 / 0 / 0 |
| Plane | 随机平面数 | 1(编辑器)/ 0(Dataflow Add Random Cuts) |
| MeshCut | 分布 / 数量 / 缩放 / 朝向 | Single / 10 / 0.5~1.5 / ±180 |
| FixTinyGeo | 合并类型 / 邻居选择 / 选择方式 / RelativeVolume / MinSize | Geometry / LargestNeighbor / RelativeVolume / 0.01 / 1 |
| AutoCluster | 方法 / 点数 / 迭代 / 连通 / 避孤立 | ByNumber / 10 / 500(Grid:Drift 0) / true / true |
| ClusterMagnet | Iterations | 1 |
| Convex | Simplify / CanExceed / FractionRemove / Overlap / Shrink | 10 / 0.5 / 0.5(工具)·0.3(节点) / All / 0 |
| 凸分解 | MaxHulls / Error / Splits | 1(C++)·-1(节点) / 0 / 4 |
| Proximity | Method / DistanceThreshold / Contact | Precise / 1 / 0(不过滤) |
| 簇凸包 | ConvexCount | 2(节点)·4(结构) |
| 碰撞(资产) | 隐式 / 粒子 / LevelSet | Convex / 60 / 5~10, 25~50 |
12. 给演示页的素材提示(可直接做交互/动画)
- Voronoi 逐步揭示:站点 → 中垂面裁剪 → cell →
PlaneCells配对(负侧/正侧)→ 同一个面被相邻两 cell 共享。可展示「CellIdx < NeighborIdx只建一次」。 - Boolean 管线动画:源网格 → ×cell_i Intersect → 孤岛拆分 → AppendToCollection(父变 Clustered,旧几何灰显=Visible:false)。
- 噪声面:平面多边形 → 细分到 PointSpacing → 沿法线 Perlin 位移(边界点完整 3D 位移)→ 自交修复 4 步二分。
- 工具画廊:Uniform / Radial(环×角参数面板)/ Cluster(球壳点)/ Brick 五种 Bond / Slice(X+Y+Z 平面数 vs 块数)/ MeshCut(Grid 摆放)。
- 层级与 AutoCluster:K-Means 迭代(初始中心=等步长抽样)→ 连通性拆分 → MergeSmall → 生成 ClusterBone;对比 ClusterMagnet 的逐圈 BFS 吸附。
- 邻接与凸包:Precise(顶点共享 / 共面反向三角形)vs ConvexHull(GJK 距离≤1);凸包重叠的切平面选择(候选 GJK 平面 vs 质心中垂面,交/并长度评分)。
- 规模化仪表盘:Brick 8192、Grid 200/轴、噪声 100 万顶点、线框 10 万 / 点 100 万、站点 UI 5000;以及 §10.2 风险表(标明「源码阅读」)。
附:未覆盖/需后续专题确认
- 运行时(Chaos Solver)如何使用 Proximity / ConnectionGraph / 凸包做簇破碎——属其它专题。
FMeshBoolean(GeometryProcessing)内部算法细节、FUVPacker/ExpMap 细节未展开。FractureToolEmbed/FractureEngineEmbed:FFractureEngineEmbed目前是空类(FX/Public/FractureEngineEmbed.h:6-12),嵌入几何逻辑仍在编辑器工具侧,未研究。- 所有性能判断均为静态阅读推断,未做实测。
