纯代码做一集东方视频

这是一个模板。整集科普视频没有一张外部图片,也没有 AI 生图:剪纸风的魔导书、十四个角色、手写字、配音和背景音乐,每一笔都是 JavaScript 在 Canvas 上画的,最后逐帧渲染成 MP4。

Touhou, Drawn in Code — an open-source template for making a voiced Touhou Project fan explainer video in nothing but JavaScript and Canvas 2D. Every stroke traces back to a line of code; no image assets, no AI-generated art.

コードで描く東方 —— JavaScript と Canvas 2D だけで、ボイスと BGM 付きの東方二次創作解説動画を作るオープンソーステンプレート。画像素材も AI 生成画像も使っていません。

十四位角色在左右两页各站七位,头和身子一样大,前十位是近三年东方人气投票的前十名。这一段是模板自带的演示场景,写在 src/scenes/1-cast.js 里。

画面是时间的函数

引擎的核心只有一条规则:每一帧只由全片时间 t 决定。不存状态,不用 Math.random(),要「随机」就用带种子的 hash。同一个 t,永远画出同一张图。

所以可以跳到任意一秒,可以把全片切开并行渲染,机器再慢,成片也不会掉帧。下面的月牙在时间轴上的位置,就是一行关键帧插值:

const u = key(tau, [[1.2, 0], [S1T(2), 0], [S1T(2) + 3.5, 1], [S1DUR, 1]], easeIO);
drawMoonIcon(c, lerp(x0, x1, u), y - 62, 44, P.moon, -.5 + u * 3);
同一个场景在 2、5、8、11、14、17 秒的六帧画面,月牙沿时间轴从左走到右
同一个场景在 2、5、8、11、14、17 秒的画面(src/scenes/2-time.js)。拖到哪一秒,画面就是哪一秒的样子。

剪纸的边,手绘的抖

画面的质感来自两件小事。剪纸:边不是曲线,是一段一段略带折角的直线,再垫一层软影。手绘:线每秒换八次种子,看起来像在呼吸。两件事都只有几十行代码,不依赖任何素材。

左边是数学上的圆,右边是剪刀剪出来的纸;下面两根线,上面那根固定种子,下面那根每秒换八次。

十四个角色,同一套 Q 版比例

博丽灵梦、雾雨魔理沙、芙兰朵露、古明地恋、魂魄妖梦、十六夜咲夜、蕾米莉亚、古明地觉、藤原妹红、琪露诺,这十位是 2024–2026 年三届东方人气投票前十的全部成员;再加上帕秋莉、红美铃、八云紫、八云蓝。每个角色都是一组剪纸部件,绕着脖子、肩、肘转动,边缘不抖,「活」来自整片轻晃和投影。全员是约 2.4 头身的 Q 版,头和身子一样大,只有帽子和发饰高低不同,并排站才整齐。

十四位 Q 版纸片人并排站立,脸和身子一样大,下面写着名字
全部是 Canvas 代码画的,没有加载任何图片。样张页 character.html 可以看每个角色的全部姿势和表情,?sheet=lineup 就是这张合照。

十个现成的特效

从前五集里提炼出十段最好看的动画,每段是一个独立的模块,头注释写了出处、好在哪、怎么复用、怎么改。挂进一集只要一行:

fxScene({ id: 'nap-fall', lines: LINES, over: (c, tau, L) => { /* 叠上自己的角色 */ } });
fxPlay('pinwheel-tiles', c, tau, { box: [x, y, w, h], t0: 1.2, speed: 1.5 });
打盹掉进深睡
第 4 集 小睡段
冰块按风车形铺满蓝格板
第 5 集 风车段
半透明冰板叠层
第 2 集 巩固段
昼夜转盘与纸雕灯箱视差
第 1 集 睡眠段

看全部十个,每个有联系表和说明 →

声音也是代码

配音走一条很土的链:汉字转拼音,拼音转日文假名,假名交给 AquesTalk 合成,就是「油库里」那种腔调。同时从音频里算出每一帧的响度,让角色按真实的音量张嘴。背景音乐是浏览器里现场合成的一首八音盒小曲。

汉字 → 拼音(pinyin-pro)→ 假名 → AquesTalk → MP3 + 响度包络

出片

出片脚本开几个无界面浏览器,把全片切成几段。每个浏览器逐帧调用同一个 renderAt(t),导出 JPEG,直接通过管道交给 ffmpeg,不落盘。几段编码完拼接,再混上离线渲染的声音。

97 秒的演示片,在一台 MacBook Air(M5)上用 4 个并行页面渲染,用了约 56 秒。想要 4K,加一个 --scale 2,建议放到自己的电脑上跑。

展望

我们把这个仓库当成两件事的试验台。在艺术这一边,它是用代码当画笔的同人创作:剪纸的折角、手绘线的呼吸、八音盒改编的角色曲都是可以读、可以改的规则,一位画师的风格可以写成一份部件表,像乐谱一样被别人演奏。在研究这一边,它是一份可复现的视觉程序语料:画面完全确定,「模型写的动画好不好」可以落到具体的帧和具体的行,适合拿来考察编码 agent 的视觉程序合成、长任务规划和多 agent 并行协作。这个仓库本身就是人和多个 AI agent 一起写的。

接下来:补齐人气投票 11–16 名的角色,加日文和英文配音,让 agent 从一份方案文档独立做完一整集,把角色样张和规格整理成可以引用的数据集。给 agent 读的入口是仓库里的 AGENTS.md 和 llms.txt,引用格式见 CITATION.cff。

常见问题

完全不用图片吗?是。角色、背景、字、特效都是 Canvas 代码画的,页面不加载任何美术素材,也没有 AI 生图。

为什么是这十四个角色?其中十位是第 20–22 回「東方Project人気投票」(2024–2026)人妖部门三届前十的全部成员,三届是同一批人;另外四位来自「帕秋莉讲座」系列。

要会画画吗?不用,要会一点 JavaScript。角色是部件表,改颜色和轮廓就是新角色;场景是只依赖时间 t 的函数。

能交给 AI agent 写吗?能,这个仓库本身就是这样做的。先让 agent 读仓库里的 AGENTS.md。

渲染要多久?97 秒的演示片,在 MacBook Air(M5)上开 4 个并行页面约 56 秒。

开始

git clone https://github.com/fish2lab/touhou-code-video-template
cd touhou-code-video-template
npm install && npm run font
open index.html          # 浏览器直接看
npm run render           # 出片,需要 ffmpeg

或者在 GitHub 上点 Use this template。然后读 教程:从「.mjs 是什么」讲到逐帧出片的原理;再读 经验:什么让一集耐看。