跳到主要内容
AIDEV CATALOGNOTE

用代码逐帧画一支 MV:PDoomVideo 全流程拆解

看到那支 Clawd 手绘水彩风的《I'm Upping My P(doom)》MV,第一反应通常是「这是哪个生视频模型做的」。答案是:没有生视频模型。整支 156.6 秒的片子,每一帧都是 JavaScript 用 p5.brush 一笔一笔「画」出来的,再用无头 Chrome 截图、ffmpeg 拼成 MP4。作者 JohnHeibel 让 Claude Opus 5.5 在 Claude Code 里完成了全部代码,仓库里没有一行人写的代码1。

这篇笔记把仓库完整拆开:从一首歌怎么变成分镜、分镜怎么变成并行 Agent 的任务、每一帧怎么被画出来、最后怎么合成视频。最后一章给出基于起步模板 ClaudeAnimationBase2 自己复刻一支 MV 的操作步骤。


一、先建立整体认知​

它不是「AI 生视频」,而是「AI 写动画程序」​

两条路线的区别决定了后面所有设计:

维度生视频模型(Sora / Seedance 等)本项目:代码逐帧绘制
产出物像素视频一段程序,运行后输出帧
可控性靠提示词抽卡每个角色、每个镜头、每个节拍都可精确修改
角色一致性易漂移同一个 clawd() 函数画出来,天然一致
卡点难BPM 写进代码,动作天然踩拍
画风取决于模型取决于画笔库(p5.brush 水彩 + 墨线)
代价生成快需要 Agent 写几千行代码,渲染按秒/帧计

所以这套方法的本质是:把 MV 当成一个软件工程项目,让 Claude 当导演 + 动画师 + 程序员。

成品规格​

项值
时长156.6 s
分辨率 / 帧率1920×1080 / 24 fps(约 3760 帧)
歌曲节奏88 BPM,一拍 0.682 s,第一拍在 0.21 s
结构9 个章节(chapter),每章一个 JS 文件,共约 6600 行
技术栈p5.js 2.x + p5.brush 2.x + puppeteer-core + ffmpeg

端到端流水线​

