穿模是3D动画中最影响观感的问题之一。角色抬手时手臂陷进躯干、跑动时大腿穿透裙摆、披风边缘扎进地面,这些瑕疵一旦出现,再精致的模型和材质也会瞬间廉价感十足。穿模的本质原因很简单:动画骨骼驱动的是蒙皮顶点,而物体之间的空间关系并没有经过任何物理校验。解决这个问题的主流思路有两条,一是给关键部位挂碰撞体,用刚体物理挡住穿插;二是在顶点或骨骼层面施加排斥力,把靠近的几何体推开。本文围绕这两种方案展开,从原理、实现到调参逐一分析。

碰撞体方案:用简化几何体挡住穿插
碰撞体方案的核心思想是"以简驭繁"。直接用角色网格做碰撞检测代价极高,一个几万面数的模型做逐三角形的碰撞查询,性能开销几乎不可接受。因此通用做法是为角色的关键部位绑定一组简化几何体,通常是胶囊体、球体和盒子。比如躯干用一到两个胶囊体近似,上臂、前臂、大腿、小腿各用一个胶囊体,头部用一个球体。这样整个角色只需要十几个基本几何体,物理引擎处理起来非常轻量。
碰撞体的绑定方式一般有两种。第一种是直接把碰撞体挂到骨骼节点上,让它跟随骨骼一起运动,这是最简单也最常见的做法,在 Unity 中把 CapsuleCollider 挂到骨骼对应的 Transform 下即可,在 Unreal 中则可以用 Physics Asset(物理资产)为每根骨骼指定碰撞体类型。第二种是程序化驱动,每帧根据骨骼的世界矩阵手动更新碰撞体位置与朝向,这种方式更灵活,适合需要精细控制的场合,比如自研引擎或者需要动态调整碰撞体尺寸的场景。
// Unity:将胶囊碰撞体绑定到骨骼并每帧同步
public class BoneColliderFollower : MonoBehaviour
{
public Transform targetBone; // 目标骨骼
public Vector3 centerOffset; // 相对骨骼的偏移
private CapsuleCollider capsule;
void LateUpdate()
{
// 在动画更新后同步碰撞体位置
transform.position = targetBone.TransformPoint(centerOffset);
transform.rotation = targetBone.rotation;
}
}碰撞体方案的优点是稳定可控,物理引擎自带的接触求解非常成熟,不会出现顶点抖动。缺点也明显:胶囊体只是近似,手臂贴近躯干时轮廓误差会导致"该挡的没挡住、不该挡的挡住了"。因此实际项目中往往需要美术手动调整每个碰撞体的半径和长度,让它在动画幅度最大的动作下依然大致贴合。另外碰撞体数量要克制,一般整个角色控制在 15 个以内,否则物理查询和调试成本都会显著上升。
排斥力方案:把穿模的顶点推回去
排斥力方案更贴近问题本质:既然穿模是顶点穿透到了另一侧,那就直接检测顶点是否进入了碰撞体内部,如果在内部就沿法线方向把它推出来。这种方式对布料、头发、软体特别有效,因为这些物体不适合用刚体碰撞描述,顶点级别的控制才能得到自然的形变效果。
以裙摆为例,典型流程是:为大腿和小腿设置胶囊体碰撞体,布料解算器在每帧积分后遍历所有布料顶点,计算顶点到胶囊体中轴线的距离,如果小于胶囊体半径加布料厚度,就把顶点沿径向推到安全距离外。Unreal 的 Cloth 工具和 Unity 的 Cloth 组件都内置了这类碰撞处理,只需要在角色骨骼上配置 Capsule Collider 并勾选布料碰撞即可生效。
// 顶点排斥示例:将布料顶点推出胶囊体
Vector3 ClosestPointOnSegment(Vector3 a, Vector3 b, Vector3 p)
{
Vector3 ab = b - a;
float t = Vector3.Dot(p - a, ab) / ab.sqrMagnitude;
t = Mathf.Clamp01(t);
return a + ab * t;
}
void ResolvePenetration(Vector3[] vertices, CapsuleCollider cap)
{
Vector3 pA = cap.transform.TransformPoint(cap.center + Vector3.up * (cap.height * 0.5f - cap.radius));
Vector3 pB = cap.transform.TransformPoint(cap.center - Vector3.up * (cap.height * 0.5f - cap.radius));
float radius = cap.radius + clothThickness; // 碰撞半径加布料厚度
for (int i = 0; i < vertices.Length; i++)
{
Vector3 closest = ClosestPointOnSegment(pA, pB, vertices[i]);
Vector3 offset = vertices[i] - closest;
float dist = offset.magnitude;
if (dist < radius)
{
// 顶点在碰撞体内部,沿径向推到表面外
vertices[i] = closest + offset.normalized * radius;
}
}
}排斥力方案的关键参数有两个:一是布料厚度,控制顶点与碰撞面保持的最小间距,太小会穿插,太大则裙摆像被气球撑开;二是推挤强度或迭代次数,一次性硬推出可能导致顶点跳变,柔和的做法是按比例逐步修正,例如每帧只修正穿透量的 60% 到 80%,让效果更平滑。此外还可以加入摩擦系数,让布料贴着碰撞体滑动时有适当的阻力,避免裙摆在大腿上"打滑"得不自然。
工程落地:调参技巧与性能平衡
实际项目中,穿模处理往往不是单一方案,而是分层次的组合。骨骼级别的大幅度穿插(如手臂穿胸)用碰撞体加 IK 修正最划算;布料、头发这类细节用顶点排斥处理;而一些极端动作干脆交给动画师通过动捕后处理或关键帧手工修型解决,不要指望物理系统能覆盖百分之百的情况。判断标准很简单:如果穿模发生在刚性部位之间,优先用碰撞体;如果发生在软体与刚体之间,优先用顶点排斥。
性能方面有几个值得注意的点。第一,布料解算的顶点数量直接决定开销,可以在建模阶段把布料网格做成分辨率较低的独立网格,解算后再通过蒙皮或曲面细分映射到渲染网格上。第二,碰撞检测应按需启用,比如角色只在播放特定动画时才开启披风解算,平时直接播放烘焙好的动画序列。第三,合理利用物理引擎的碰撞矩阵,把布料碰撞体限定在角色自身骨骼层内,避免与场景中无关物体的碰撞查询。在移动平台上,这类设置往往能把布料开销压缩到可接受的范围。
最后是调试环节的建议。穿模问题的排查非常依赖可视化,务必开启物理调试绘制,让所有碰撞体在视口中显示为半透明线框,这样能立刻看出是碰撞体位置没跟上骨骼,还是半径设置不合理。调整时选取动画库中幅度最大的一段动作作为测试基准,比如挥臂过肩、深蹲、转身一百八十度,确保极端姿势下依然不穿帮。参数不要追求理论最优,视觉效果过关即可,毕竟穿模修正的一切目的都是让观众注意不到它的存在。
总结一下,碰撞体方案胜在稳定、适合刚性部位之间的遮挡,顶点排斥方案胜在细腻、适合布料和软体,两者结合再配合合理的性能预算与调试流程,就能覆盖绝大多数穿模场景。与其寄希望于某个万能插件,不如先理解这两种思路的适用边界,再根据项目类型灵活组合,这才是解决穿模问题最可靠的路径。