头发应该自然摆动,而不是变成触手
作者:Oleg Sidorkin,Cinevva CTO 兼联合创始人

角色转动头部。她的头发应该稍晚一点跟随,轻轻摆动,然后逐渐静止。但我们的第一个版本却表现得不一样:发梢向下卷曲,仿佛这款发型正试图变成一条触手。
骨骼长度保持不变。模拟也很稳定。我们关注的各项数值都通过了检查。但运动效果看起来仍然不对。
这是我们的自动绑定工具的下一项功能:为头发赋予独立关节和次级运动。我们先从一个简单的测试网格开始,然后转向两个真实虚拟形象。这既改变了我们的实现方式,也改变了我们的测量指标。
首先,重建一条真实的马尾辫
Seed-san 的马尾辫是一个独立网格。在这次测试中,我们清除了该网格原有的蒙皮权重,将其连接到头部,并让几何拟合器构建一条新的六关节头发链。角色的其余部分保留原始蒙皮。七条较短的刘海链也继续使用美术师制作的权重。
拟合器从发根开始,沿连通的头发表面向外追踪,在路径上放置关节,并混合邻近关节的影响。连接处有一小段区域会保持固定。这是一套选定发束的工作流:我们需要明确告诉它哪个网格是头发。它不会自动找出任意角色上的每一根发丝。
试试下面的马尾辫。 使用 头发运动,即可在同一个预览中切换早期和改进后的求解器。选择 移动后静止,观察头发如何逐渐停止。你可以暂停并检查某个姿势,也可以拖动预览来环绕查看。动画选择器提供全部 260 个 Universal 动画。选择一个片段即可查看全身运动;选择 取景 → 头发特写 可将镜头重新拉回发丝。选择 转头测试 则会返回受控对比。
我们从这些视图中移除了 Seed-san 独立的背包和机械臂,以免遮挡马尾辫。这项可见性调整不会改变身体、头发几何体、蒙皮权重或动画;它并不是对背包碰撞问题的修复。
试用 Universal 动画库
动画选择器使用的世界空间重定向器与 Cinevva 的绑定工具和运行时相同。我们将每个虚拟形象的 VRM 人形骨骼元数据映射到重定向器所需的关节名称,选择身体蒙皮而不是先前仅包含头发的蒙皮,并在头发演示开始前捕获身体的未动画姿势。这样,在选择其他动画片段时,系统就不会将已经转动的头部或蹲伏姿势误认为角色的静止姿势。
系统会从共享的 Universal 动画库获取所选片段,执行一次重定向,然后在选定角色上播放。为确保角色始终处于画面中,我们移除了水平方向的位移。在这些演示中,所有选中的 Universal 动画都会循环播放,包括原本标记为单次播放的动作。选择 重新开始 可重播当前动作。头发运动在身体动画之后运行。
我们为每个虚拟形象重定向了全部 260 个片段:共生成 520 个输出,没有缺失必要的身体关节,没有无效的数值轨道,也没有以头发关节为目标的身体动画轨道。我们还抽样检查了 Idle、Walk、Jump、Back Flip、Dance、Crouch Forward 和 Sitting Idle,核对身体运动和同步情况,并检查渲染后的姿势。这些检查证明重定向可用于这两个绑定,但不代表每一帧在视觉上都完美无缺。重定向检查结果。
服装也需要使用其原有的控制器。这个 VRM 示例使用瞄准约束和扭转约束来控制袖子和四肢变形;如果只驱动人形骨骼关节,袖子会停留在原始姿势。演示现在会通过官方 VRM 节点约束模块加载这些控制器,并在身体动画之后、头发模拟之前更新它们。这样可以保留模型自带的服装绑定,但不会额外添加布料模拟。
你可以检查完整的动画库,但下文的穿模和拉伸测量仍然使用受控转头序列。它们并不能证明头发在每个舞蹈、跌倒或翻转动作中都不会发生碰撞。
问题出在静止形状上
关节链需要有一个可以回归的方向。早期求解器会相对于上方已经弯曲的关节来计算这个方向。因此,顶部的一点轻微偏转会改变下一段的静止方向,而这又会继续改变再下一段的方向。
这种累积效应产生了卷曲。即使每根骨骼都保持正确长度,也无法阻止这一现象。
新求解器会在随头部移动的坐标系中记住发型原本的弧线。每一段仍然具有惯性和阻尼,但其恢复方向来自原始形状。头部可以转动,头发也可以滞后跟随;但这种滞后不会再变成下方所有发段的新静止曲线。
我们还对现有的美术师绑定略微提高了刚度,并增加了阻尼。因此,改进前后的对比不仅包含坐标系修正,也包含这些参数调整。
保留原始弧线的移动目标
对于第
每一步都会将朝目标位置的吸引力与重力和指数阻尼结合:
其中,
然后,我们将端点重新放回距关节固定长度
这一投影会保持骨骼长度。但它本身既不能保持每条三角形边的长度,也不能确保发丝始终位于服装外部。如果候选方向退化,实现会改用静止方向。
接着,试试完整发型
第二个虚拟形象是一个 pixiv 示例,拥有十二条头发链和 12,868 个头发顶点。在这里,我们保留美术师制作的关节和权重,只替换次级运动行为。这个示例展示的是现有发型的动画效果,而不是自动重建其融合在一起的头发网格。
请从侧面和背面观察发梢。改进后的版本在保留运动感的同时,更好地维持了预期轮廓。早期版本则会让发梢聚拢成更明显的卷曲形状。
真实模型还暴露了一个不太显眼的问题:一些短刘海的末端是一个本身不属于蒙皮骨骼的节点。我们此前会跳过这些关节链。现在,导入器会接受这类端点,并从 VRM 1 弹簧元数据中读取显式命名的头发链。它还会读取球体和胶囊体碰撞代理,以及每个关节的碰撞半径。
VRM 的弹簧骨骼规范提供了交换数据结构。我们的运动系统采用带有调优默认值的自定义求解器,而不是对每项 VRM 弹簧设置的完整实现。演示使用 pixiv 的 MToon 材质加载器来呈现原有的着色、纹理以及面部和服装描边。我们禁用了扩张后的头发描边外壳,因为它们会与这些较薄的分层表面相交。两种运动设置使用完全相同的光照和材质。pixiv 的 three-vrm 弹簧骨骼实现是该生态中成熟的参考实现。
为什么前侧发束会穿过衬衫
仅检查关节位置会漏掉一种明显的故障:即使两个关节端点都位于碰撞体之外,它们之间的发段仍可能穿过肩膀。我们还发现 VRM 半径处理代码中存在一个索引错误:读取下一个关节的半径会导致最后一段的半径变为零。
现在,两条前侧发束会沿每一整段执行碰撞检查。一个共享的拟合过程会测量现有碰撞体周围可见的身体和服装几何体,并根据身体尺寸限制扩张范围。系统通过发束的延伸范围、位置和蒙皮权重归属来判断哪些关节链需要进行分段检查。两个虚拟形象现在都使用这一算法,无需针对模型名称或发束名称编写规则。将碰撞检查扩展到整个发型会增加拉伸,因此头部连接约束和短刘海仍保留原来的处理方式。
我们还独立于这些代理碰撞体,对照实际蒙皮后的衬衫检查了结果。在一个转头周期内,使用早期运动时,衬衫最多会遮住 1,503 个前侧发束顶点中的 555 个。自动拟合后,无论每秒 30 帧还是 120 帧,超出 1 毫米测量阈值的命中数均为零。早期版本测得的最大穿透深度为 75.2 毫米。这是一次正面视角下的遮挡测量,并不代表实现了完美的表面碰撞,也不保证所有动画都不会穿模。下载前侧发束测量结果。
让发束保持在衬衫外部也有代价:分层发型的第 99 百分位拉伸率从碰撞拟合前的 17.1% 增加到 18.0%。它仍低于早期求解器的 26.0%;下表反映的是当前结果。
接触响应还需要单独修正:将发束推出服装时,这个位移此前会被转换为速度,导致下一步出现明显回弹。现在,我们会在不增加该冲量的情况下解决重叠,消除接触时朝向碰撞体内部的运动,同时保留滑动运动。一项静止发束回归测试会同时检查端点接触和完整发段接触:修正后的发束会停留在接触位置,而不是突然弹开。
沿发束检测接触,同时避免回弹冲量
头发连杆和胶囊体轴线都可以表示为线段。将它们的点分别写作
为避免将位置修正变成反弹,我们会在将端点推出接触区域之前重建速度:
发生非零接触修正后,令
切向运动会保留下来,因此发束仍可滑动。位置修正本身不会被加入速度。此后,求解器会重复执行接触投影,并移除沿连杆径向方向的速度;相互作用的约束仍可能留下残余重叠。
Back Flip 暴露了重建马尾辫上的另一处覆盖缺口:它独立的头发蒙皮不包含躯干骨骼。现在,碰撞体发现流程会搜索身体层级,而共享拟合器会在躯干覆盖缺失的位置添加一个躯干胶囊体。在每秒 30 帧和 120 帧下进行的六秒 Back Flip 检查中,都没有发现采样到的马尾辫连杆穿透该胶囊体。这是一项代理碰撞检查,并不能保证不会穿过每一个服装三角形;原有的转头变形检查仍然全部通过。
快速运动还需要检查姿势之间的状态。演示现在以每秒 120 个步长同步推进身体、服装约束和头发,并在这些步长之间检查发束与移动球体及胶囊体代理的碰撞。接触时仍会保留横向滑动,而不是让整条发束完全停止,否则头发会像粘住一样。在我们的四秒检查中,两个虚拟形象执行 Walk 和 Back Flip 时,在每秒 30、60 和 120 帧下都产生了完全相同的采样头发位置和速度。这可以处理采样后的基本体运动;它并不是针对每个服装三角形的连续碰撞检测,后续约束仍可能留下未解决的接触。下载帧率和接触检查结果。 这种一致性是有代价的。在之前以每秒 60 帧运行的双视图基准测试中,记录到的第 95 百分位更新时间,Seed-san 约为 1 ms,分层模型为 14–23 ms,均不包括渲染。这些开销对应两个角色,而现在的实时演示只渲染一个。分层模型仍需优化;这些桌面端测量结果并不能代表手机上的性能。
适配器仍然采用人体结构假设和有界扩展。当所需的身体部位角色或几何体缺失时,它会保留作者设定,并报告不受支持的变换。另有八项检查覆盖骨骼重命名、缩放变化、服装厚度、隐藏配饰以及恢复原始设置。这两个虚拟形象用于调优;在宣称结果具备普遍可靠性之前,仍需测试未见过的发型和服装。
测量网格,而不只是骨架
骨骼可以保持长度不变,但其周围的三角形仍可能被拉伸。因此,我们在一次十二秒的运行中测量了每条头发三角形边:前四秒为头部运动,随后八秒为回稳过程。模拟以每秒 120 步运行;网格测量则以每秒 30 次的频率采样。
对于每条边,我们记录它在整次运行中的最大拉伸量。下表报告这些逐边最大值的第 99 百分位数:99% 的边,其拉伸量都不超过该数值。
对于原始长度为
| 真实模型测试 | 之前的运动效果 | 改进后的运动效果 |
|---|---|---|
| Seed-san:重建的马尾加原始刘海 | 8.0% | 5.0% |
| Seed-san:原始头发绑定 | 7.6% | 5.1% |
| pixiv:原始分层头发绑定 | 26.0% | 18.0% |
最差的单条边仍然值得关注。在分层模型上,其拉伸量从 102.7% 改善到 70.8%——这仍是一个不能被百分位数掩盖的缺陷。Seed-san 的最差边在两种情况下都从 35.2% 改善到 27.8%。这些数据是在我们用于调优的开发示例上测得的,并不代表未见过的发型上的成功率。
链条根部保持固定。在最后两秒中,每个改进案例里,采样帧之间测得的末端移动量都低于 0.001 mm。十三项针对性测试覆盖绑定、固定根部、形状保持、碰撞、短端点、导入和动画控制权。我们还检查了在查看器中应用和撤销绑定,以及导出循环动画和单次播放动画。
下载测量数据。实时示例使用与原型查看器和导出流程相同的求解器代码。它们是交互式渲染,而非预录视频。
这能实现什么——以及还有哪些问题尚未解决
对于独立的马尾或发束,我们现在已经有了一条从选定几何体生成关节、权重和次级运动的路径。对于现有且兼容的头发绑定,我们可以保留美术师的成果并为其添加动画。当动画中已经包含这些头发关节的轨道时,程序化求解器会让位于作者制作的运动,而不是在其上叠加第二重运动。
头发数值基准测试使用头部转动和轻柔的身体平移。Universal 选择器允许读者查看其他动作,但这些测量结果并不能证明头发在奔跑、跳跃或翻滚时的效果。碰撞代理并不等同于完整的身体表面碰撞,我们目前也尚未处理头发的自碰撞。辫子、卷发、融合在一起的头发区域以及手机上的实际性能,都需要各自进行测试。
该功能仍处于实验阶段。但通过检查真实角色,我们明确看到了改进:头发可以运动,而不必不断改写它本应恢复到的形状。
模型署名。 Seed-san © VirtualCast, Inc.,来自官方 VRM 示例集合。VRM1_Constraint_Twist_Sample © 2022 pixiv Inc.,来自 three-vrm 的示例模型。两个文件都包含 VRM Public License 1.0 元数据,允许再分发和修改。重建的马尾和自定义动作是我们的改动。源文件网址与文件哈希标明了所使用的确切示例。
动画署名。 Universal 动画片段由 Quaternius 制作,来自 Universal Animation Library 和 Universal Animation Library 2,以 CC0 许可提供。
下一步:在生成式角色上复用头发
后续工作将从未绑定的素材库网格开始:把 46 种发型适配到一只青蛙上,并检查从中提取的发丝引导线。这个实时实验将头皮附着与尚待实现的物理模拟分离开来。