Motion · Camera

Máy quay góc trên chuyển động

Hai kiểu tiết lộ từ trên cao: tilt-reveal ngẩng thẳng lên, và overhead-tabletop-drop trượt ngang qua dàn thẻ rồi lao vào.

1920×108016:930 fps4.89s · 147 frameremotionGiấy phép: Apache-2.0#shotcraft#remotion#camera#opening#transition

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

Biến thể

tilt-reveal
Cả trang nằm phẳng ở rotateX -80° trong khung perspective, ngẩng thẳng lên trong ~43f; rotateX/scale/translateY dùng chung easing out-cubic, hơi vọt nhẹ ở cuối.
Cảnh thiết lập mở đầu; một chủ thể
overhead-tabletop-drop
Dàn thẻ nằm phẳng ở rotateX 62°, đoạn lia chỉ chuyển động translateX trượt ngang qua, đoạn lao xuống chạy đồng thời góc/tỉ lệ/vị trí để lao vào và an vị bố cục cuối.
Chọn một trang sau khi tuần tra nhiều trang; dùng được cả cho chuyển cảnh lẫn mở đầu

Khi nào dùng

Mở đầu/chuyển cảnh kể chuyện bằng "góc ngẩng/cúi": dùng A cho cảnh thiết lập một trang, dùng B cho tuần tra nhiều trang rồi chọn một trang lao vào

Ý đồ

Kho cảnh vẫn thiếu góc quay từ trên cao — crane-rise là kiểu nâng lên "lia + zoom" (góc ngẩng/cúi không đổi), space-camera kiểu C là drone-dive lao xuống một mạch. Hai kiểu này để chính góc ngẩng/cúi kể chuyện: A là tiết lộ — cả trang nằm phẳng qua rotateX, mở đầu chỉ thấy một dải phối cảnh hẹp ở mép trên, máy quay "ngẩng lên" trở về ngang bằng, nội dung tràn vào từng hàng, như mở một bản vẽ trên bàn; B là chọn lựa — ba thẻ trang nằm phẳng thành dàn bàn, camera lia ngang qua từng thẻ từ trên cao (kiểu tabletop của Wes Anderson), lướt tới trang mục tiêu thì đột ngột lao xuống, ngẩng thẳng lên lấp đầy khung hình — "tuần tra một vòng, rồi chọn cái này". Chọn theo ngữ nghĩa: mở đầu một trang dùng A, chuyển cảnh/mở đầu nhiều trang dùng B.

Thời lượng & năng lượng

  • Thời lượng: A ~4.8s / B ~4.7s
  • Năng lượng: A Trung bình / B 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)

意图

库内俯拍视角一直空缺——crane-rise 是"平移+缩放"的升起(俯仰角 不动),space-camera C 式 drone-dive 是俯冲降落一镜到底;这两式让 俯仰角本身讲故事:A 是揭示——整页 rotateX 平躺、开场只见上缘 一条透视窄带,机位"抬头"回正,内容一排排涌入视野,像掀开桌上的 图纸;B 是选择——三张页面卡平躺成桌面卡阵,相机俯拍横向滑过依次 掠过(韦斯·安德森 tabletop),滑到目标页猛地俯冲扎入、抬正成满屏, "巡视一圈,就选这张"。选型:单页开场用 A,多页转场/开场用 B。

两式选型

| 式 | 做法 | 适用 | |----|------|------| | A tilt-reveal | perspective 容器内整页 rotateX -80° 平躺,~43f 抬正,rotateX/scale/translateY 共用 out-cubic,末端轻过冲 | 开场 establishing;单主体 | | B overhead-tabletop-drop | 卡阵平躺 rotateX 62°,pan 段只动 translateX 横滑掠过,drop 段角度/缩放/位移三通道同跑扎入落版 | 多页巡视后择一;转场/开场两用 |

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | A 俯角 | rotateX 关键帧 [-80, 2.6, -0.9, 0](f25→68→72→76),transformOrigin 50% 0% | 首版 -55° 太弱:f0 仍见半页,无"窄带"悬念(实渲判例) | | A 联动 | perspective 600→1200、perspectiveOrigin 5%→40% 随进度插值;scale 3.2→1 + translateY 200→0 | perspective 不联动则俯角初始见不到"只露顶栏" | | A 收尾 | 全部动画 f76 结束,145f 留 69f 真静止 | 过冲幅度 ≤3°,再大读作弹簧不是机位 | | B pan 段 | 0–55f 只改 translateX +700→-650(in-out cubic),角度不动 | pan 段动角度 = 两段职责糊掉(判例:pan 巡视、drop 表态) | | B drop 段 | 55–85f 三通道同跑:rotateX 62→-1.8→0.6→0(out-cubic 过冲)、scale 1→2.04→2.0、translateX -650→0 | 三通道必须同跑;分先后读作两个动作 | | B 卡阵 | 卡片 996×560(16:9 精确比),落版 scale 2.0 恰满屏正视;transform 顺序 translateX→rotateX→scale | 顺序错了 pan 会带出弧线轨迹 | | B 地板 | 浅灰网格地板 | 无地板参照,横滑段读不出"相机在动" | | B 收尾 | 全部动画 f93 结束,140f 留 47f 真静止 | — |