歌曲 mp3 + 歌词时间轴 (lyrics.js)
│
▼
① STORYBOARD.md ← Opus 写:故事主线、角色表、逐句分镜(时间/歌词/画面/转场)
│
▼
② ANIMATION_GUIDE.md ← Opus 写:给子 Agent 的「导演简报」+ 绘图 API 手册
│
▼
③ 共享底座 src/*.js ← core(画笔/时间/镜头)、clawd(角色)、cast、props、timeline
│
▼
④ 9 个并行子 Agent ← 每人只改 src/ch/cXX_*.js 一个文件,画自己那一章
│ ↺ 自查回路:render.mjs --sheet 渲染联系表 → Agent 看图 → 改代码
▼
⑤ studio.html ← 浏览器页面,window.renderAt(t) 画出任意时刻的一帧
│
▼
⑥ render.mjs ← puppeteer 开多个无头 Chrome 页,并行逐帧截图 → out/frames/*.jpg
│
▼
⑦ ffmpeg ← 帧序列 + mp3 → out/pdoom.mp4

二、制作过程:两轮生成​

仓库 README 说明,视频一共生成了两轮,都在 Claude Code 里完成1:

轮次模型 / 推理强度产物效果
第一轮Opus 5.5(Medium)legacy/flash-version.html,单文件约 1200 行Flash 风:扁平纯色块、粗描边、大量文字标签和气泡
第二轮Opus 5.5(xhigh)仓库其余全部内容水彩笔刷风、每句歌词有独立画面和转场

作者给的方向极少,第一轮只有两条:

  • 用 Clawd(Claude Code 吉祥物)的角色设计
  • 给每句歌词有趣的画面和转场

第二轮在第一轮基础上追加了几条修改意见:改用 p5.brush 笔刷、每个场景要更有看点、每个场景都要转场到下一个、减少文字。第二轮耗时约 45 分钟。

两轮对比说明了一件事:推理强度和「给不给明确的美术约束」对成品影响极大。ClaudeAnimationBase 的 README 也提到,推理强度越高,模型做出的场景越「铺张」、越注重细节2。


三、第一步:分镜 STORYBOARD.md​

分镜是整个项目的「单一事实来源」。所有子 Agent 都按它画,它决定了片子是「一部短片」还是「一堆片段拼贴」。

一个核心创意把全片串起来​

PDoom 的分镜第一段不是镜头,而是一个 idea:整支 MV 是一场失控的舞台剧。开头是剧场红幕拉开,结尾是同一块幕布落下;中间 Clawd 从显示器里的小涂鸦长成星球大小的超级智能,最后一句 "Was it all for show?" 镜头拉远——原来一切都是道具,巨型 Clawd 是三个小 Clawd 套的戏服,蛇怪是木偶,全员谢幕。

这个设计同时解决了三个问题:开头结尾押韵、各种梗之间有了统一世界观、有一个反转收尾。

全片级的规则​

分镜在逐镜头之前先写了一组「贯穿全片」的约束,这部分最值得抄:

约束内容
每个镜头都要「发生点什么」角色做事、东西坏掉、变形、追逐、坠落
少文字笑点靠画面和表演;全片只用几个大拟声词(FOOM、BOOM、CHOMP、SLAM)
场景而非卡片每章发生在一个地点,镜头在里面移动;四段副歌都回到同一个舞台,逐次升级
有动机的转场章节之间用笔刷擦除;章节内用动作带过剪辑点(咬到黑屏、火箭、坠落、穿过眼睛…)
情绪要演出来表情不能硬切:先挤眼、身体压扁拉伸、弹出小符号(汗滴、爱心、!),再睁开新表情
镜头永远在动每个镜头都有推、摇、倾、甩、拉远或踩拍震动
色彩弧线暖奶油灯光 → 天蓝 → 太空紫金 → 钢灰爵士蓝 → 机房青 → 警报红 → 回到剧场深红金
主角够大副歌舞蹈镜头里主角约占画面高度 40%
贯穿道具P(doom) 温度计:每段副歌 Clawd 用打气筒把数值往上打,8→34→61→86→99.9

逐镜头表格​

每章一张表,时间精确到 0.1 秒,和歌词对齐:

## 1 · The Lab (1.5–23) · night indigo, lamp ochre, monitor teal

| Time | Lyric | Shot | Out |
| --------- | ---------------------------------- | ------------------------------------------------------------------- | ------------------------------- |
| 1.5–3.6 | I see sparks of AGI… | 昏暗实验室,越过研究员肩膀看显示器,屏幕里小 Clawd 在睡觉,然后睁眼 | 镜头持续推进 |
| 3.6–5.9 | …in your eyes | 推到 Clawd 脸上,眼睛变星星,火花像烟花一样喷出屏幕 | 踩拍切 |
| ... | | | |
| 17.9–22.5 | ChatGPT, please don't eat me alive | 追逐:Clawd 头顶像饭盒一样掀开露出牙齿…… | **CHOMP**:嘴合上盖住镜头,黑屏 |

四列的意义:Time 让子 Agent 知道边界,Lyric 是内容依据,Shot 写「画面 + 事件」,Out 写这个镜头怎么接到下一个。Out 列是保证整体性的关键——它强迫每个镜头都想清楚出口。


四、第二步:导演简报 ANIMATION_GUIDE.md​

分镜写完后,Opus 要把 9 个章节分给并行子 Agent。子 Agent 是冷启动的,不知道前面讨论过什么,所以 Opus 写了一份简报,让每个子 Agent 读完就能独立开工。README 明确说这份文件是「Opus 写来给它并行运行的子 Agent 做简报的」1。

简报包含五块内容:

模块内容作用
章节契约文件结构、chapter() 注册方式、只准改自己的文件让并行不冲突
画布与布局1920×1080;底部 y 975–1070 是卡拉 OK 字幕区,关键动作放在 y 960 以上避免遮挡
绘图 APIpaint、inkLine、镜头、时间、角色、道具的完整手册子 Agent 不用读源码就能画
风格规则水彩 + 墨线、柔和饱和色、踩拍动作、一个焦点动作、性能预算统一画风
自查方法用 render.mjs --sheet 渲染联系表,用 Read 工具看图,反复迭代让 Agent 能「看见」自己的作品

并行开发的隔离规则​

这几条规则让 9 个 Agent 同时写代码而互不踩踏:

  1. 每章一个文件 src/ch/cXX_name.js,整个文件包在 IIFE 里,私有 helper 随便起名不会冲突。
  2. 只能改自己的章节文件。共享文件(core / clawd / cast / props / timeline / lyrics / studio.html / render.mjs)发现 bug 只报告,不修改;缺 helper 就在自己的 IIFE 里私有实现。
  3. 需要被终章谢幕复用的客串角色(蛇怪、shoggoth、龙猫等),挂到全局 CAST 对象上导出。
  4. 渲染用真实 GPU,多个 Agent 可以同时渲染检查。
// src/ch/c03_takeoff.js
(() => {
const gym = t => { ... }; // 私有 helper,不会和其他章节冲突
function stable(t, lt, dur) { ... } // 一个镜头
function singularity(t, lt, dur) { ... }
chapter('takeoff', 38.5, 59.0, [[38.5, stable], [41.5, singularity], ...]);
CAST.basilisk = (x, y, s, t, o = {}) => { ... }; // 只导出终章要复用的角色
})();

五、第三步:渲染引擎的核心机制​

这一章是整套方案能跑起来的技术底座,也是自己复刻时最需要理解的部分。

1. 每一帧都是时间 t 的纯函数​

这是最重要的一条约束。渲染时多个 Chrome 页面并行、乱序地画帧,所以:

  • 镜头函数签名是 fn(t, lt, dur):t 是歌曲时间,lt 是镜头内时间,dur 是镜头长度
  • 每次调用必须画完整一帧,包括背景
  • 不能有跨帧状态、计数器、逐帧积分的物理,禁止 Math.random()
  • 一切位置、角度、颜色都用 t 的闭式表达式算出来
// 一个镜头:t 相同,画面就必须完全相同
function labOver(t, lt, dur) {
const e = ease(lt / dur);
camBegin(lerp(960, 1030, e), lerp(540, 470, e), lerp(1, 1.32, e), 0); // 整个镜头缓慢推进
wall();
desk(700);
const s = monitor(1040, 455, 560, 450, { glow: 1 });
const md = mood(t, [[1.5, 'closed', 'zzz'], [B(4), 'normal', '!'], [B(5) - .15, 'look']]); // 睡 → 惊醒 → 张望
// ...
camEnd();
}

随机性分两种:

需求用法说明
固定随机(星星位置、书本高度)hash(i)同一个 i 永远返回同一个值
手绘抖动(线条「沸腾」)jit(a) / random()每秒重置种子 12 次(BOIL = 12),线条像手绘动画一样微微颤动

「沸腾(boil)」是手绘感的关键来源:draw() 开头执行 randomSeed(1000 + Math.floor(T * BOIL)),同一个 1/12 秒内的帧画出完全相同的抖动,下一个 1/12 秒换一组,效果等同于传统动画「一拍二」重描。

2. 时间轴:章节 → 镜头​

timeline.js 维护一个章节注册表。drawWorld(t) 每帧做的事:

找到 t 所在的 chapter
→ 在章节的 shots 里找到当前 shot,调用 fn(t, t - t0, end - t0)
→ flushLetters() 把排队的文字合成进画面
→ 如果本镜头没画温度计,在副歌窗口画角落小温度计
→ 如果 t 靠近章节分界点(1.5 / 38.5 / 73 / 109.4 s),画笔刷擦除转场
→ karaoke(t) 画底部卡拉 OK 字幕条

注意:转场、字幕、角落温度计都是 timeline 自动叠加的,子 Agent 只管画自己的镜头内容。

3. 音乐同步:一切踩在拍子上​

const BPM = 88, BEAT = 60 / BPM, OFF = 0.21;           // 第一拍在 0.21 s
const bpOf = t => (t - OFF) / BEAT; // 当前拍位置(浮点)
const beatN = t => Math.floor(bpOf(t)); // 第几拍
const pulse = (t, k = 6) => Math.exp(-frac(bpOf(t)) * k); // 每拍瞬间为 1,然后衰减

pulse(t) 是最常用的:乘到缩放、位移、震动幅度上,东西就会「每拍一跳」。舞蹈函数 move(style, t) 也基于 bpOf(t),所以所有角色天然踩拍。分镜里的关键动作也都写成拍点时间,例如第一章用 B(n) = OFF + n * BEAT 计算第 n 拍的时刻,CHOMP 合嘴就落在 B(33)。

4. 绘画封装 paint()​

所有图形都走 paint(pts, o),一次调用 = 一个形状,可叠加四层:

选项效果用途
wash平涂纯色角色、道具等必须清晰的东西
fill + bleed/tex/border水彩填充,边缘晕染、有颜料纹理背景、光晕、阴影
hatch排线干笔质感,少量使用
ink + sw墨线描边(一条连续变粗细的笔触)轮廓;ink: null 表示不描边

形状用点列表描述:rectPts、ellPts、rrPts(圆角矩形)、starPts、heartPts,都带抖动参数。禁止直接用 p5 的 rect() / ellipse() / fill()——那会画出 2000 年代 Flash 的感觉,第一轮失败正是这个原因。

5. 镜头、全屏特效和文字​

API作用
camBegin(cx, cy, zoom, rot) / camEnd()把世界坐标点放到屏幕中心,实现推拉摇移;只能一层,必须成对
shakeXY(t, amt)确定性震屏(按 24fps 取 hash)
flash(k)全屏闪白
iris(cx, cy, r) / irisShape(pts)圆形或任意形状的遮罩转场(例如嘴形、心形、钥匙孔)
letter() / sfx()文字和漫画拟声词,先排队,flushLetters() 时合成

6. 角色系统​

clawd(x, y, u, o) 画一个 Clawd,u 是尺寸单位(身体 10u × 8u)。所有能力都通过参数组合:

  • 姿态:dy、sq(压扁拉伸)、rot、flip、aL/aR(手臂角度)、walk
  • 表情:eyes(normal、spark、heart、swirl、shades…)、mouth、blush
  • 帽子:party、crown、halo、fedora、cat…
  • 表情符号:emote(sweat、heart、!、zzz…)
  • 钩子:draw 在身体坐标系画配件,armL/armR 在手尖画手持道具,道具自动跟随手臂
  • lid:头顶像饭盒一样掀开露出牙齿(CHOMP 梗)

两个关键辅助函数让表演不僵硬:

// 情绪切换:自动做「眯眼 → 压扁 → 弹出符号 → 睁开新表情」,不会硬切
const md = mood(t, [[0, 'normal'], [2.1, 'scared', 'sweat'], [3.4, 'happy', 'heart']]);
clawd(960, 860, 24, { ...md, mouth: 'smile' });

// 舞蹈:返回踩拍的姿态偏移
dancer(960, 860, 40, 'bounce', t, { eyes: 'happy' });

7. 一帧的合成顺序​

WEBGL 画布(p5.brush)
1. 纸张底纹(预生成,一次性)
2. drawWorld(t):镜头内容 → 文字 → 角落温度计 → 转场 → 字幕底条
│
▼
2D 输出画布 #out
3. 拷贝 WEBGL 画面
4. 叠文字
5. multiply 叠加纸纹颗粒 + 暗角(颜料「渗进」纸里的感觉)
6. 画卡拉 OK 字幕(逐词变色)
│
▼
window.renderAt(t) → toDataURL() → 交给 puppeteer

studio.html 同时是开发工具:直接用 Chrome 打开,底部有时间滑块可以拖动预览任意时刻;URL 带 ?render 时隐藏开发 UI 供无头渲染使用。


六、第四步:Agent 的自查回路​

模型读代码看不出动作好不好,所以简报要求每个子 Agent 渲染成图、用 Read 工具看图、再改代码:

# 联系表:一张图里放多个时刻的缩略图,同时打印每帧耗时
node render.mjs --sheet=38.6,39.3,40.1,40.9,41.6,42.3 --cols=3 --w=640 --out=out/check/c03_a.jpg
# 全分辨率单帧
node render.mjs --stills=39.5,43.2 --out=out/check/c03_full

检查清单:

  • 每个镜头的首帧、尾帧和中间几帧
  • 关键动作附近每 0.1 s 取一帧,看动作连不连贯
  • 本章的入场和出场转场
  • 重要内容有没有被底部字幕条挡住
  • 性能:每帧 ≤ 2.5 s,最多不超过 4 s;成本主要来自 fill 形状和笔画数量,几百个没问题,几千个不行

这个回路是「代码生成动画」能达到可看水平的关键:模型有了眼睛。


七、第五步:出片​

npm install
node render.mjs --frames=0:156.6 --workers=4 # 4 个页面并行画帧 → out/frames/f00000.jpg ...
node render.mjs --encode --out=out/pdoom.mp4 # 帧序列 + assets/pdoom.mp3 → MP4

render.mjs 的几个设计细节:

细节做法目的
并行开 N 个 page,共享一个待渲染队列,每页取下一个帧号利用纯函数特性乱序渲染
可续跑启动时跳过已存在且 > 1KB 的帧文件中断后继续,不用重来
原子写入先写 .tmp 再 rename中断不会留下半张图
编码参数libx264 -crf 17 -preset slow -pix_fmt yuv420p,音频 aac 192k,-shortest高画质、兼容主流播放器
快速预览--clip=0:6 通过管道边画边送进 ffmpeg调试短片段

八、自己动手:基于 ClaudeAnimationBase 复刻一支 MV​

PDoomVideo 是为这首歌定制的,很多地方写死了(时长 156.6、Chrome 路径是 Windows 的 C:/Program Files/...、GPU 参数是 d3d11)。作者后来把整条管线抽成了通用起步模板 ClaudeAnimationBase,自己做 MV 应该从它开始。

本章针对「已经有一首歌」的情况,步骤 2 的音乐准备必须人工完成。只想快速出片、不想找音乐时,改用 TTS 旁白 + 程序化 BGM 的方案,见 不找配乐也能出片:TTS 驱动的手绘动画短片手把手教程。

两个仓库的差异​

维度PDoomVideoClaudeAnimationBase
定位一支成片的源码通用起步模板
结构9 个 chapter 文件 + chapter()src/scenes/*.js + shots()
配置常量写死在 core.jssrc/config.js:PROJECT = { duration, bpm, offset, audio }
Chrome 查找写死 Windows 路径自动查找 Win / macOS / Linux 常见路径,或 CHROME_PATH
GPU 参数固定 --use-angle=d3d11按平台选择 d3d11 / metal / angle
Clawd基础表情 + mood()31 种情绪 feel() / emotions()、5 个转身视角 turn()、jump、stroll、spring 等表演函数
防抖全局 boil 种子boilSeed(key) 按元素分别设种子,避免运动物体导致静止物体乱抖
光效无glow() 加色光,解决「黄光叠蓝底变绿」的颜料混色问题
自查工具--sheet、--stills额外有 --strip(一段时间内逐帧)和 --crop(局部放大看细节)
指南章节分工简报完整规则:三大目标 + 7 条规则 + 动画原理 + 工作流 + 常见失败清单
歌词字幕有卡拉 OK 条默认不上字幕:歌词要「演出来」而不是写出来

前置条件​

依赖说明
Node.js跑 render.mjs
Google Chromepuppeteer-core 不自带浏览器,调用本机 Chrome
ffmpeg编码 MP4
独立 GPU(推荐)p5.brush 水彩填充依赖 WebGL;核显会慢到每帧数秒,作者建议此时让模型少用水彩 fill
Claude Code + Opus 5.5作者所有测试视频用 xhigh 推理生成2

步骤 1:跑通模板​

git clone https://github.com/JohnHeibel/ClaudeAnimationBase my-mv
cd my-mv
npm install
node render.mjs --sheet=0.5,3,6,9 --cols=2 --w=480 --out=out/check/a.jpg # 先出一张联系表验证环境
node render.mjs --clip --out=out/video.mp4 # 渲染 11 秒 demo

用 Chrome 打开 studio.html 可以拖动预览;加 ?loop=emotions 或 ?loop=views 查看角色设定图。

步骤 2:准备音乐素材​

MV 相比普通短片多出来的工作都在这一步,而且必须人来做准(模型听不见音乐):

  1. BPM:用任意测速工具或 DAW 测出歌曲 BPM。
  2. 第一拍偏移 offset:第一个重拍出现的秒数(PDoom 是 0.21 s)。offset 错了,所有舞蹈都会「抢拍」或「拖拍」。
  3. 歌词时间轴:每句的开始、结束秒数。可以从带字幕的视频里抄,或者用 Whisper 之类的工具出时间戳再人工校对。格式照抄 PDoom:
// 歌词时间轴:[开始, 结束, 歌词]
const LY = [
[1.5, 5.9, "I see sparks of AGI in your eyes"],
[6.0, 7.9, "Your circuits make me nervous,"],
// ...
];
  1. 段落结构:标出前奏、主歌、副歌、间奏、尾奏的时间范围。副歌重复是设计「同一场景逐次升级」的天然素材。

然后改配置、放音频:

// src/config.js
const PROJECT = { duration: 156.6, bpm: 88, offset: 0.21, audio: 'assets/song.mp3' };

步骤 3:让 Claude 先出分镜,不写代码​

最小提示词(模板 README 里的示例)2:

Read ANIMATION_GUIDE.md, then make a 15-second video of Clawd trying to catch a butterfly.

做 MV 建议显式要求「先分镜,确认后再写代码」,并把音乐信息一次给全:

读 ANIMATION_GUIDE.md。我要为 assets/song.mp3 做一支完整 MV。
- 歌曲 BPM=88,第一拍 0.21s,时长 156.6s,歌词时间轴见 src/lyrics.js
- 段落:前奏 0–1.5,主歌1 1.5–23,副歌1 23–38.5,……
- 主角用 Clawd;每句歌词要有独立、有趣的画面,每个镜头都要转场到下一个
- 用 p5.brush 笔刷,不要文字标签,歌词含义靠表演而不是字幕
先只写 STORYBOARD.md:一个贯穿全片的核心创意、角色表、色彩弧线、贯穿道具、
按章节分表(时间 / 歌词 / 画面与事件 / 出场转场),每个镜头列出观众需要看懂的 reads 及其时间。
写完停下来给我看,不要写代码。

拿到分镜后对照这张清单审一遍(来自模板指南):

  • 每个镜头都有「事件」吗?首帧和尾帧之间有变化吗?
  • 每个 read 都有足够时间被看懂,而且重要的 read 没有重叠吗?
  • 每个接缝都有转场吗?
  • 有没有文字?
  • 结尾和开头押韵吗?

步骤 4:共享底座 + 并行子 Agent​

分镜确认后,复刻 PDoom 的分工方式:

按 STORYBOARD.md 实现:
1. 先由你自己补齐共享层:新角色、贯穿道具、场景道具放进 src/ 的共享文件,并补一个 chapter 级的时间轴(按章节时间分发镜头)。
2. 写一份给子 Agent 的简报(章节契约、画布布局、可用 API、风格规则、自查命令),追加到 ANIMATION_GUIDE.md 或单独成文。
3. 按章节拆分,每章一个 src/ch/cXX_name.js,开子 Agent 并行实现。每个子 Agent 只能改自己的文件,共享文件有 bug 只报告不修改。
4. 每个子 Agent 必须用 render.mjs --sheet / --strip 渲染并查看自己的镜头,迭代到满意为止。

为什么要先建共享层再并行:角色、道具、转场、时间轴如果让每个子 Agent 各写一份,画风和比例一定不一致;共享层先定死,并行时每个人只负责「演戏」。

步骤 5:总验收与出片​

所有章节完成后,自己(或让主 Agent)做一次全片验收:

# 每章抽首、中、尾帧,看整体节奏和色彩弧线
node render.mjs --sheet=0.5,10,22.8,23.2,30,38.4,38.6,50 --cols=4 --w=480 --out=out/check/all_a.jpg
# 转场接缝逐帧检查
node render.mjs --strip=38.2:38.8 --cols=6 --w=320 --out=out/check/seam_38.jpg
# 出片
node render.mjs --frames --workers=4
node render.mjs --encode --audio=assets/song.mp3 --out=out/video.mp4 # --encode 不读 PROJECT.audio,必须显式传

只有 --clip(一条命令直出 MP4)会自动读取 PROJECT.audio。


九、边界、代价与常见坑​

这套方法的代价​

  • 渲染慢:水彩 fill 是性能大头。PDoom 的预算是每帧 ≤ 2.5 s,3760 帧单线程就是 2.5 小时,所以需要多 worker 并行和续跑。实际速度取决于 GPU:在 Apple Silicon Mac 上实测,PDoom 成片约 170~400 ms/帧,Base 模板 demo 约 80~200 ms/帧,远低于预算。
  • Token 与时间:第二轮约 45 分钟;Donald Jewkes 在此基础上接入 fal 生图、Seedance 生视频作为底层素材、再用 JS 重绘覆盖,耗时约 12 小时。
  • 音乐理解靠人:BPM、offset、歌词时间轴、段落划分,模型都拿不到,必须人提供准确数据。
  • 画风上限受画笔库限制:p5.brush 擅长绘本水彩风,写实风格不适合走这条路。

常见坑​

坑表现解决
用了 Math.random() 或跨帧状态并行渲染后画面闪烁、跳变一律用 hash(i) 和 t 的闭式计算
静止物体乱抖前面有运动物体消耗了不同数量的随机数,导致后面全部重新「沸腾」每个独立元素前调用 boilSeed(key)(Base 模板提供)
光晕发脏发绿p5.brush 按颜料混色,黄叠蓝变绿发光物用 glow()(加色),或用满不透明度 wash 覆盖
画面文字太多招牌、标签、气泡里写字,看起来就是 AI 做的分镜阶段就禁止;情绪用符号(!、汗滴、爱心)表达
节奏太赶所有事件一个速度堆在一起,观众没看懂就结束分镜里写 reads 并给每个 read 时间;Base 指南说这是「模型最常出错的地方」
主角太小大场景里一个小不点,全片看不清表演中景 u≈20–28,特写 u≈40–70,副歌舞蹈约占画面高 40%
表情硬切前一帧哭后一帧笑用 mood() / emotions() 做带预备动作的切换
PDoom 在 macOS 上跑不起来Chrome 路径写死为 Windows;只传 --chrome 仍会报 Error creating webgl context,因为 --use-angle=d3d11 在 Mac 上不可用把 render.mjs 里的 --use-angle=d3d11 改成 --use-angle=metal,再传 --chrome="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"(已实测);或直接用 Base 模板
--encode 出的视频没声音--encode 只认命令行 --audio=,不读 PROJECT.audio显式传 --audio=<音频路径>,或改用 --clip
无 GPU / 核显每帧数秒甚至更慢让模型少用水彩 fill,改用平涂 wash

十、实践建议​

把这套方法迁移到自己的 MV,优先级是:

  1. 先把音乐数据做准:BPM、offset、歌词时间轴决定了所有卡点,这一步错了后面全部白做。
  2. 先有核心创意,再有镜头:PDoom 好看的根本原因是「失控的舞台剧」这个统一世界观,而不是某个镜头画得多精细。让模型先提 2–3 个核心创意,挑一个再展开分镜。
  3. 分镜是最便宜的修改点:在分镜阶段改一句话,比在代码阶段改一个镜头便宜两个数量级。分镜不满意就不要进入编码。
  4. 用 xhigh 推理 + 明确的美术约束:第一轮 Medium、无约束的结果是 Flash 风;约束(笔刷、少文字、转场、每镜有事件)加上高推理才有成片质量。
  5. 先做一段副歌验证:完整 MV 成本高,先拿 15–20 秒的一段副歌走通「分镜 → 实现 → 自查 → 出片」全链路,再铺开到全曲。
  6. 让模型「看」:不管是否并行,都要求每个镜头渲染联系表并查看。这是这套方法和「让模型盲写动画代码」之间的最大差别。

参考资料​


  1. JohnHeibel — PDoomVideo · GitHub · 2026,成片见 YouTube↩
  2. JohnHeibel — ClaudeAnimationBase · GitHub · 2026↩