· 6 分钟阅读 · 2143 字
软绳多迭代几次就变硬:XPBD 累计量必须活过整个子步
一根静长 1 米的软连接,预测后的两个端点相距 2 米。一次位置修正后,距离变为 1.333333 米;如果多迭代九次,距离应继续趋近 1 米吗?对于指定柔度的 XPBD 距离约束,单独这一条约束在本例中已经达到离散方程的解。继续收紧会改变材质响应,不能用“求解更充分”来解释。

本篇用一维两粒子模型隔离这个差别。两个粒子质量各为 1 千克,逆质量均为 1;静长为 1 米,柔度为 0.01 秒平方每千克,子步时长为 0.1 秒。模型只做预测位置之后的约束投影,不执行重力积分、速度回写或完整绳索模拟。这样,改变迭代次数时出现的差别可以直接追到求解状态,而不会混入积分器和碰撞的影响。
柔度方程里还保留着上一轮修正
设端点坐标 a 小于 b,约束误差为 C=b-a-rest。正误差表示连接被拉长,梯度对 a 为 -1、对 b 为 +1。令柔度为 α、子步时长为 h、累计乘子为 λ,缩放柔度为 α̃=α/h²。每轮更新使用:
Δλ = (-C - α̃ λ) / (wa + wb + α̃)
a ← a - wa Δλ
b ← b + wb Δλ
λ ← λ + Δλ
本例 α̃ 等于 1,第一轮 Δλ 为 -1/3,两端各向内移动 1/3 米。新的 C 为 1/3,λ 为 -1/3;第二轮分子的两项恰好抵消,因此不再修改位置。非零的伸长正是柔度允许保留的形变。把 C 单独作为收敛指标,就会把合法的软约束平衡误判成求解不足。
求解后满足的是 C+α̃λ=0。在这条梯度恒定的一维约束里,第一轮就是闭式结果。代入更新式可得 C新=C旧+(wa+wb)Δλ,与新的累计量合并后残差为零。多约束系统则可能在后续处理邻接边时再次改变端点,仍需多轮迭代;单条约束已经平衡,不意味着整张约束图已经收敛。

