OpenMontage 如何用 Ink Puppet 的 mocap 动作库驱动手绘角色动作动画
如果你的目标是让一个白纸上"铅笔线"风格的手绘角色做出走、跑、跳舞、踢腿、挥手这类动作,OpenMontage 的 Ink Theater 提供的做法不是手写关键帧,而是把真实动作捕捉(mocap)数据重定向到简笔画火柴人上。你只需要按名字从 mocap/catalog.json 动作库里挑选动作,拼进一条 GSAP 时间线,再通过 HyperFrames 渲染成 MP4。整条路径运行在 HyperFrames(atelier)之上,环境要求是 Node.js ≥ 22、PATH 中可找到 ffmpeg、npx,且 npx hyperframes doctor 退出码为 0(见 skills/core/hyperframes.md)。
Ink Puppet 的分工:谁写运动,谁挑动作
官方技能文档 skills/creative/ink-theater.md 把角色动画的规则定得很死:走 / 舞 / 挥手这类动作不允许手写运动(正弦曲线、手工摆帧都不行),正确姿势是"puppet + mocap 动作库"。角色部分由两个文件承载:
- ink-theater/ink-puppet.js —— 运行时:构建火柴人、播放 clip、暴露声明式编排 API。角色姿态是"每个片段局部时间的纯函数",天然 seek-safe。
- ink-theater/mocap/clips.js 与 ink-theater/mocap/catalog.json —— 动作库:由 ink-theater/mocap/bvh2clip.mjs 把 3D BVH 一次性离线转换成 2D 关节轨迹(hips 相对姿态 + 独立 root motion,缩放到固定身高)。
clips.js把全部 clip 挂到window.INK_CLIPS,choreograph从中按名字取 clip。
编排 API 只有三步,来自 ink-theater/README.md:
var p = InkPuppet.create(mount, { cx: 960, ground: 902, boil: "boil" });
p.drawIn(tl, { start: 0.4 }); // 铅笔逐肢把人物画出来
InkPuppet.choreograph(tl, p, [ // 然后按名字播放 mocap clip,零手调
{ clip: "walk" }, { clip: "dance_spin" }, { clip: "kick" }, { clip: "wave" }
], { start: 3.7 });
两个行为细节来自 ink-puppet.js 源码,排错时有用:每个片段以 clip 的原生 fps 循环播放来填满 dur(给片段加 loop: false 则播到最后一帧为止);clip 名在 window.INK_CLIPS 中不存在时不会报错,而是打印 [InkPuppet] unknown clip "..." 的 console 警告并列出所有已知 clip,该时长的姿态保持不动。
动作库现有 12 个动作,按名字挑选
完整清单就是 ink-theater/mocap/catalog.json 的正文,全部 CMU 来源(mocap/NOTE.md 说明这些 clip 免费用于研究与商用,BVH 取自 una-dinosauria/cmu-mocap 镜像):
| name | 类别 | 说明 | 帧数 | 来源 |
|---|---|---|---|---|
| climb | locomotion | clambers up | 180 | CMU 01_02 |
| dance_glide | dance | gliding ballet dance | 180 | CMU 05_10 |
| dance_spin | dance | expressive dance w/ a pirouette | 180 | CMU 05_02 |
| jump | action | a forward jump | 104 | CMU 13_11 |
| kick | action | kicks | 180 | CMU 10_01 |
| march | locomotion | marches on the spot | 180 | CMU 15_01 |
| run | locomotion | runs / jogs | 38 | CMU 09_01 |
| shuffle | locomotion | a slow sneaking creep | 180 | CMU 77_29 |
| sit | posture | sits then stands | 180 | CMU 13_01 |
| twist | dance | a twisting dance | 143 | CMU 141_12 |
| walk | locomotion | walks | 86 | CMU 02_01 |
| wave | gesture | waves hello | 75 | CMU 141_16 |
挑选规则在 README 和 skill 文档中重复强调:先读 catalog,为每个叙事节拍挑匹配的动作,并且永远不要循环同一条 clip(循环播放是画面显得重复的根源)。
一条最短可跑的组合
仓库自带可直接 lint 的参考组合 ink-theater/examples/mocap-figure/index.html:铅笔线自己画出火柴人,然后依次 walk / run / dance_spin / kick / sit / wave,全程真实 CMU mocap。在自己的项目里复用引擎,按 ink-theater/examples/README.md 的说法是把 ink-theater.js、ink-puppet.js、mocap/clips.js 和 assets/patrickhand.ttf 拷到项目根目录,然后按下面的顺序组织 composition。
1. 脚本加载顺序。index.html 中依次引入 gsap,再按 ink-theater.js → ink-puppet.js → clips.js 的顺序加载(顺序照抄参考 example 的 <script> 标签序列)。clips.js 必须在 choreograph 执行前加载,因为后者依赖 window.INK_CLIPS。
2. 手写字体必须内嵌完整字体文件。这是文档里标注的"真正的根因":从 Google Fonts css2 API 只取一个 woff2(grep … | head -1)拿到的是单个 unicode-range 子集,常常缺 basic-latin,导致所有英文静默回退成衬线体,而渲染器日志仍显示 Fonts: 1 loaded。正确做法是内嵌仓库自带的完整 TTF,并把字幕、台词气泡放在 HTML 覆盖层 <div> 上:
@font-face { font-family: "InkHand"; src: url("assets/patrickhand.ttf") format("truetype"); font-display: block; }
不要用 Google Fonts 热链——渲染时的网络请求会破坏 HyperFrames 的确定性契约。
3. 时间线注册与编排。一个 paused: true 的时间线必须注册到 window.__timelines["<id>"],id 与 composition 元素上的 data-composition-id 一致,参考 example 的完整写法:
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var pup = window.InkPuppet.create(document.getElementById("mount"),
{ cx: 960, ground: 902, boil: "boil" }); // boil 指向 SVG 里 feTurbulence 滤镜的 id
// 1) 铅笔逐肢画出人物
pup.drawIn(tl, { start: 0.4, each: 0.5 });
// 2) 按名字编排动作:每条 clip 以原生 fps 循环填满 dur
window.InkPuppet.choreograph(tl, pup, [
{ clip: "walk", dur: 2.6 },
{ clip: "dance_spin", dur: 3.4 },
{ clip: "kick", dur: 2.6 },
{ clip: "wave", dur: 2.6 }
], { start: 3.7 });
// 可选:台词气泡,文字走 HTML 覆盖层所以手写字体生效
// opts: into, overlay, at, dur, text, mouth:[x,y], center:[x,y], w, size, boil
// window.InkTheater.balloon(tl, { into: fxGroup, overlay: htmlOverlay, at: 5, dur: 2, text: "hello!", boil: "boil" });
// 3) 注册时间线(id 与 data-composition-id 对应)
window.__timelines["main"] = tl;
SVG 场景里需要一个 id="mount" 的 <g> 作为人物挂载点,以及一个带 feTurbulence 的 boil 滤镜供 InkTheater.boil(...) 驱动(参考 example 中的 <filter id="boil"> 定义)。
4. 确定性约束。这个引擎遵守 HyperFrames 渲染契约:闭式弹簧缓动、boil 由时间线上的分步 seed 驱动(而非 SMIL)、"随机"摆动全部走带种子的 PRNG、禁止 repeat:-1。写自己的编排时不要引入运行时随机数或渲染时钟,否则 seek 重放会不一致。
可选:给动作库加一条新动作
catalog 里没有想要的动作时,用自带转换命令扩展,不需要改代码:
node ink-theater/mocap/add-motion.mjs backflip 90_01 dance "a backflip"
第二个参数可以是 CMU id(如 05_02 或 005/05_02,脚本会从 una-dinosauria/cmu-mocap 镜像拉取 BVH)、一个 BVH 的 URL,或本地 .bvh 路径。要提前知道这个命令的副作用:它会发起网络下载,并在 ink-theater/mocap/ 目录内写入新 clip 的 JSON、重写 clips.js、更新 catalog.json(见 ink-theater/mocap/NOTE.md),所以请在你自己的仓库副本里运行。成功时打印类似 ✓ added "backflip" (dance) · <帧数>f · library now N actions 的确认行;之后把新的 clips.js 拷进你的项目。
两个已知的适配边界:新骨架若不被自动映射(脚本自动支持 fair1 / CMU / Mixamo),需要在 bvh2clip.mjs 的 ALIAS 表里补一条别名;投影平面不合适时可用 --axis xy|zy 指定。
验证与渲染
按 skills/core/hyperframes.md 的验证协议,渲染前要跑完整三步,lint 或 validate 失败就不要渲染:
# 内置示例可以直接这样 lint(注意指向含 index.html 的 composition 目录,
# 不要指向 examples/ 本身——linter 在找 index.html)
npx hyperframes lint ink-theater/examples/mocap-figure
# 自己的项目:lint(静态契约检查)→ validate(浏览器运行时检查)→ render
npx hyperframes lint
npx hyperframes validate
npx hyperframes render --quality standard
lint检查重复 id、轨道重叠、缺data-composition-id、时间线未注册等静态契约,必须先过。validate会真正 seek 进暂停的组合、截屏采样、核对window.__timelines注册;迭代期对比度检查可用--no-contrast延后,最终渲染前必须完整通过。render产出 MP4;npm 包名是hyperframes(不要使用@hyperframes/cli,该名称不在公开 registry 上)。
如果运行期看到 console 里 [InkPuppet] unknown clip 警告,对照 catalog.json 或 clips.js 里的名字修正——注意 clips.js 是动作库的打包产物,改了 catalog 后必须把新的 clips.js 同步进项目。
限制
- 手绘角色动画是
animation/character-animation管线上的"风格 + 引擎",不是独立管线,仓库里找不到对应的.yamlmanifest 是正常的(skill 文档明确标注 pipeline-exempt)。 - 动作库当前只有上面 12 条 CMU 动作,新动作必须走
add-motion.mjs或bvh2clip.mjs转换,没有"直接拖个视频进来"的路径。 - 台词气泡的文字依赖 HTML 覆盖层 + 内嵌完整字体,SVG 内直接写文字不会获得手写效果。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K637- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python330
cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端TypeScript2 K146
hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程Python46667
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go20043
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java33951