想象一下奶奶织毛衣:每一针是一个交叉结点,每一段毛线把两个结点连在一起。线绷紧了就互相拉扯,线松了就自然垂落。如果把整件毛衣拆开看,它就是由成千上万个结点和线段构成的“弹性网”。物理模拟里的布料,就是把这团数字毛线搬进电脑——把一块布拆成一个个“针脚”(质点),针脚之间用“毛线”(弹簧)相连。整块布飘动、垂下、被风吹起的模样,都是无数弹簧互相拉扯的结果。 而软体物理,则是往这件毛衣里塞了一团棉花:捏它凹下去,松手弹回来。布只有“面”,软体却有“体”,它必须守住自己的体积。本文就从一根毛线讲起:质点弹簧的骨架、胡克定律的脾气、一帧一帧推进的数值积分、不许穿模的碰撞约束,再到软体的压力模型、空间哈希加速、引擎调参和常见的抖动伪影。读完你会明白,游戏里飘逸的披风、果冻般的史莱姆,背后藏着的都是一套精细的“针线活”。
掌握布料与软体物理,等于拿到了游戏、影视、机器人仿真领域“让虚拟世界可信”的钥匙,是引擎动画与物理系统岗位的必备内功。
目录
可视化演示
展示质点弹簧布料网格受风力变形、Verlet积分约束求解与软体四面体碰撞变形,切换模式观察物理模拟。
本节配有交互动效(进入视口后自动加载,约 3KB)。
质点弹簧布料网格 结构弹簧约束相邻质点水平垂直距离 剪切弹簧约束对角线防止过度变形 F = -k(x - x0) - cv 胡克定律计算弹簧恢复力。
布料基石:质点与弹簧网络
织毛衣的第一步是起针。奶奶在针上排出一排针脚,每个针脚只需要记住两件事:我此刻在哪儿,谁在拉着我。布料模拟的第一步也是“起针”——把布面离散成一张网格,每个网格交点叫质点,相邻质点之间用一根弹簧拴住。质点负责记录位置、速度和“轻重”(质量),弹簧负责记住两个质点之间的初始距离,也就是“原长”。
这一步的灵感来自生活:整块布的行为,等于所有针脚互相拉扯的合谋。网格越密,布越细腻,但计算量也越大。最常用的拓扑是四边形网格:每个内部质点与上下左右四个邻居相连。不过四边形抗剪切能力差,一拉就斜——所以工程上会再加对角弹簧,像毛衣的加固线一样,防止布面“扯歪”。
质点弹簧模型(Mass-Spring)是整个布料模拟的基石,它的能量由每根弹簧的形变决定。弹性势能公式如下:
就像起针决定了毛衣的版型,质点和弹簧构成了布料的“骨”。针脚密,布细腻;针脚疏,布粗糙。接下来,我们要看看这些毛线到底是怎么用力的。
- 形变偏差弹簧当前长度与原长的差值,是回弹力的“证据”。
- 平方放大取平方后,拉伸和压缩的偏差都变成正值,能量永远不小于零。
- 弹性系数k 越大,同样形变储存的能量越高,布料越挺括,像粗毛线织的厚毛衣。
弹簧动力学:胡克定律与阻尼
毛线拉长后会回弹,但不会永远弹跳下去——因为空气和线自身的摩擦在“刹车”。弹簧的这一体两面,正是模拟的日常:回弹交给胡克定律,刹车交给阻尼。如果只有回弹没有阻尼,布会像装了永动机一样荡个没完。
胡克定律说:弹簧的恢复力与形变量成正比,方向与拉伸方向相反。k 是弹性系数,形变量越大,回拉力越猛。而阻尼项与速度成正比:质点动得越快,阻力越大,方向与运动方向相反。把两项叠在一起:
有了力,质点就会动。但“动”是连续的,计算机只能离散地推进——这正是下一节“时间推进器”要解决的事。
- 恢复力项形变量 x - x₀ 越大,回拉的力越大;负号表示方向指向原长。
- 阻尼项c 是阻尼系数,v 是两端质点的相对速度;负号表示与运动方向相反。
- 合力两项叠加得到弹簧施加在质点上的总力,再除以质量就是加速度(牛顿第二定律)。
数值积分:显式欧拉与 Verlet
织毛衣不能一步到位,得一行一行往上织。物理模拟也一样:把时间切成一小段一小段,每段叫一个时间步 Δt。每走一步,就根据当前状态推算下一帧。这个过程在数学上叫数值积分。
最直观的推进方法是显式欧拉:用当前速度直接推位置,用当前力直接推速度。但它有个著名的毛病——步长稍大就“爆炸”:位置越过平衡点太多,下一帧回弹更猛,最后整块布崩成雪花。布料模拟界更爱用 Verlet 积分:干脆不显式存速度,而是用前两帧的位置“外推”当前帧:
一针一针往前推,时间步就是“每一针的宽度”:针脚太宽,布就变形;太细,织得慢。但光有推进还不够——布不能穿过身体,这就轮到“版型规则”登场了。
- 惯性外推当前位置的两倍减去上一帧位置,等效于“保持原速度滑行”,不存速度也能隐含速度。
- 外力修正加速度 a 乘以时间步的平方,表示重力、弹簧力在这一步里的位移贡献。
- 完整更新惯性外推加外力修正,得到下一帧位置;这个格式天然对称,长时间模拟不容易飘。
碰撞与约束:投影的艺术
毛衣穿在身上,不能穿到身体里面去。道理简单,可数字世界里的“身体”和“布”都没有实体,谁都不拦着谁。于是物理引擎想出一个粗暴又聪明的办法:约束——规定某些关系必须满足,不满足就直接“掰”回去。
最典型的约束是距离约束:一根弹簧两端质点的间距要尽量保持原长。我们不调整力,而是直接计算误差,然后沿两点连线方向把位置修正到合法值。这种“直接改位置”的框架叫 PBD(Position Based Dynamics),是游戏引擎里最流行的布料方案。它的核心公式是约束修正量:
这就像织毛衣时随时对照版型修一修:哪里鼓了按回去,哪里紧了松一松——不纠结于力,直接改位置。碰撞检测也同理:质点穿透地面,就把它投影回地面上方。有了这套规则,布才真正“穿得住”。
- 约束误差两点当前距离减去原长 L,得到一个标量误差 C,正代表拉长了,负代表压短了。
- 修正方向单位向量指向分离方向,保证“拉长了就靠近,压短了就远离”。
- 平分修正误差由两个端点各摊一半,谁被固定住(pin)谁就不动。
软体物理:有限元与压力体
如果往毛衣里塞一团棉花,按压会凹、松手会弹——这就是软体。布是“面”,软体是“体”。软体不仅要像布那样抗拉伸,还要对抗一个更重要的东西:体积变化。捏一个果冻,它内部会被挤压,压强升高,然后把你的手指顶回去。
模拟软体有两条主流路线。有限元法(FEM) 把物体切成许多小四面体,逐个分析每个单元的形变与应力,精确但计算量惊人;压力体模型 则把物体内部当成一个气球腔,用气压抵抗挤压。游戏里常用后者:软体表面每个小面元都有面积 A,内部压强 P 沿外法线方向推出去:
就像棉花填充让毛衣立体,软体物理让虚拟物体有了“体积感”。一压一弹,都是压强在说话。从果冻怪物到软体机器人,这套模型撑起了半个“有弹性”的世界。
- 面元面积软体表面被切成很多小面片,A 是其中一片的面积。
- 压强推挤内部压强 P 乘面积 A,是这一片受到的推力大小。
- 外法线方向单位向量 n 垂直于小面元指向外部,保证力是“向外鼓”,而不是向内塌。
性能优化:空间哈希与 GPU 并行
一群织工同时织一件毛衣,得先分好片区:甲织左袖,乙织右袖,最后拼接。可要是所有人的手都能伸到同一个格子里,就打架了。布料模拟里最大的开销是碰撞检测:布料的每一点都要确认自己没穿进别的东西。朴素做法是每对质点都测一遍,复杂度 O(n²),一万个质点就是五千万次运算,直接卡死。
空间哈希 把空间切成均匀的正方格子,每个质点只跟自己所在格子和周围 9 个格子里的邻居比对。查邻居的平均成本从 O(n) 降到 O(1),总复杂度变成 O(n)。格子的坐标经过一个哈希函数,映射到一维数组:
除了空间划分,GPU 并行 是另一大杀器:质点和弹簧彼此独立,天然适合并行——每个线程管几个质点,一帧里跑好几轮迭代。再加上细节层次:远处用粗网格、近处用细网格。十万质点的披风,就这样在游戏里 60 帧飘了起来。
- 格子坐标用坐标除以格子边长 s 再向下取整,把连续位置变成格子编号。
- 线性编码按行主序把二维格子编码成一维索引,W 是每行格子数。
- 取模映射对哈希表长度 N 取模,把索引压缩到数组范围内,减少内存浪费。
引擎实战:稳定条件与参数调优
奶奶会按季节选毛线:夏天细棉线透气,冬天粗毛线保暖。引擎里的布料也有“线号”——一堆参数等着你调。Unity 的 Cloth、UE 的 Chaos Cloth 都暴露了刚度(stiffness)、阻尼(damping)、迭代次数、质量等旋钮。调之前,必须先懂一条“生死线”:显式积分的稳定条件。
如果时间步长太大,弹簧力会把质点推过头,回弹再推更远,最终爆炸。临界步长由质量和刚度决定:
有了这条公式,调参顺序就清晰了:先定刚度,满足手感;再算临界步长,不够就加密子步;最后微调阻尼,去掉余振。工程上还有个常用技巧:多次小步迭代——把一步拆成几个子步,既绕过稳定性限制,又保持效率。毛线选不好毛衣就织不好,参数配不好布料就变成爆炸粒子特效。
- 质量项质量 m 在分子上,越重的质点越“笨”,越不容易被一步推飞。
- 刚度项k 在分母上,弹簧越硬,临界步长越小,越需要“小步快走”。
- 开方与因子开方让量纲回到时间单位,因子 2 对应临界阻尼的边界。
常见伪影:抖动、拉伸与锁定
织得太松,毛衣领口越穿越大;织得太紧,缩水硬邦邦。布料模拟也有两个对应毛病:拉伸(超弹)和抖动(jitter)。拉伸来自约束太软或迭代不足:弹簧没被“掰”到位,布越拉越长;抖动来自步长太大或误差来回震荡:画面像筛糠。还有一个更隐蔽的问题——锁定:弯曲约束太强,布料硬得像纸板,怎么都不肯自然打褶。
约束投影的迭代收敛速度能解释很多现象:
知道毛病出在“松紧”,就能对症下药:加迭代、减步长、微调阻尼。若还嫌慢,用过松弛(over-relaxation)加速收敛,或者换 XPBD 消除过冲。一块平静妥帖的数字布料,就织出来了。
- 初始误差弹簧当前距离相对原长的偏离程度,是起点。
- 减半收敛投影法每轮迭代把误差压缩一半,k 轮后就是初始误差乘以 2 的负 k 次方。
- 工程含义想更平滑,把迭代次数从 3 加到 6;再往上性价比骤降,不如减小时间步长。
AI 应用场景
游戏角色披风的实时解算
阶段:游戏物理与动画管线
解决的问题:传统做法是美术逐帧 K 动画,角色转身时披风穿模、动作一改就要重 K。用质点弹簧 + PBD 自动模拟,披风每帧自己“飘”,不穿模且随动作自然响应。
具体案例:开放世界 RPG 主角的披风,在奔跑、跳跃、翻滚时都由布料解算驱动;后端跑空间哈希碰撞查询 + 少量迭代,宿主机的 60 帧预算内稳稳跑完。
影视级衣物离线解算
阶段:离线解算与缓存烘焙
解决的问题:电影角色裙摆每帧渲染费时,布料与身体碰撞稍有不慎就穿帮。离线场景追求精度,可以用高迭代 + 有限元软体,把裙摆的每一条褶皱算到满意为止。
具体案例:动画电影角色穿长裙行走,先在 Marvelous Designer 里打版,再导入 Maya nCloth 解算节点,CPU 集群一帧算几分钟到几十分钟,最后烘焙成动画缓存供渲染。
软体机器人抓取的仿真数据生成
阶段:仿真数据生成(具身智能)
解决的问题:软体机械手抓鸡蛋、豆腐等易碎物,真实实验成本高、难复现。用压力体 + 弹簧网格模拟软体抓手,批量生成抓取姿态数据,供强化学习策略训练。
具体案例:在 PyBullet 中搭建果冻质感的软体抓手模型,自动采样上万次抓取,收集力反馈和形变数据,训练出的策略再迁移到真实机械臂。
Python 演示
import mathimport random class Particle: def __init__(self, x, y, pinned=False): self.x = x self.y = y self.ox = x self.oy = y self.pinned = pinned def step(self, dt, gravity): if self.pinned: return vx = (self.x - self.ox) * 0.98 vy = (self.y - self.oy) * 0.98 self.ox, self.oy = self.x, self.y self.x += vx self.y += vy + gravity * dt * dt class Spring: def __init__(self, a, b, rest): self.a = a self.b = b self.rest = rest def solve(self, particles): p1 = particles[self.a] p2 = particles[self.b] dx = p2.x - p1.x dy = p2.y - p1.y dist = math.hypot(dx, dy) if dist < 1e-9: return diff = (dist - self.rest) / dist * 0.5 if not p1.pinned: p1.x += dx * diff p1.y += dy * diff if not p2.pinned: p2.x -= dx * diff p2.y -= dy * diff class Cloth: def __init__(self, cols=12, rows=9, gap=12.0): self.particles = [] self.springs = [] for r in range(rows): for c in range(cols): x = c * gap y = r * gap jitter = random.uniform(-0.3, 0.3) is_pinned = (r == 0) self.particles.append(Particle(x + jitter, y, pinned=is_pinned)) for r in range(rows): for c in range(cols): i = r * cols + c if c < cols - 1: self._add_spring(i, i + 1) if r < rows - 1: self._add_spring(i, i + cols) def _add_spring(self, i, j): a = self.particles[i] b = self.particles[j] rest = math.hypot(b.x - a.x, b.y - a.y) self.springs.append(Spring(i, j, rest)) def simulate(self, dt=0.016, gravity=500.0, iterations=4): for p in self.particles: p.step(dt, gravity) for _ in range(iterations): for s in self.springs: s.solve(self.particles) for p in self.particles: if p.y > 300: p.y = 300 p.oy = 300 def main(): cloth = Cloth(12, 9, 12.0) for frame in range(60): cloth.simulate() if frame % 10 == 0: xs = [p.x for p in cloth.particles] ys = [p.y for p in cloth.particles] width = max(xs) - min(xs) print("frame=%3d width=%6.2f bottom_y=%6.2f" % (frame, width, max(ys))) if __name__ == "__main__": main()
is_pinned = (r == 0)第一行质点固定,相当于把毛衣领口挂在衣架上,其余部分才能自由下垂。vx = (self.x - self.ox) * 0.98用“当前位置减上一帧位置”还原速度,0.98 是隐式阻尼,避免永动。self.y += vy + gravity * dt * dtVerlet 积分的位移更新,重力在这一小步里贡献一个二次位移。diff = (dist - self.rest) / dist * 0.5距离误差归一化,两个端点各摊一半修正量。for _ in range(iterations):迭代次数越多,弹簧越“听话”,布料越不容易拉伸。if p.y > 300:地面碰撞的暴力投影:不许穿到地板下面,同时重置上一帧位置防止抖振。
常见误区
误区布料不够挺括?那就多加弹簧,横的竖的斜的全加上,总该真实了吧?
正解弹簧过量会导致“锁定”(locking):约束互相矛盾,布料反而硬得像纸板,动都动不了。真实感来自恰当的拓扑和迭代次数,而不是弹簧数量。就像毛衣不是线越多越好,线乱成一团反而皱巴巴。
为何常见:初学者直觉认为约束越多越精确,忽略了约束之间会互相打架。
误区把弹簧刚度 k 调到很大,布料不就笔挺了吗?
正解刚度越大,稳定条件 Δt ≤ 2√(m/k) 越苛刻,显式积分更容易爆炸。工程上更常用“多次迭代 + 约束投影”来保持形状,而不是一味加刚度。现实中想把毛衣织挺,靠的是针法,不是把线勒断。
为何常见:胡克定律的直觉是“力大就有用”,但忽略了数值稳定性这条天花板。
误区Verlet 积分不用显式存速度,所以它天然稳定,步长可以随便给。
正解Verlet 只是比显式欧拉更宽容,步长过大同样会过冲、抖动,甚至爆炸。它还需要阻尼和碰撞处理配合。就像看脚印预测下一步,步子迈太大照样摔跤。
为何常见:把“无需速度”误解为“无条件稳定”,混淆了数值格式本身的特性。
误区布料模拟不就是给游戏角色做衣服吗?跟 AI 有什么关系?
正解布料和软体物理是游戏物理、影视特效、机器人仿真、具身智能数据生成的基础。软体抓手的仿真数据训练策略,靠的正是这套“弹性网”思想。毛衣能织衣服,也能织出果冻机械手。
为何常见:只看到“布料”的字面含义,没看到“质点弹簧 = 通用弹性模型”这个抽象内核。
面试题
Q1. 布料模拟中最基本的组成单元是什么?
- A.网格顶点与弹簧← 正确答案
- B.三角面片与材质
- C.刚体与碰撞盒
- D.光源与渲染贴图
答案:A。布料模拟的基石是质点弹簧模型:质点记录位置质量,弹簧记录原长和弹性。就像毛衣的针脚和毛线,两者配合才构成布。三角面片和材质是渲染层的概念,不是物理模拟的基本单元。
Q2. 胡克定律中弹簧恢复力与什么成正比?
- A.弹簧当前长度的平方
- B.相对原长的形变量← 正确答案
- C.质点自身的质量
- D.模拟的时间步长
答案:B。胡克定律 F = -k(x - x₀),恢复力正比于形变量(当前长度减原长)。形变量越大回弹越猛,就像毛线拉得越长回弹力越强。质量是 F=ma 中的角色,时间步长属于积分环节。
Q3. 显式欧拉积分在大时间步长下容易出现什么现象?
- A.布料逐渐变僵硬
- B.位置爆炸式发散← 正确答案
- C.布料变成完全透明
- D.纹理被无限拉伸
答案:B。显式欧拉用当前状态外推,步长过大时位置越过平衡点太远,回弹再推更远,形成正反馈爆炸。就像织毛衣针脚太宽,整件衣服越扯越大最后崩线。
Q4. Verlet 积分相比显式欧拉的主要优势是?
- A.无需显式存储速度← 正确答案
- B.完全避免碰撞穿透
- C.自动生成网格拓扑
- D.不需要消耗算力
答案:A。Verlet 用前两帧位置外推当前位置,速度信息隐含在位置差里,因此无需显式存储速度。它只是更稳定,并不能完全避免穿透,也照样消耗算力、需要手动建网格。
Q5. 空间哈希加速碰撞检测的核心思想是?
- A.只检测相邻格子内的对象← 正确答案
- B.用神经网络预测所有碰撞
- C.把布料烘焙成静态贴图
- D.每个质点单独检测全部
答案:A。空间哈希把空间切分成均匀格子,每个质点只检查所在格子及周围相邻格子里的对象,把 O(n²) 降到 O(n)。就像工厂分区流水线,工人只跟同区的同事打交道。
Q6. 约束投影公式中 C(x) 代表什么?
- A.质点在场景中的颜色值
- B.描述约束违反程度的函数← 正确答案
- C.弹簧阻尼系数的缩写
- D.相机空间裁剪面的编号
答案:B。C(x) 是约束函数,计算当前状态偏离目标值的程度。比如距离约束 C = |x_a - x_b| - L,数值越大说明布被拉得越远,需要修正得越狠。
Q7. 减少布料模拟抖动的常用手段包括?
- A.减小模拟的时间步长← 正确答案
- B.增加每帧约束迭代次数← 正确答案
- C.把弹簧刚度调到无穷大
- D.为速度增加少量阻尼← 正确答案
答案:ABD。抖动源于误差累积,减小步长、增加迭代、加阻尼都能压住误差。刚度调到无穷大会让临界步长趋近于零,反而更容易爆炸,是错误选项。
Q8. 显式积分的稳定条件 Δt ≤ 2√(m/k) 说明什么?
- A.弹簧越硬允许步长越小← 正确答案
- B.时间步长与弹簧刚度无关
- C.质点质量越小越稳定
- D.阻尼增大反而容易爆炸
答案:A。公式中 k 在分母上,k 越大临界步长越小;m 在分子上,质量越大越稳。这条公式是布料调参的定盘星,决定了“小步快走”还是“大步流星”。
Q9. 关于布料模拟中的“锁定”现象,正确的说法有?
- A.弯曲约束过强布料像纸板← 正确答案
- B.添加对角弹簧缓解锁定← 正确答案
- C.无限加密网格必然消除锁定
- D.锁定与约束迭代次数无关← 正确答案
答案:ABD。锁定是约束方向性导致的,约束过强或拓扑不当都会让布料变硬。对角弹簧能增加剪切自由度,缓解锁定。网格加密不一定消除锁定,甚至可能加剧;这是几何拓扑问题,与迭代次数无关。
Q10. PBD 中多个距离约束互相矛盾时,正确处理方式是?
- A.多次重复迭代直到收敛← 正确答案
- B.只应用一次约束就停止
- C.增大时间步长加速求解
- D.删除所有相互矛盾的约束
答案:A。互相矛盾的约束无法一次满足,PBD 采用 Gauss-Seidel 式多次迭代,让所有约束逐渐逼近平衡。只迭代一次或删约束都会失真,增大步长反而加剧震荡。这就像多位织工同时拉一根线,只能来回协调几次才能都满意。