若每轮都把 λ 清零,更新式就丢掉抵抗继续收紧的那一项。固定输入中,每轮误差都缩小为前一轮的 1/3,十轮后只剩 1/3^10 米。迭代预算从一轮改成十轮,实际上把同一条软连接收紧了四个数量级以上。美术或玩法调好的柔度因此会随着质量档位、迭代预算改变,求解器配置也就偷偷成了材质配置。
累计量属于约束实例,重置属于子步入口
研究目录 .tmp/daily-labs/2026-11-04-1930/ 的 DistanceConstraint.cs 保留完整实现。Program.Main 通过 BeginSubstep → Solve 建立一次求解生命周期;只有子步开始调用重置,迭代循环对同一个实例反复求解。
public sealed class DistanceConstraint
{
public double Lambda { get; private set; }
public void BeginSubstep() => Lambda = 0;
public bool Solve(ref double a, ref double b, double wa, double wb,
double rest, double compliance, double dt)
{
if (!double.IsFinite(a) || !double.IsFinite(b) ||
!Valid(wa) || !Valid(wb) || !Valid(rest) || rest == 0 || !Valid(compliance) ||
!double.IsFinite(dt) || dt <= 0 || b <= a)
throw new ArgumentException("Invalid constraint input");
if (wa == 0 && wb == 0) return false;
double alpha = compliance / (dt * dt);
double denominator = wa + wb + alpha;
if (!double.IsFinite(alpha) || !double.IsFinite(denominator) || denominator <= 0)
throw new ArgumentException("Unrepresentable constraint scale");
double c = b - a - rest;
double delta = (-c - alpha * Lambda) / denominator;
double nextA = a - wa * delta, nextB = b + wb * delta;
double nextLambda = Lambda + delta;
// 整体校验后提交,错误输入不留下部分更新。
if (!double.IsFinite(nextA) || !double.IsFinite(nextB) ||
!double.IsFinite(nextLambda) || nextB < nextA)
throw new ArgumentException("Invalid constraint result");
a = nextA; b = nextB; Lambda = nextLambda;
return true;
}
private static bool Valid(double x) => double.IsFinite(x) && x >= 0;
}
这个类型只接受严格有序的端点和正静长,拒绝重合端点;三维距离约束在零长度处也需要另外指定方向策略。两端逆质量为零时跳过,避免把无法移动的连接当成可修复误差。一个静态端点则正常参与求解,全部位移由另一个端点承担。实现先计算候选,再整体校验提交,非法尺度或结果不会留下只移动一端的状态。
λ 必须归具体约束所有,不能在求解每条边时新建临时实例,也不能让整根绳的所有边共享一个数。静长、柔度和逆质量在一次子步的迭代过程中必须保持不变,修改这些参数后要重新开始求解。它记录的是当前约束在当前子步中已经施加的修正;外层调度器负责子步边界,内层循环负责迭代次数。本实现每个子步从零开始,不支持跨步热启动。如果要复用历史乘子,需要另外定义时长变化、约束身份与预测状态的转换合同。
子步变短,不等于同一方程多算一遍
保持相同预测位置,把 h 从 0.1 秒改为 0.05 秒,α̃ 从 1 增为 4,单次残余伸长变为 2/3 米。更短的物理时间允许同一柔性连接产生更小的位置修正。这个结果比较的是同一预测状态下的离散投影;它没有证明整段动态模拟在不同子步数下具有完全一致的轨迹。
真正的多子步还会重新积分外力、推进位置并更新速度。柔度参数可保持材料含义,但时间离散、碰撞处理和有限迭代依然会影响结果。若传入的是整帧时长,而实际每个子步只推进四分之一帧,α̃ 就会小十六倍,连接会被错误地调硬。求解入口应接收真实子步 h,不能从渲染帧计时器里自行取值。
α 为零时,公式退化为刚性距离投影;正柔度时,不能把“距离误差必须归零”的刚性停止条件直接照搬过来。若需要诊断收敛,应检查柔度残差与邻接约束的相互影响,再决定是否增加迭代。λ/h² 可以在相应离散模型中解释为约束力尺度,但这里没有做速度重建和力学标定,不把它包装成完整引擎的受力输出。
用残差和质量中心同时约束结果
2026-11-04 在 .NET 9 Release 下运行独立 C# 程序。Check 验证固定结果、累计量重置、半时长、静态端点与双静态跳过;Reject 验证负数、非有限数、零静长、非法时长与不可表示尺度,并断言拒绝后位置和乘子未变。
cd "$WORK_DIR"
dotnet build .tmp/daily-labs/2026-11-04-1930/DistanceLab.csproj -c Release --no-restore
dotnet run --project .tmp/daily-labs/2026-11-04-1930/DistanceLab.csproj -c Release --no-build
build: exit=0, warnings=0, errors=0
run: exit=0, passed=27, failed=0, skipped=0
elapsed_ms=7.5998
first_residual=0.3333333333333335
retained_10=0.3333333333333335
wrong_reset_10=1.6935087808533922e-05
half_dt_residual=0.6666666666666665
random_count=10000
maxResidual=3.1086244689504383e-15
maxCenter=2.842170943040401e-14
maxIter=3.552713678800501e-15
固定种子 71104 的 10,000 组输入将起点限制在 [0,10)、初始间距在 [1,5)、正静长在 [0.2,1)、逆质量在 [0.1,3.1)、柔度在 [0,0.05)、时长在 [0.01,0.1)。每组检查 C+α̃λ、质量加权位置和 a/wa+b/wb 的守恒,以及再迭代五轮的距离变化。后者只有质量不变时才能直接用作质量中心守恒的等价检查;静态端点单独验证,不放进除以逆质量的统计。
这些是数值正确性结果,耗时没有引擎或 GPU 性能含义。证据由当前核心源码、固定与随机回归及当天运行输出共同组成,尚未覆盖三维方向变化、闭环绳索、多约束耦合和碰撞。
绳索求解器可以把迭代预算作为收敛资源,但前提是约束实例保留本子步的累计量。柔度、子步时长与迭代状态分别属于材料、时间推进和求解器,三个入口一旦混在一起,降低画质预算就可能同时改掉角色能够拉多长绳子。应先修复这个状态生命周期,再讨论增加迭代或细分子步。