技术博客

Chaos Cloth 布料:XPBD 的约束求解

第 48 / 100 篇 · 2026-07-04 · ue5 · Chaos物理引擎布料模拟源码分析

约束系统篇推导了 XPBD 的乘子更新公式。布料是这个公式最大的消费者:一块游戏里的斗篷,动辄几千个粒子上万条约束,每帧投影两轮。这篇把公式落到 UE 的布料实现上——数据怎么组织、编辑器参数怎么进求解器、钱花在哪。

从网格到粒子系统

Chaos Cloth 不吃三角面,它吃粒子图:导入的蒙皮网格按 Cloth 面板里的 Add Cloth Painted Vertices 选区,把顶点转成粒子(FClothingSimulationSolver::AddParticles),再把网格的边映射成约束。一个顶点在布料求解里是一个带质量的粒子,质量从顶点关联的骨骼权重反推——骨骼绑得死的区域(披风肩部)质量大、约束多,自由摆动的下摆质量小。重力、阻尼、风力(FClothingWind* 外部力接口)都作用在粒子层。

约束分两类:

  • Stretch(拉伸)约束:网格每条边一条,限制相邻粒子的距离。这是布料的"骨架",刚度极高(compliance $\alpha \approx 0$),几乎没有弹性;
  • Bend(弯曲)约束:隔一个顶点的粒子对(或二面角形式),限制折叠。弯曲刚度决定布料是"硬纸板"还是"丝巾",可调的余地很大。

每类约束在求解器里是独立的 FConstraint 子类(Chaos/Public/Chaos/Cloth/ 下的 StretchConstraints / BendingConstraints),各持有自己的约束数组,求解时逐个 Apply。

XPBD 在布料上的形态

约束系统篇的公式在布料里变得具体。对一条连接粒子 $p_i, p_j$ 的距离约束:

$$C(x_i, x_j) = \lVert x_i - x_j \rVert - d_0$$

$d_0$ 是静止长度。梯度 $\nabla_{x_i} C = \hat{n}$,$\hat{n}$ 是两点连线单位向量——投影方向就是"把两点沿连线拉开或压回",物理意义直白。XPBD 乘子更新:

$$\Delta \lambda = \frac{\lVert x_i - x_j \rVert - d_0 - \tilde{\alpha}\lambda}{w_i + w_j + \tilde{\alpha}}$$

分子里多了 $-\tilde{\alpha}\lambda$ 累积项,这是 XPBD 和"每条约束每帧清零重算"的朴素 PBD 的分界:乘子跨迭代保留,等价于隐式积分的弹性势能,布料在被拉扯时表现出真实的"越拉越硬"的渐进刚度,而不是每一轮都从零开始的无记忆修正。

布料求解的一帧就三件事:施加重力和外力 → 预测位置 → 对 stretch、bend 约束各做若干轮 Gauss-Seidel 投影(UE 布料默认迭代很低,个位数)→ 速度重建。迭代数是布料行为的第一旋钮:低了布会"软塌"、拉伸明显;高了CPU 成本线性涨。Chaos 布料敢用低迭代,靠的是 stretch 约束极硬时收敛极快——距离约束是最良态的约束,几轮就到位,难收敛的是弯曲约束和碰撞。

碰撞与自碰撞:钱花在这

布料和环境的碰撞走两层:披风对应的角色胶囊体(FCollisionPrimitive 系)先粗筛,三角网格部分(角色实际网格)做精细碰撞——把三角形转成隐式碰撞体,对布料粒子做球扫掠。这层很贵,通常只用于关键区域。

自碰撞(披风缠在自己身上)是性能黑洞,原因在数据结构:自碰撞需要一张粒子空间哈希表(spatial hash),每帧重建,然后对每个粒子查邻居、为靠得太近的粒子对动态生成碰撞约束。生成约束是分配行为——和刚体那套"约束数量相对固定"的假设相反,布料的碰撞约束数每帧都在变,cache 不友好、并行难做(动态约束要原子写)。Chaos 的做法是限制自碰撞候选(只在厚度参数内的粒子对生成约束)并给它单独的子迭代预算。

实践结论:自碰撞只开给视觉焦点布料(主角披风),NPC 的布料靠高弯曲刚度 + 禁自碰撞糊弄过去, Self Collision Thickness 调大反而能早点被粗筛干掉。

编辑器参数到物理系数的映射

Cloth 面板里几十个滑条,真正进求解器的通道很窄,值得记几个对应关系:

  • Stretch Stiffness (Horizontal/Vertical) → stretch 约束的 compliance 缩放,1.0 基本等于 $\alpha \to 0$ 的硬约束;
  • Bend Stiffness → bend 约束的 $\alpha$,换算关系是非线性的,经验值 0.2 以下是软布,0.5 以上接近皮革;
  • Damping → 速度重建后对 $v$ 的缩放,压的是高频抖动,代价是"水蛇感";
  • Drag / Lift → 风力项在法向和切向的分解系数,不是物理量,是手感参数;
  • Max Distance / Backstop → 不是约束,是蒙皮阶段把粒子锚回骨骼的运动学混合权重,粒子离骨骼超过 Max Distance 就被拉停。披风"穿身"的最后一道防线是它,不是碰撞。

最后一组尤其容易误解:很多人调不动布料穿模就去加碰撞迭代,其实 Max Distance 设小了根本没给布料自由摆动的余量,或者设大了等于没设。正确的排查顺序是先开 p.ChaosCloth.DebugDraw 系列把 Max Distance 可视化,再动求解参数。

源码入口

  • 求解器:Chaos/Public/Chaos/Clothing/ChaosClothSolver.h 的 FClothingSimulationSolver,看 Update 里的外力、预测、迭代三段;
  • 约束实现:Chaos/Cloth/ChaosClothConstraints*(stretch/bending/collision 各一个文件),看 Apply 里的 XPBD 更新;
  • 与角色的桥:ClothingSimulation.cpp 的 FClothingSimulation 接口,GT 侧把骨骼位置灌进来、把模拟结果写回蒙皮顶点。

粒子-约束-迭代的关系画在这张图里:

Chaos Cloth:从蒙皮顶点到求解的一帧 蒙皮网格 顶点 + 骨骼权重 粒子图 边→stretch/bend 约束 XPBD 迭代 低迭代 + 空间哈希碰撞 写回蒙皮 Max Distance 锚定 预算分配(实测感受) stretch/bend 投影:便宜,良态约束几轮收敛 自碰撞:贵,空间哈希重建 + 动态约束分配 排查穿模的顺序 1. 可视化 Max Distance / Backstop 2. 再动碰撞厚度与自碰撞开关