Was macht VRM 角色动画?
此 Skill 源自一个同时支持 Live2D 和 VRM 角色的桌宠(见文末「来源与维护」)。用过的模型包括官方示例、VTube Studio 所用模型以及从 VRChat 转换而来的 VRM。下面的所有做法都经过实际测试、量化,或由用户审阅、修改过。
贯穿两种角色的三条核心原则
- 程序层和动作片段会相互覆盖。 每帧谁先计算、谁后写入,直接决定了用户最终看到的效果。几乎所有「闪烁一下」、「手放下了」、「头不动」的问题都源于此处:
- Live2D:在
beforeModelUpdate中写入的程序叠加值不会被物理系统处理,导致头发和身体不会跟随运动。解决方案是将程序叠加逻辑包裹在physics.evaluate调用之前。 - VRM:three.js 的
AnimationMixer在一帧中混合出的值与上一帧相同时,将不会再向骨骼写入值。这意味着每帧由程序设置的站立姿势和呼吸,会从动作片段「暂停」的地方显露出来。解决方案是跳过当前正在播放的动作片段所写入的骨骼(依据clip.tracks的节点名)。暂停时同理,需要停止程序并重写站姿。
- Live2D:在
- 不修改动作文件,在其后添加程序层。 手动 K 帧或 AI 生成的动作通常只操控少数几根骨骼(例如挥手只动胳膊),导致角色像木偶一样僵硬。正确的做法是在动作片段之后添加一层程序计算的「带动」效果(例如抬肩、躯干微微让位、手腕延迟、头部慢半拍),同时不叠加动作片段本身已经写入的骨骼。一次做好,所有动作都能受益,并且可以随时开关对比效果。
- 验证依赖逐帧数据记录,而非肉眼观察。 动画问题往往只持续一两帧,肉眼难以确认是否已修复。应逐帧记录目标骨骼(或参数)在渲染时的值(在 render 后,或 Live2D 的
beforeModelUpdate中读取),分别记录修复前后的数据进行对比;然后在两个不同的模型上截图确认。
何时阅读哪个参考文档
| 要做的事 | 阅读文档 |
|---|---|
| Live2D:加载、参数写入、物理、待机、动作、定格、表情 | references/live2d.md |
| VRM:程序姿势和动作片段、带动层、动捕片段、表情、弹簧骨骼、透明窗口 | references/vrm.md |
| VRM:动作配合表情、手型和手腕、手部接触身体 IK、打断动作后手部不下垂、腿部动作限制、眼睛跟随头部、关键帧插值与分段 | references/vrm.md 第 19–31 条 |
| 让 AI 生成或修改表情、动作 | references/ai-motion.md |
| 跟随音乐点头、说话时头部动 | references/live2d.md 第 16 条、references/vrm.md 「跟节拍」 |
最常见的陷阱(优先检查这几条)
| 现象 | 原因 | 解决方案 |
|---|---|---|
| Live2D 报错「Could not find Cubism runtime」或无法渲染 | 加载顺序错误 | Cubism Core → 注册渲染管线的模块 → PixiJS Application.init() → 加载模型 |
| Live2D 写入参数后读出 58、null 等异常值 | 直接传入了字符串 ID | 先使用 internalModel.getIdSafe(name) 转换为句柄 |
| Live2D 头部不跟随动作(VTube Studio 模型) | ParamAngle* 参数是物理系统的输出,写入后会被覆盖 | 读取 physics3.json,改为写入其输入参数 |
| Live2D 只有头部晃动,头发和身体不动 | 叠加逻辑放在了物理模拟之后 | 包裹 physics.evaluate,在其之前添加叠加逻辑 |
| Live2D 角色站立不动 | VTube Studio 模型没有像样的待机动画 | 程序化待机:让头、身体做三个慢速正弦波运动 + 呼吸,仅写入标准参数 |
Live2D stopAllMotions 后角色又自己动起来 | 引擎发现没有动作后,会重新播放 Idle 动画 | 将 motionManager.groups.idle 指向一个不存在的组,并停止 breath |
| VRM 手动 K 帧动作暂停时,手放下,首尾帧闪烁 | mixer 不写入未变化的值,导致程序化站姿暴露出来 | 程序化姿势应跳过当前动作片段写入了的骨骼 |
| VRM 一次性动作结束时,角色一帧跳回初始姿势 | clampWhenFinished=false 时,mixer 在 finished 事件前就关闭了 action | 设置 clampWhenFinished = true,然后再执行 fadeOut |
| VRM 动捕动作导致角色站得很宽,开始时会移动,裙子甩动过度 | 动作片段本身包含胯部和腿部骨骼轨道 | 转换时删除胯部和腿部的轨道,只使用上半身数据 |
| VRM 裙子穿透大腿,碰撞辅助线只看到一两个球体 | 碰撞体未注册到弹簧骨骼的碰撞组(three-vrm 只识别已注册的碰撞组) | 按照标准骨骼为胯部下方的链补充腿部胶囊体;避免胯部在一帧内跳跃(详见 references/vrm.md 第 15 条) |
| VRM 动作片段中的表情不显示 | 每帧的状态表情、眨眼、口型覆盖了片段中的表情 | 让动作片段只写入骨骼数据 |
| 表情在小尺寸下看不出来 | 表情文件只改变了很小区域 | 为每种情绪叠加额外参数(如眯眼、脸红、歪头、嘴角上扬) |
| VRM 做动作时脸上无表情 | 表情仅随状态变化,动作未携带表情信息 | 在动作旁添加 mood 和 beats 数据,根据动作权重混合表情(详见 references/vrm.md 第 25 条) |
| 执行闭眼、张嘴等动作时,模型自身的「困」、「笑」表情挡住了 | 表情的 overrideBlink / overrideMouth 是阻塞性质的 | 在执行这些动作的瞬间,临时压制这些状态表情(详见第 26 条) |
| VRM 单眼眨动看不到 | blinkLeft/blinkRight 未绑定形态键 | 用双眼一起眯起来代替单眼眨动(详见第 27 条) |
| VRM 一个动作打断另一个时,手先放下再重新抬起 | 新动作从站立姿势开始,交叉淡入时经过了站姿 | 从新动作中最接近当前状态的那一刻开始播放,并从打断的那一刻开始过渡(详见第 19 条) |
| VRM 动捕手指僵直或握拳;手动 K 帧动作手指一直半握 | 重定向的手指数据/动作不写入手指信息 | 移除动捕手指数据,在动作中写入手型(详见第 22、23 条) |
| 托腮、捂嘴、比心等动作,换个身材就无法准确落在目标位置 | 只编写了旋转角度 | 根据骨骼和网格实时计算目标位置,使用两节 IK 解算器(详见第 24 条) |
| 跳舞动作结束时,脚一帧跳回原位 | 腿部 IK 在淡出开始时便立即接管 | 让 IK 根据带腿动作的权重缓慢接管(详见第 20 条) |
| 膝盖半蹲、放松时腿部弯曲过多 | 多层胯部偏移叠加在一起 | 设置膝盖弯曲上限,减少二分之一胯部偏移;抬脚跟反而会导致更多弯曲(详见第 21 条) |
| VRM 眼睛一直死盯着镜头 | lookAt 功能对着镜头,转头时眼睛反而朝相反方向转 | 动作转头时让视线跟随头部、转头时眨眼、添加微小眼部运动(详见第 28 条) |
| 手动 K 帧动作一顿一顿,不流畅 | 每段关键帧都是两头慢,且每个关键帧的速率为 0 | 使用单调三次样条插值,并将各段关键帧错开(详见第 29 条) |
| 伸懒腰、举手时,手臂直直地从身体两侧扫上去/向两边平伸 | 缺少收拳、前臂折叠等中间阶段;没有将扭转和抬臂动作分开 | 分段重做,只在手臂到达门框高度时才执行扭转(详见第 30 条) |
| 闲置换重心时,上半身整个歪向一边 | 膝盖上限压缩了胯部的倾斜,但腰和胸部仍按完整的反向倾斜执行 | 将胯部未倾斜的部分补偿到腰部(详见第 31 条) |
来源与维护
- 来源: Cirra(AI 桌宠,RedCarpG/cirra-deskpet),2026-10 至 2026-11。
- Live2D 代码:
src/pet/live2d.ts、src/pet/live2d-physics.ts、src/pet/cubism-core.ts;渲染库untitled-pixi-live2d-engine1.4.0(PixiJS 8)。 - VRM 代码:
src/pet/vrm.ts、src/pet/vrm-motion.ts,2026-11-11 起还包括vrm-face.ts、vrm-hands.ts、vrm-blend.ts、vrm-reach.ts、vrm-gaze.ts;提交记录 2b6ab57(clampWhenFinished)、e5e8687(不使用 combineSkeletons)。 - 文档:
docs/stage5.md、docs/live2d-motions.md、docs/live2d-common-motions.md、docs/live2d-studio.md、docs/loop-motions.md、docs/expression-outfit-split.md、docs/music.md、docs/vrm.md、docs/vrm-motion.md、docs/vrm-motion-library.md、docs/vrm-studio.md、docs/vrm-toon.md、docs/beat-tracking.md、docs/vrm-skirt.md、docs/vrm-motion-polish.md。 - 经验总结:私人仓库中的
lessons/live2d-motion.md、lessons/threejs-vrm-animation.md。
- Live2D 代码:
- 源头: RedCarpG/redcarpg-skills 仓库的
skills/live2d-vrm-animation/目录。~/.claude/skills/中的是安装产生的副本,请勿直接修改。Cirra 项目中的代码是其来源,两者不会自动同步。 - 如何修改:
- 在 redcarpg-skills 仓库中修改,并提交 Pull Request;更新
manifest.json的版本号,通过node scripts/validate.mjs验证后发布(tag 格式为live2d-vrm-animation-v<版本>),然后重新安装本地副本。修改前请先查看上述两个 lessons 文件,确认是否有新条目可以收录。 - three.js、three-vrm、Live2D 渲染库升级后,需要重新验证「最常见的陷阱」表格中与库内部行为相关的条目(如 mixer 不写未变值、
clampWhenFinished、shouldRequestIdleMotion、加载顺序等)。 - 仅在单一模型上观察到的现象,需标明是哪种模型,切勿总结为通用规律。
- 在 redcarpg-skills 仓库中修改,并提交 Pull Request;更新
- 更新记录:
- 2026-11-12 · 1.0.0,迁入 redcarpg-skills 公开发布。修改了本地绝对路径,将来源项目改为链接;内容不变。
- 2026-11-10 · Cirra · 初版:三条核心原则、陷阱速查表;Live2D、VRM、AI 生成动作三篇参考文档;
evals/中的两个测试提示。 - 2026-11-10 · Cirra · 修改 VRM 第 15 条(裙子穿模):原来的「改成胶囊就好了」解决方案无效,改为碰撞组注册、通用腿碰撞胶囊、避免胯部跳跃三条(
docs/vrm-skirt.md);陷阱速查表新增一行。 - 2026-11-10 · Cirra · 第 15 条补充:长发进入袖子(手臂胶囊不能往回收)、衣服被顶出是蒙皮问题。
- 2026-11-10 · Cirra · references/vrm.md 新增「VRM 0.x 的规范化骨骼面朝 -Z」(程序姿势更换朝向时,避免转身抵消 rotateVRM0;提交 e2e6c0c,
src/pet/vrm-basis.ts)。 - 2026-11-11 · Cirra · references/vrm.md 新增第 19–28 条(打断动作时手不下垂、带腿片段淡出时 IK 接管、膝盖上限与抬脚跟、动捕手指换手型、手型与手腕、手部接触身体 IK、动作配合表情、override 阻塞 beats、空的单眼眨动、眼睛跟随头部),第 11 条改为指向第 25 条;ai-motion.md 新增 face、hands、reach 的格式和校验规则;陷阱速查表新增 9 行。来源:
docs/vrm-motion-polish.md§10、§11,src/pet/vrm-face.ts、vrm-hands.ts、vrm-blend.ts、vrm-reach.ts、vrm-gaze.ts、vrm-clip-extras.ts、vrm-pose.ts,sidecar/src/shared/motion-face.ts、motion-hands.ts(合并提交 b2f115d、ea3be9e)。 - 2026-11-11 · Cirra · references/vrm.md 新增第 29–31 条(关键帧每段两头慢导致动画卡顿 → 单调三次样条和错开关键帧;伸懒腰动作的分段和上臂扭转;膝盖上限压缩胯部倾斜导致上半身歪斜),陷阱速查表新增 3 行。来源:
docs/vrm-motion-polish.md§12,src/vrm-studio/motion-spec.ts的smoothAt、src/pet/vrm-pose.ts(合并提交 c5b4728)。 - 2026-11-11 · Cirra · references/live2d.md 第 21 条新增 VTube Studio 模型的两类排除规则(不处理物理输入参数、不带 Param 前缀的面部参数),否则面部表情会在点头时受到影响。来源:
src/pet/clash.ts的standardInputs、isStandardParam,docs/expression-clash.md(提交 7721742)。