在3D服装制作中,如果只是用骨骼蒙皮让衣服跟随人体变形,很容易出现肩部拉扯、袖口僵硬以及大幅度动作时的穿模。真正自然的虚拟服装需要把网格看作可变形柔性材料,每一帧求解内部弹性力、外部重力和风力,以及服装与身体之间的碰撞约束,让顶点位置随时间演化。布料仿真通常从质点弹簧模型出发,再结合位置动力学与空间加速结构,在实时性与稳定性之间取得平衡。本文会围绕核心模型、物理参数和碰撞处理展开,并给出可运行的关键代码。

一、质点弹簧模型:用弹簧结构逼近柔性面料
质点弹簧模型是最直观的布料仿真方案。将服装网格的每个顶点看成一个带质量的粒子,每条网格边或者额外添加的边看成一根本构弹簧。仿真循环里,先对所有质点累加外力,再根据弹簧长度变化施加内力,最后更新速度和位置。弹簧力的大小通常与伸长量成正比,方向沿两个质点连线方向。
仅仅连接网格边还不够。三维空间中的面料需要抵抗拉伸、剪切和弯曲三种变形,因此实际实现中会建立三类弹簧。结构弹簧连接相邻顶点,保持面料的初始长度;剪切弹簧连接对角线顶点,防止网格被压成平行四边形;弯曲弹簧跨过一个三角面连接间隔顶点,抵抗折叠。三种弹簧的刚度通常不同,弯曲刚度远小于拉伸刚度,这样布料可以轻松弯折但不容易被拉长。
基础弹簧力公式为 F = k (|x_i - x_j| - L0) * n,其中 k 是刚度系数,L0 是静止长度,n 是从 i 指向 j 的单位向量。再加入阻尼项可以抑制振动。下面是一个简化的Python实现,展示质点和弹簧的受力计算。
class Particle:
def __init__(self, pos, mass=1.0):
self.pos = pos
self.prev_pos = pos
self.vel = [0.0, 0.0, 0.0]
self.force = [0.0, 0.0, 0.0]
self.mass = mass
class Spring:
def __init__(self, a, b, rest_length, stiffness, damping):
self.a = a
self.b = b
self.rest_length = rest_length
self.stiffness = stiffness
self.damping = damping
def apply_force(self):
delta = [self.b.pos[i] - self.a.pos[i] for i in range(3)]
dist = max((delta[0] ** 2 + delta[1] ** 2 + delta[2] ** 2) ** 0.5, 1e-6)
dir_vec = [delta[i] / dist for i in range(3)]
spring_force = self.stiffness * (dist - self.rest_length)
rel_vel = [self.b.vel[i] - self.a.vel[i] for i in range(3)]
damp_force = self.damping * sum(dir_vec[j] * rel_vel[j] for j in range(3))
total = spring_force + damp_force
for i in range(3):
self.a.force[i] += total * dir_vec[i]
self.b.force[i] -= total * dir_vec[i]
这个模型结构简单,容易理解,但显式积分对时间步长敏感,刚度较大时会出现数值爆炸。因此游戏和虚拟试衣中更常用的是位置动力学或者隐式积分,后文会详细说明。
二、物理属性如何塑造服装手感
同样一个网格拓扑,参数不同,呈现出的服装质感可能从丝绸变成牛仔布。拉伸刚度决定面料抵抗被拉长的能力,数值越高越不容易变形,比如皮革和牛仔布需要较高的拉伸刚度,而丝绸和雪纺的拉伸刚度较低。剪切刚度控制网格斜向变形,影响面料在斜裁时是否容易滑移。弯曲刚度决定布料能否轻松折叠,数值越低,褶皱越细密,越接近薄纱。
除了刚度,阻尼会影响布料摆动停止的速度。阻尼过小会出现来回抖动,阻尼过大则会让裙摆显得沉重。每平方米克重对应质点的质量,密度高的面料下摆惯性更大。摩擦系数决定服装与身体、服装层与层之间是贴合还是滑动,丝绸的摩擦系数低,容易滑落,棉麻则相对滞涩。
下表给出几类典型织物的参数范围,用在服装材质面板中能快速定调。
| 材质 | 拉伸刚度 | 弯曲刚度 | 密度 | 摩擦系数 |
|---|---|---|---|---|
| 丝绸 | 较低 | 很低 | 低 | 0.25 |
| 棉布 | 中等 | 中等 | 中等 | 0.45 |
| 牛仔布 | 较高 | 较高 | 高 | 0.60 |
| 皮革 | 很高 | 很高 | 很高 | 0.55 |
实际调参时通常不是单独调某一个值,而是以弯曲刚度为核心调整褶皱大小,再配合拉伸刚度防止面料被过度拉长。可以在编辑器里暴露一个材质预设接口,根据设计师选择的布料类型自动填充这些参数,再允许微调。
三、位置动力学与约束求解
显式欧拉方法在时间步长稍大或刚度较高时容易发散。位置动力学直接对质点位置操作,先把外力作用到位置上得到一个预测位置,然后反复求解距离约束,把顶点投影回满足约束的状态,最后根据位置差更新速度。由于它在每一帧通过迭代逼近约束解,即使刚度和步长较大,也不容易爆掉,非常适合实时服装仿真。
PBD中弹簧不再是力的形式,而是一个距离约束:C(p1, p2) = |p1 - p2| - L0。每轮迭代里,检查两个质点距离是否偏离静止长度,偏离多少就沿连线方向按逆质量加权移动两个顶点。约束刚度不再是胡克定律中的弹性系数,而是通过一个在0到1之间的修正因子控制,迭代次数也影响最终软硬程度。下面代码展示了距离约束的单次投影。
def solve_distance_constraint(p1, p2, rest_length, inv_mass1, inv_mass2, correction):
delta = [p2[i] - p1[i] for i in range(3)]
dist = max((delta[0] ** 2 + delta[1] ** 2 + delta[2] ** 2) ** 0.5, 1e-6)
diff = (dist - rest_length) / (inv_mass1 + inv_mass2)
corr = diff * correction
direction = [delta[i] / dist for i in range(3)]
for i in range(3):
p1[i] += corr * direction[i] * inv_mass1
p2[i] -= corr * direction[i] * inv_mass2
在实际服装仿真中,每帧通常迭代3到10次。如果只做1次,布料会显得很软;迭代10次以上,布料会明显变硬。对于弯曲约束,可以用相邻两个三角形的二面角来表示,求解时调整相邻顶点使二面角趋近静态角度,这样褶皱的分布会更自然。
四、碰撞与自碰撞:从穿模到自然褶皱
服装仿真中最容易被察觉的问题是穿模。身体可以简化成一组胶囊体或者一个有符号距离场,当布料顶点进入身体表面内部时,沿着梯度方向把顶点推出,并根据碰撞前的速度调整切向运动,结合摩擦系数决定滑动距离。这样肩部、胸部和肘部在弯曲时不会出现布料嵌入皮肤的现象。
自碰撞同样重要。裙摆多层折叠时,内侧和外侧的面料会互相穿过,产生视觉错误。常见的做法是为每个三角形生成包围盒,用空间哈希只检测附近三角形对,再对近距离三角形做点-三角形和边-边碰撞响应。为了性能,可以对顶点施加位置修正,增加厚度参数保留微小间隙,模拟面料本身的厚度。
风力和空气阻力用于表现走动或风吹时服装的动态。风力可以简化为沿风向对每个三角面施加与面积相关的力,空气阻力则抵消法线方向的速度分量。这些力与布料的质量、阻尼共同作用,让虚拟服装在运动时产生自然的摆动与滞空感。最终,把解算出的顶点位置与法线传给渲染器,配合纹理和光照,就能呈现接近真实的虚拟试衣效果。