已知坑

  • demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,

首次实战须以真实素材回验

  • 大角度透视下卡片必须 backfaceVisibility hidden + translateZ(4px)

兜底,否则个别帧背面/深度穿帮

  • A 与 crane-rise-reveal 同片二选一——都是开场揭示,两个"揭示开场"

等于没有揭示

  • B pan 段角度锁死是命门(两段职责分离判例):巡视归巡视、扎入归

扎入,混着动观众不知道相机想干嘛

  • 两式都是"相机大动作",与 space-camera/crane-rise 同屏段落互斥,

一个段落只能有一个持镜人

参考实现

demos/camera/overhead-camera-moves/ (OverheadTabletopDrop.tsx / TiltReveal.tsx)

Mã nguồn tham chiếu (Remotion)

  • demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx
  • demos/camera/overhead-camera-moves/TiltReveal.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/camera/overhead-camera-moves/TiltReveal.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.

  1. 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.
  2. Clone repo video-shotcraft; component import fixture chung từ demos/_fixtures và helper từ assets/lib.
  3. Đă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/overhead-camera-moves.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx, demos/camera/overhead-camera-moves/TiltReveal.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("overhead-camera-moves")

Prompt

Use the video-shotcraft shot recipe "Máy quay góc trên chuyển động" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/overhead-camera-moves.json
Raw TSX: https://gimgs.net/motion/overhead-camera-moves/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx, demos/camera/overhead-camera-moves/TiltReveal.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 4.89s

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).

JSON · Markdown · llms.txt

5,989 chars · Apache-2.0Mở template gốc ↗
// ===== demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx =====
// overhead-tabletop-drop|上帝视角桌面滑降
// perspective(1600px) 内三张"页面卡"rotateX(62°) 平躺成桌面卡阵。
// 0–55f 相机横向滑过(只动 translateX,缓入缓出);55–85f 骤降扎入:
// rotateX 62→0 + 世界 scale 1→2.0(2% 过冲)+ translateX -650→0 三者同跑,
// 中间卡对位落成正视满屏 dashboard。全部动画 f=93 结束,收尾真静止 52f。
// 卡片 996×560(spec 900→996,使 FakeDashboard 0.5185 缩放严丝合缝、
// 落版 16:9 精确对位),间距 140px。
import React from 'react';
import { AbsoluteFill, interpolate, useCurrentFrame, Easing } from 'remotion';
import { FakeDashboard, Card, G } from '../../_fixtures/Fixtures';

const CARD_W = 996;
const CARD_H = 560;
const GAP = 140;
const PITCH = CARD_W + GAP; // 1136
const INNER = CARD_H / 1080; // 0.5185:FakeDashboard 缩到卡内正好铺满

const PAN_END = 55;
const DROP_END = 85;

// 单张"页面卡":白底圆角卡,内容按 INNER 缩放塞满
const PageCard: React.FC<{ x: number; children: React.ReactNode }> = ({ x, children }) => (
  <div
    style={{
      position: 'absolute',
      left: x - CARD_W / 2,
      top: -CARD_H / 2,
      width: CARD_W,
      height: CARD_H,
      background: G.card,
      border: `2px solid ${G.border}`,
      borderRadius: 14,
      overflow: 'hidden',
      boxSizing: 'border-box',
      boxShadow: '0 24px 48px rgba(0,0,0,0.22)',
      backfaceVisibility: 'hidden',
      transform: 'translateZ(4px)',
    }}
  >
    <div style={{ width: 1920, height: 1080, transform: `scale(${INNER})`, transformOrigin: '0 0' }}>
      {children}
    </div>
  </div>
);

