Motion · Nhịp & montage
Chuyển nhịp kiểu sakuga
Từ một-đập-ba sang một-đập-một — yếu tố di chuyển giật cục mỗi 3 khung hình một bước, rồi bật sang mượt từng khung hình ở cao trào.
Nguồn: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Composition Remotion (React/TSX) — có video preview, không có player trong trình duyệt
Khi nào dùng
Dịch chuyển nhấn mạnh cho một yếu tố đơn (thẻ vào cảnh, chỉ số về đích); đoạn cần tương phản "cảm giác thủ công → bùng nổ cao trào"
Ý đồ
Kho cảnh đã có speed-ramp cho đổi tốc độ liên tục và hit-counter cho khựng cục bộ. Cảnh này khác — đổi chính tần số khung hình chứ không phải tốc độ: đoạn giật cục lấy khung hình điều khiển q=floor(f/3)*3, yếu tố chỉ động mỗi 3 khung hình một lần (cảm giác lật sách 10fps); tại điểm chuyển, nó đổi sang điều khiển bằng f gốc từng khung hình một cho cú nước rút mượt mà. Đoạn ba-khung-một-bước thô cứng và đoạn mượt cắt cạnh nhau trong cùng một cảnh — đây là kỹ thuật diễn xuất "shooting on 3s / on 1s" của hoạt hình Nhật. Đoạn giật cục không phải cảm giác kém chất lượng mà là cảm giác thủ công, điểm chuyển chính là dấu mốc cao trào.
Thời lượng & năng lượng
- Thời lượng: ~5s
- Năng lượng: Trung bình cao
Công thức cảnh quay (bản gốc tiếng Trung — timing, easing, bảng tham số, lỗi hay gặp)
意图
库内时间轴操纵已有 speed-ramp 连续变速与 hit-counter 的局部顿帧。 本卡是另一种——改的不是速度是帧率本身: 顿挫段驱动帧 q=floor(f/3)*3,元素每 3 帧才动一步(10fps 手翻书), 切换帧起改用原始 f 逐帧驱动,丝滑冲刺。一拍三的钝与一拍一的滑 同屏对切,日式作画的拍数演出(shooting on 3s / on 1s)。顿挫段 不是低级感是手工感,切换点即高潮标记。
动效核心
- 顿挫段:q = Math.floor(f/3)*3 当驱动帧,位置线性 X 120→1380,
每步 ≈79px;姿势 sin(q) rotate ±5°,一步一个样
- f48 切换:改原始 f 连续驱动,Easing.out(poly(4)) 高初速冲刺
折返中央 + 过冲 36px + 3f scaleX 0.9 回弹急停
- 冲刺段运动拉伸由位置差分速度驱动(scaleX 峰值 ≈1.35 /
scaleY 0.88),残影 2 层条件挂载——速度低即摘除,非 opacity 0
- 角标 "on 3s"/"on 1s" 随段切换,切换瞬间 pop 1.35×;角标
line-boil 用 h() 伪随机、f=108 后冻结
- f108–150 真静止 42f,全画面无任何动画
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 拍数 | 顿挫段 floor(f/3)(一拍三) | /2 顿感不足、/4 以上读作播放器卡死 | | 每步位移 | ≈79px(总程 ÷ 步数倒推) | <30px 读作卡顿掉帧不是顿挫——命门 | | 步间姿势 | sin(q) rotate ±5° | 无姿势变化的纯位移顿挫像 bug 不像手翻书 | | 切换帧 | f48(顿挫:冲刺 ≈ 3:2) | 切换必须一帧完成,任何补间都毁掉"换拍"感 | | 冲刺缓动 | Easing.out(poly(4)) + 过冲 36px + 3f 回弹 | 初速要猛,与顿挫段拉满反差 | | 收尾 | 真静止 ≥42f,角标沸腾同步冻结 | 沸腾残留毁真静止(feTurbulence 摘罩判例同族) |
已知坑
- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,
首次实战须以真实素材回验
- 同一镜头单一时间操纵者判例:与 speed-ramp / hit-counter 顿帧
互斥,一个镜头只能有一个人动时间轴
- 顿挫段每步位移 ≥30px 才读得出"顿"——微步顿挫读作渲染掉帧,
这是本卡命门;步幅不够就缩短总时长换大步幅
- 切换点必须有标注(角标 "on 3s"→"on 1s" 或一声打点,
sound-design §4.5)——无标注观众只觉得"变快了",读不出"换拍"
参考实现
demos/rhythm/sakuga-timing-shift/ (SakugaTimingShift.tsx)
Mã nguồn tham chiếu (Remotion)
- demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx
Component import fixture chung từ demos/_fixtures và helper từ assets/lib của repo; xem SKILL.md trong repo để biết quy trình render.
Mọi thứ trên trang này đều có bản máy đọc. Đưa agent link JSON, hoặc thêm MCP server gimgs rồi bảo nó tìm motion template.
- Tải JSON — trong đó có công thức cảnh quay (timing, easing, bảng tham số, lỗi hay gặp) và toàn bộ source TSX.
- Clone repo video-shotcraft; component import fixture chung từ demos/_fixtures và helper từ assets/lib.
- Đăng ký component thành <Composition> trong project Remotion rồi render bằng `npx remotion render`, hoặc dựng lại công thức bằng engine của bạn.
curl -s https://gimgs.net/motion/sakuga-timing-shift.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("sakuga-timing-shift")Prompt
Use the video-shotcraft shot recipe "Chuyển nhịp kiểu sakuga" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/sakuga-timing-shift.json Raw TSX: https://gimgs.net/motion/sakuga-timing-shift/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 5.06s 1. Read the JSON and follow its "prompt" section (when to use, intention, parameter table, known pitfalls). 2. Adapt it for: <describe what you want>. 3. Either clone the repo, register the component as a <Composition> in a Remotion project and run npx remotion render <CompositionId> out.mp4 — or re-implement the recipe in your own engine (HTML/WAAPI works: keep the same timing and easing).
// ===== demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx =====
// sakuga-timing-shift —— 一拍三转一拍一(作画打拍切换)
// 0–48f:驱动帧 q = floor(f/3)*3,卡从左侧顿挫横移到右侧(10fps 手翻书感),
// 每步 ≈74px + rotate 摆动 = 一拍三的钝感;48f 切换点后改用原始 f 连续驱动,
// 48–75f 丝滑冲刺折返中央(out-poly(4) 高初速 + 运动拉伸 scaleX + 残影),
// 过冲 36px 后 3f 回弹落位。左上角标 "on 3s"/"on 1s" 随段切换并带 line-boil
// (boil 在 f=108 后冻结)。收尾真静止 ≥40f。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G, Card, TitleBlock } from '../../_fixtures/Fixtures';
const W = 1920;
const CARD_W = 420;
const CARD_H = 260;
const CARD_Y = (1080 - CARD_H) / 2; // 410
const X_LEFT = 120;
const X_RIGHT = 1380; // 卡左缘,右侧停点
const X_CENTER = (W - CARD_W) / 2; // 750,中央落位
const OVERSHOOT = 36;
const SWITCH = 48; // 打拍切换帧
const ARRIVE = 70; // 冲刺到过冲点
const SETTLE = 75; // 回弹落位完成
// 段一:一拍三。位置函数线性,但只在 q = floor(f/3)*3 上取值。
const pos1 = (t: number): number =>
interpolate(t, [0, SWITCH], [X_LEFT, X_RIGHT], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// 段二:一拍一。out-poly(4) 高初速冲刺 → 过冲 → 3f 回弹。
const pos2 = (t: number): number =>
interpolate(t, [SWITCH, ARRIVE, SETTLE], [X_RIGHT, X_CENTER - OVERSHOOT, X_CENTER], {
easing: Easing.out(Easing.poly(4)),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
export const SakugaTimingShift: React.FC = () => {
const f = useCurrentFrame();
const onThrees = f < SWITCH;
// ---- 卡片位置 ----
const q = Math.floor(f / 3) * 3; // 一拍三驱动帧
const x = onThrees ? pos1(q) : pos2(f);
// 顿挫段 rotate 摆动(随 q 冻结,一步一个姿势)
const rot = onThrees
? Math.sin(q * 0.7) * 5
: interpolate(f, [SWITCH, SWITCH + 8], [Math.sin(SWITCH * 0.7) * 5, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// 冲刺段速度(位置差分,帧时间解耦)→ 运动拉伸
const v = onThrees ? 0 : Math.abs(pos2(f) - pos2(f - 1));
const sFac = Math.min(v / 55, 1);
const stretchX = 1 + 0.35 * sFac; // 峰值 ≈1.35,横向拉丝
const stretchY = 1 - 0.12 * sFac;
// 落位急停回弹:72–78f scaleX 压扁再回 1
const sqX = interpolate(f, [SETTLE - 3, SETTLE, SETTLE + 3], [1, 0.9, 1], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const sqY = interpolate(f, [SETTLE - 3, SETTLE, SETTLE + 3], [1, 1.07, 1], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// 冲刺残影:只在段二速度高时挂载(条件挂载,不留 opacity 0 的壳)
const ghosts =
!onThrees && f > SWITCH + 1 && f < ARRIVE + 2 && sFac > 0.15
? [
{ xg: pos2(f - 2), op: 0.28 * sFac },
{ xg: pos2(f - 4), op: 0.13 * sFac },
]
: [];
// ---- 角标 "on 3s" / "on 1s",line-boil,f=108 后冻结 ----
const h = (n: number) => {
const s = Math.sin(n * 127.3) * 43758.5453;
return s - Math.floor(s);
};
const qb = Math.min(Math.floor(f / 4) * 4, 108); // boil 驱动帧,108 后冻结
const bx = (h(qb + 1) - 0.5) * 7;
const by = (h(qb + 2) - 0.5) * 7;
const brot = (h(qb + 3) - 0.5) * 3;
// 切换瞬间角标弹一下
const pop = interpolate(f, [SWITCH, SWITCH + 3, SWITCH + 9], [1, 1.35, 1], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const titleOp = interpolate(f, [0, 10], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
return (
<div style={{ width: 1920, height: 1080, background: G.bg, position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: 90, width: '100%', textAlign: 'center', opacity: titleOp }}>
<TitleBlock text="SAKUGA TIMING SHIFT" size={64} />
</div>
{/* 轨道基线 + 中央落位虚线槽 */}
<div
style={{
position: 'absolute',
left: 100,
right: 100,
top: CARD_Y + CARD_H + 24,
height: 3,
background: G.line,
borderRadius: 2,
}}
/>
<div
style={{
position: 'absolute',
left: X_CENTER,
top: CARD_Y,
width: CARD_W,
height: CARD_H,
border: `3px dashed ${G.bar}`,
borderRadius: 14,
boxSizing: 'border-box',
}}
/>
{/* 冲刺残影 */}
{ghosts.map((g, i) => (
<div
key={`ghost-${i}`}
style={{ position: 'absolute', left: 0, top: CARD_Y, opacity: g.op, transform: `translateX(${g.xg}px)` }}
>
<Card w={CARD_W} h={CARD_H} seed={4} />
</div>
))}
{/* 主卡 */}
<div
style={{
position: 'absolute',
left: 0,
top: CARD_Y,
transform: `translateX(${x}px) rotate(${rot}deg) scaleX(${stretchX * sqX}) scaleY(${stretchY * sqY})`,
transformOrigin: '50% 50%',
}}
>
<Card w={CARD_W} h={CARD_H} seed={4} />
</div>
{/* 角标:on 3s / on 1s */}
<div
style={{
position: 'absolute',
left: 120,
top: 160,
transform: `translate(${bx}px, ${by}px) rotate(${brot}deg) scale(${pop})`,
transformOrigin: '0% 50%',
fontFamily: 'Courier New, monospace',
fontWeight: 700,
fontSize: 84,
color: G.ink,
borderBottom: `6px solid ${G.ink}`,
paddingBottom: 6,
}}
>
{onThrees ? 'on 3s' : 'on 1s'}
</div>
</div>
);
};
- Read the recipePrompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points.
- Get the codetool: git
git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install - Register + rendertool: remotion
npx remotion render <CompositionId> out.mp4 # component: demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx - Or re-implementPort the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing.
| param | type | default | description |
|---|---|---|---|
| — | |||
Cùng danh mục

Ngữ pháp trailer: ba nhịp
Ba thủ pháp ngữ pháp trailer — trailer-bumper cắt nhanh móc câu mở đầu, card-footage-cadence xen thẻ chữ với cảnh đối thoại, smash-cut đập cắt vào tĩnh lặng.

Đổi tốc độ và đóng băng chú thích
Hai thủ pháp nhịp điệu remap số khung hình phi tuyến — speed-ramp (nhanh → 0.2x nhìn kỹ → nhanh) và freeze-annotate (chảy → đóng băng khoanh chú thích → tan băng).

Gạch chân hóa phổ tần
UI hóa phổ tần — gạch chân tiêu đề tách thành dải cột dọc nhảy theo phổ tần suốt hai nhịp, rồi thu lại thành đường thẳng; hình ảnh hóa nhạc sống trong UI.

Ảnh bóng lặp kiểu smear
Ảnh bóng smear — thẻ để lại 4 bản sao bán trong suốt rõ, đếm được khi lướt ngang nhanh, gộp lại làm một lúc dừng; phương án dựng hình thay cho motion blur.