定格动画视频
使用本技能制作的视频运动方式如同随歌曲逐帧拍摄的定格动画。每个姿势保持一个八分音符的长度,然后所有元素一起变化。页面在小节线处进入。每次点击音效都与鼓点同步,而重大变化则发生在重击鼓声上。帧率人为保持较低水平。当某个动作看起来粗糙时,应更好地间距其姿势;绝不会为了平滑而抹去节拍。
这些规则源于为一个169.825 BPM的鼓打贝斯音轨制作47秒启动影片,并经过数百轮笔记迭代。影片以30fps运行,每个姿势保持一个八分音符,即每秒5.66个姿势,每个姿势占用5或6帧。这里的数值是影片的实际值,而在 live.md 文件中则是播放该影片场景并配合节拍器的网页的数值。当规则可以泛化到这些具体数值之外时,文本中会说明。
词汇表
- 姿势 (Pose):一个静止的画面,对应歌曲的一个八分音符。在代码中称为
beat(STEP_MS,useBeat)。 - 歌曲节拍 (Song Beat):BPM 计数的单位,即一个四分音符。它相当于两个姿势的长度,并对应一次节拍器点击。
- 小节 (Bar):四个歌曲节拍,八个姿势。在鼓打贝斯音乐中,重击鼓(kick)落在姿势 0 上,军鼓(snare)落在姿势 2 和 6 上(从 0 开始计数)。
- 敲击 (Hit):歌曲中的一种鼓声。一个重击鼓 (Big Kick) 是开启一个乐句的鼓声(在影片的音轨中,每个奇数小节开始处)。
- 创作毫秒 (Authored ms):代码中的时间。它们基于一个 200ms 的姿势编写,并以
SPEED倍的速度播放(SPEED = TEMPO / 150)。 - 时间戳 (Timestamps):以
m:ss.ff格式编写,其中ff是帧计数。这是 Remotion Studio 显示时间的方式,也是笔记到达的格式。
操作姿态
- 有意保持低帧率。 永远不要为了修复某个动作而抹去节拍。应改改善该动作姿势的间距。
- 所有元素保持一个姿势一拍并同时变化,包括转场。没有任何东西运行在自己独立的时钟上:嵌入的动画或滚动页面也必须跟随姿势的节拍。打字是唯一的例外:它比节拍运行得更快,正如打字本身那样,但从其对应的姿势开始。
- 歌曲是时钟。 在绘制故事板之前先测量歌曲。将页面放在歌曲的小节线上,并将动作放在歌曲的敲击点上。
- 整个视频共用一个时钟。 在视频的帧上计数姿势,绝不在某个场景内部计数。
- 按整个小节移动。 通过整个小节来改变页面长度,或者在下一个小节线之前将多出的姿势退回到别处。否则,此后的所有内容都会与歌曲错位。
- 没有空闲的姿势。 一旦某个页面上发生的事情完成,只需保持足够长的时间让观众理解即可。
- 变化种类少,方向单一,无淡入淡出。
- 通过测量来检查。 使用每个姿势一张的静帧、在渲染音频中测量的时间,以及一个节拍器剪辑。不要仅凭肉眼判断同步。
工具:默认使用 Remotion
建议使用 Remotion 来制作视频,除非项目已经以其他方式制作视频。在开始故事板之前说明这一点,并给出理由,只有在用户要求使用其他工具时才更改。如果是在网页上,则改为实时播放(live.md);在动态图形工具中,规则仍然适用,但辅助工具不再适用。
- 为什么它很合适。 每一帧都是一个根据帧号计算得出的 React 组件,因此一个姿势会精确地落在它该落的帧上,并且每次渲染结果相同。渲染时帧是乱序绘制,横跨多个标签页,这只有由帧驱动的视频才能胜任。
assets/remotion/中的辅助工具是为它编写的,其 Studio 以m:ss.ff格式显示时间,这正是笔记到达的格式。 - 设置它。 使用
npx create-video@latest命令和空白模板。将remotion及所有@remotion/*包锁定到一个精确版本(影片使用的是 4.0.532),并将 TypeScript 保持在 5.x 版本(clock.md §7)。一个合成,1920×1080 分辨率,30fps。将beat.ts和transitions.tsx复制到src/目录,并设置TEMPO。 - 配置它。 在
remotion.config.ts中:JPEG 帧质量设为 95(默认值 80 会使纯色背景和细线边缘变得粗糙),BT.709 色彩空间(以便播放器显示的颜色与浏览器一致),并启用覆盖写入。 - 预览和渲染。 使用
npx remotion studio进行工作;使用npx remotion render <Comp> out/<name>.mp4渲染 1080p 视频,添加--scale=2参数可渲染 4K;使用--frames=<first>-<last>或npx remotion still <Comp> --frame=<n>来检查某一段或某个姿势。
决策顺序
从上到下进行。每一步都为下一步设定约束。
- 建议工具。 使用 Remotion,除非项目已有其他制作视频的方式;在进行任何其他操作之前,按上述方式设置它。
- 测量歌曲。 通过歌曲的起始点找到节奏,并检查这个节奏网格在你使用的片段内是否稳定。标记小节和乐句,标记鼓声消失和回来的地方,以及重击鼓的位置。将音乐片段精确裁剪到音频样本的开头,在乐句第一个重击鼓前约 23ms 处。参见 song.md。
- 设置时钟。 一个姿势是歌曲的一个八分音符。基于一个 200ms 的姿势进行创作,使用
SPEED = TEMPO / 150,以 30fps 渲染,并基于整个视频的帧来计数姿势。参见 clock.md。 - 将页面放置在小节上。 以姿势为单位编写故事板。每个页面包含整数个姿势,并从小节线开始。在制作任何动画之前,将歌曲的事件映射到姿势上。
- 放置敲击音效。 代码以 2 到 4 个字符为一组的方式在八分音符上输入,标题和按住退格键的操作从其对应的姿势开始,比节拍运行得更快。点击和爆裂声放在军鼓上,着陆和新页面放在强拍上,重大变化放在重击鼓上。在音乐安静的部分进行构建,在鼓声回归时进行着陆。参见 hits.md。
- 间距姿势。 为每个动作曲线选择其姿势。一个动作的第一个姿势就是它的第一步,每个姿势都使其可见地移动。事物应该“着陆”而不是“爬行”。参见 poses.md。
- 切换页面。 使用节拍上的硬切、地面的切出、像纸张一样覆盖的页面、摄影机进入一种颜色、从内部填充、穿过形状的开口或像素块。绝不要使用淡入淡出。参见 hits.md。
- 设置字体。 随着字号增大,字间距收紧,行距也随之收紧。在每个姿势下,每个单词与其背景的对比度都必须达到 4.5:1 以上。标题是“打”出来的,而非淡入的。参见 type.md。
- 制作声音。 每个点击音效都精确放置在歌曲自身的敲击点上,并与音频样本对齐。所有点击音效都合并到一个与歌曲同时开始的音轨中。在点击音效下方将音乐音量降低,并保留一个节拍器剪辑用于检查。参见 sound.md。
- 决定何时跳过一拍。 降音、闪避、保持、吸附、剪切整个小节、在歌曲节拍上结束。参见 skipping.md。
- 检查。 制作一个联系表,测量同步情况,并观看节拍器剪辑。以映射到姿势的时间戳形式做笔记。参见 checking.md。
- 在浏览器中实时播放? 遵循相同的规则,配合节拍器:页面上使用一个时钟,画面比声音提前 25ms,声音基于音频时钟提前调度,在没有歌曲时,每次变化都要对应一个点击。参见 live.md。
规则一览
时钟
| 项目 | 规则 |
|---|---|
| 帧率 | 30fps,有意为之。一个平滑的 60fps 变体可以共享故事板 (clock.md §8) |
| 姿势 | 歌曲的八分音符:在 169.825 BPM 下为 176.65ms,每秒 5.66 个,每个占 30fps 下的 5 或 6 帧 |
| 创作 | 所有时间以 ms 为单位,基于一个 200ms 的姿势;SPEED = TEMPO / 150;ms() 转换为帧,seconds() 用于嵌入式时钟 |
姿势 n 的第一帧 | ceil(n × frames per pose),基于整个视频的帧(beatFrame) |
| 页面 | 整数个姿势,从小节线开始(姿势序号能被 8 整除) |
| 更改长度 | 通过整个小节进行,或者在下一个小节线之前将多出的姿势退回去 |
| Studio 拖动 | 拖动的轨道会写入一个 from 偏移量,导致画面与声音错位:请移除它 |
间距
| 动作 | 曲线 | 姿势分布 |
|---|---|---|
| 进入和退出(摄影机运动、掠过、下划线) | easeInOutSine | 6 个姿势上分布为 7, 18, 25, 25, 18, 7% |
| 从画面外进入 | 缓出曲线 | 否则其缓慢的起始部分会浪费在屏幕外 |
| 一个页面覆盖在另一个上面 | easeOutSine | 3 个姿势的转场上分布为 38, 70, 92% |
| 缩放、线条变粗、开口变大 | 每个姿势按相同因子增长 | 缩放时其目标沿直线移动到中心 |
| 不是用于少量姿势 | 三次缓入缓出(3, 12, 35, 35, 12, 3:中间跳跃);强缓出(作为三姿势转场,在其第一个姿势着陆:78, 97, 100%;作为使用 moved() 的动作:88, 99, 100%) |
节拍上的事物
| 事物 | 规则 |
|---|---|
| 点击 | 按下一个姿势,在重击鼓或军鼓上;光标在最后一次点击后的下一个姿势消失 |
| 光标 | 打字时实心,然后两个姿势亮,两个姿势灭 |
| 旋转器 | 最多每个姿势转四分之一圈;超过半圈看起来像是倒转 |
| 滚动标记 | 按行程/半径的角度转动,最终直立,使用 easeOutSine 曲线 |
| 计数器 | 每个姿势变化一个数字,使用强缓出曲线,使用表格数字,整十数字保持两个姿势 |
| 标题 | 从自身姿势开始,比节拍更快地“打”出来:每三个帧一个单词;长行每 25ms(创作时间)一个字母 |
| 代码 | 每个姿势输入不均匀的 2 到 4 个字符块 |
| 按住退格 | 一次运行,每 25ms(创作时间)一个字母,包括换行符 |
| 摄影机升起 | 升起,在顶部静止两个姿势,然后落下 |
| 从画面外进入 | 在移动之前不被绘制 |
| 手动放置 | 最多偏移该姿势移动量的十分之一,一旦停止则完全归位;在几像素内落下 |
敲击 (Hits)
| 在歌曲中 | 在画面中 |
|---|---|
| 八分音符 | 输入代码的块,动作的步骤 |
| 军鼓 | 点击、爆裂声 |
| 强拍 | 着陆、新页面、页面的第一个动作 |
| 重击鼓 | 重大变化:页面着陆、最后一个完整页面、最终点击 |
| 鼓声消失 | 构建 |
| 鼓声回归 | 着陆 |
点击必须落在节拍上。转场可以偏离节拍。
声音
| 项目 | 规则 |
|---|---|
| 点击放置 | 精确对齐音频样本,放在歌曲的敲击点上(影片的敲击点落在其姿势后 25ms) |
| 点击文件 | 在滴答声之前有 40ms 的静音,这样修剪到样本时永远不会吃掉它 |
| 点击轨道 | 所有点击合并到一个与歌曲同时开始的轨道中 |
| 闪避 | 在重要的点击周围保持静音;在其他点击下方 160ms 内降低到 45%;使用 10ms 淡入淡出 |
| 节拍器 | 每个歌曲节拍在其敲击点上点击一次,小节的第一个点击音调更高,音乐音量在 20%;绝不包含在导出中 |
字体
| 字号 | 字间距 | 行距 |
|---|---|---|
| 小于 20px | 0;等宽大写 +2% | 1.4–1.5 |
| 20px 及以上 | −1% | 约 1.4 |
| 32px 及以上 | −2% | 约 1.25 |
| 56px 及以上 | −3% | 1.1–1.2;两行展示标题可降至 0.9 |
每个单词在每个姿势下与其背景的对比度必须至少达到 4.5:1。默认使用一种字体家族(示例中使用 Geist 和 Geist Mono),黑底白字,如触觉设计所做;影片本身将显示字体和正文字体配对,并将其页面放置在纯色背景上。计数器使用表格数字。标题是“打”出来的,绝不淡入。
实时播放(浏览器中)
| 项目 | 规则 |
|---|---|
| 时钟 | beat = anchorBeat + elapsed s × BPM / 60 × 2 基于 performance.now();在速度变化时重新锚定到当前时间;每个场景都是节拍(beat)的纯函数 |
| 画面 | 提前 25ms 读取节拍;每个场景在动画帧内提交(flushSync) |
| 声音 | 每 25ms 一个定时器,调度音频时钟上接下来的 120ms(getOutputTimestamp());窗口以音频时间测量,加上输出延迟 |
| 在视图中 | 只有屏幕上的内容才进行动画;示例中大部分在屏幕上的内容都能被听到 |
| 启动 | 加载时尝试,然后在首次按下或按键时启动;如果按键启动了声音,则不会静音该声音(600ms);记住静音状态 |
| 无歌曲 | 每次变化都对应一个点击:动作可以在八分音符上迈步,但必须在点击上开始和着陆;去掉该点击本身的节拍器点击 |
| 节拍器 | 一个轻柔的木制敲击声(880Hz,持续 50ms,小节第一个为 1320Hz),没有音乐时不发出重击声,全部通过一个限制器 |
| 暂停 | 在最近的点击上暂停:继续播放到该点击,或者在 160ms 内回退 |
| 减少动画 | 一个展示想法的静态姿势,只在按下声音按钮后移动 |
| 字体 | 单词对比度 4.5:1,有意义标记 3:1,缩放场景的最小单词渲染后为 9px 或更大 |
何时跳过一拍
网格是严格的,因此打破它是一种决策。以下每种打破方式在 skipping.md 中都有其原因。
- 在重要的点击周围降低音乐音量。 音乐从点击前 50ms 到下一个小节线处变为静音,带有 10ms 淡入淡出。点击声被单独听到,节拍随着后续内容回归。
- 在其他点击下方闪避。 将音乐音量降低到 45%,持续 160ms,以便点击的重击声有空间。
- 单独保持一个结果 一个歌曲节拍,然后才开始下一件事,例如复制后的一个勾选标记。
- 在一个升起动作的顶部保持两个相同的姿势。
- 在整十数字上保持两个姿势,然后再继续。
- 将一个落在节拍之间的音符吸附 到最近的加重节拍上。
- 剪切整个小节,即使笔记要求更少。说明这样做的代价。
- 在歌曲节拍上结束。 最后一页保持大约 2 秒钟,音乐在最后一个姿势上淡出。否则在动作结束后立即结束。
绝不能发布的内容
- 为了隐藏间距不良的动作而平滑掉节拍的运动
- 从自身第一帧开始计数姿势的场景
- 没有在旁边注明原因就开始于非小节线的页面
- 没有按整个小节更改,也没有在下一个小节线前退回的长度更改
- 合成树中残留的 Studio
from偏移量 - 落在非重击鼓或非军鼓上的点击,或者将点击声音放置在帧上而非对齐音频样本
- 在其自身帧上开始短促点击声音,而不是使用一个合并的轨道
- 其第一个姿势就是它离开的姿势的动作,或者在最后几像素“爬行”的动作
- 在移动之前就已经被绘制出来的画面外元素
- 每个姿势转动超过半圈的旋转器或轮子
- 淡入淡出,或在饱和背景之间的交叉淡入淡出
- 背景变化快于每秒三次闪烁
- 在任何姿势下,单词与其背景对比度低于 4.5:1
- …
[已截断]