export const OverheadTabletopDrop: React.FC = () => {
  const f = useCurrentFrame();

  // 0–55f 横滑:只动 translateX,缓入缓出
  const panX = interpolate(f, [0, PAN_END], [700, -650], {
    easing: Easing.inOut(Easing.cubic),
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });

  // 55–85f 骤降:角度 / 缩放 / 位移三者同跑,out-cubic + 2% 过冲
  const drop = interpolate(f, [PAN_END, DROP_END], [0, 1], {
    easing: Easing.out(Easing.cubic),
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });
  const rotX = interpolate(f, [PAN_END, DROP_END, DROP_END + 4, DROP_END + 8], [62, -1.8, 0.6, 0], {
    easing: Easing.out(Easing.cubic),
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });
  const scale = interpolate(f, [PAN_END, DROP_END, DROP_END + 7], [1, 2.04, 2.0], {
    easing: Easing.out(Easing.cubic),
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });
  const dropX = interpolate(drop, [0, 1], [panX, 0]); // drop 段把 -650 收回 0

  const tx = f <= PAN_END ? panX : dropX;

  return (
    <AbsoluteFill style={{ background: G.bg, overflow: 'hidden' }}>
      <div style={{ position: 'absolute', inset: 0, perspective: 1600, perspectiveOrigin: '50% 42%' }}>
        {/* 世界原点 = 屏心;先 in-plane scale,再 rotateX,最后 translateX */}
        <div
          style={{
            position: 'absolute',
            left: '50%',
            top: '50%',
            width: 0,
            height: 0,
            transformStyle: 'preserve-3d',
            transform: `translateX(${tx}px) rotateX(${rotX}deg) scale(${scale})`,
          }}
        >
          {/* 桌面:浅灰底 + 网格线,给横滑段提供运动参照 */}
          <div
            style={{
              position: 'absolute',
              left: -2800,
              top: -760,
              width: 5600,
              height: 1520,
              background: '#e6e6e4',
              backgroundImage: `repeating-linear-gradient(90deg, ${G.line} 0px, ${G.line} 2px, transparent 2px, transparent 160px), repeating-linear-gradient(0deg, ${G.line} 0px, ${G.line} 2px, transparent 2px, transparent 160px)`,
              backfaceVisibility: 'hidden',
            }}
          />
          <PageCard x={-PITCH}>
            <FakeDashboard variant="B" />
          </PageCard>
          {/* 目标页:落版满屏 */}
          <PageCard x={0}>
            <FakeDashboard variant="A" />
          </PageCard>
          <PageCard x={PITCH}>
            <div
              style={{
                width: 1920,
                height: 1080,
                background: G.panel,
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
              }}
            >
              <Card w={760} h={520} seed={4} />
            </div>
          </PageCard>
        </div>
      </div>
    </AbsoluteFill>
  );
};


// ===== demos/camera/overhead-camera-moves/TiltReveal.tsx =====
// tilt-reveal|俯仰揭示
// 开场俯视 dashboard 顶部(rotateX 平躺、只露顶栏),~43f 机位抬头回正,
// 内容一排排涌入视野。out-cubic + 末端轻微过冲回正,落定真静止 ≥35f。
import React from 'react';
import { AbsoluteFill, interpolate, useCurrentFrame, Easing } from 'remotion';
import { FakeDashboard, G } from '../../_fixtures/Fixtures';

const HOLD = 25; // 俯角定格
const MOVE = 43; // 主抬升
// 过冲:-55° → +2.6° → -0.9° → 0°,全部动画在 f=76 结束(145-76=69f 真静止)

export const TiltReveal: React.FC = () => {
  const f = useCurrentFrame();

  const rotX = interpolate(
    f,
    [HOLD, HOLD + MOVE, HOLD + MOVE + 4, HOLD + MOVE + 8],
    [-80, 2.6, -0.9, 0],
    { easing: Easing.out(Easing.cubic), extrapolateLeft: 'clamp', extrapolateRight: 'clamp' },
  );

  const p = interpolate(f, [HOLD, HOLD + MOVE], [0, 1], {
    easing: Easing.out(Easing.cubic),
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });
  const scale = interpolate(p, [0, 1], [3.2, 1]);
  const ty = interpolate(p, [0, 1], [200, 0]);
  const persp = interpolate(p, [0, 1], [600, 1200]);
  const perspY = interpolate(p, [0, 1], [5, 40]);

  return (
    <AbsoluteFill style={{ background: G.bg, overflow: 'hidden' }}>
      <div
        style={{
          position: 'absolute',
          inset: 0,
          perspective: persp,
          perspectiveOrigin: `50% ${perspY}%`,
        }}
      >
        <div
          style={{
            width: 1920,
            height: 1080,
            transformOrigin: '50% 0%', // 画面上缘
            transform: `translateY(${ty}px) scale(${scale}) rotateX(${rotX}deg)`,
          }}
        >
          <FakeDashboard variant="A" />
        </div>
      </div>
    </AbsoluteFill>
  );
};


  1. Read the recipe
    Prompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points.
  2. Get the code
    tool: git
    git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install
  3. Register + render
    tool: remotion
    npx remotion render <CompositionId> out.mp4 # component: demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx, demos/camera/overhead-camera-moves/TiltReveal.tsx
  4. Or re-implement
    Port the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing.
paramtypedefaultdescription
—

Cùng danh mục