Motion · Camera

Basic 3D Scene Tour

An impress.js-style spatial deck: cards scattered through 3D space with distinct position/rotation/scale, the camera flies to align to the inverse of each step's pose, ending pulled back to an OVERVIEW.

1920×108016:930 fps6.5s · 195 framesremotionLicense: Apache-2.0#shotcraft#remotion#camera

Source: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Remotion composition (React/TSX) — preview video, no in-browser player

When to use

Spatializing a concept/roadmap/three-step story; replacing flat slides with a "different spatial viewpoint per step" feel.

Intention

Turn "turning a page" into "traveling through the space of an idea": each card has not just a position but a pose (step two tilts 40° sideways, step three rotates the whole frame 90°), and as the camera aligns to it, the viewer's world rotates too — the spatial change itself becomes the ritual of "entering a new chapter."

Duration & energy

  • Duration: About 6.0s (180f@30fps; four stops, three flights of 0.96s each).
  • Energy: Medium (each transition is a spatial surprise; the dwell segments are quiet reading time).

Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)

意图

把"翻页"变成"在想法的空间里旅行":每张卡不仅有位置还有姿态(第二步侧转 40°、第三步整个画框转 90°),相机对齐它时观众的世界也跟着转——空间变化本身成为"进入新章节"的仪式感。

动效核心

  • 核心配方一行:camera = stepTransform.inverse()。每步只写卡片自己的 pose(x/y/z/rx/ry/rz/s),相机变换按 scale(1/s) rotateZ(-rz) rotateY(-ry) rotateX(-rx) translate3d(-x,-y,-z) 取逆,顺序严格相反
  • 四站姿态各有戏剧点:STEP01 原点正视 → STEP02 右移 520px + ry−40°(世界左转)→ STEP03 rz90°(画框侧躺,相机跟着转正它)→ OVERVIEW s=3.1(相机 1/3.1 拉远总览)
  • 飞行链式插值:三段 flyAt=[0.22,0.48,0.76] 各 0.16(inOutCubic),cam 从当前值向下一站 lerp——中断任一段都是合法姿态
  • enter/exit 聚焦:af(累计飞行进度)与卡片序号距离决定 opacity 0.28–1 + blur 0–3.5px;OVERVIEW 段强制全员点亮(max(1-d, over))
  • OVERVIEW 卡本身放大 3.1 倍但内容预除 2.2——总览时它是"标题牌"而非巨物

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 站点姿态 | 每站至少一个新变换维度 | 只挪 x/y 是平移幻灯片;rz 站(画框旋转)是记忆点,一支片用一次 | | 飞行时长 | 0.16(约 0.96s) | 有旋转的段别快于 0.12,观众需要时间理解"世界在转" | | 停留间隔 | 0.10–0.12 | 读卡位;卡内文字多时优先加停留而不是减飞行 | | OVERVIEW 缩放 | s=3.1(相机拉远 1/3.1) | 值越大总览越"高空";保证所有卡都进画框再留 10% 边距 | | 聚焦衰减 | opacity 0.28 底 + blur 3.5px 峰 | 底值别低于 0.2——离焦卡要"在场",纯黑会丢空间感 | | perspective | 1000px | 与最大 |z|(520)比值约 2:1;更深的 z 需同步加大 persp 防畸变 |

已知坑

  • 逆变换的旋转顺序(Z→Y→X 反着正变换的 X→Y→Z)不能乱——乱序在单轴旋转时看不出来,多轴复合站会歪
  • 卡片 pose 写在卡自身 transform 和相机公式两处消费,加站只改 poses 表,两处 transform 字符串别动
  • rz90° 站的卡内文字会跟着躺倒,相机转正后才可读——这是设计(转正瞬间"啊看懂了"),但该站文案要短
  • OVERVIEW 后无返程,作段落收尾用;要循环回 STEP01 就在末尾加一段 fly back(cam 再 lerp 回 poses[0])

参考实现

demos/camera/basic-3d-scene/ (Basic3DScene.tsx)

Reference implementation (Remotion)

  • demos/camera/basic-3d-scene/Basic3DScene.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/camera/basic-3d-scene/Basic3DScene.tsx

The component imports shared fixtures from demos/_fixtures and helpers from assets/lib in the repo; SKILL.md there documents the render workflow.

Everything on this page is also machine-readable. Point your agent at the JSON, or add the gimgs MCP server and ask it to search motion templates.

  1. Fetch the JSON — it holds the shot recipe (timing, easing, parameter table, pitfalls) and the full TSX source.
  2. Clone the video-shotcraft repo; the component imports shared fixtures from demos/_fixtures and helpers from assets/lib.
  3. Register the component as a <Composition> in a Remotion project and render with `npx remotion render`, or re-implement the recipe in your own engine.
curl -s https://gimgs.net/motion/basic-3d-scene.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/camera/basic-3d-scene/Basic3DScene.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("basic-3d-scene")

Prompt

Use the video-shotcraft shot recipe "Basic 3D Scene Tour" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/basic-3d-scene.json
Raw TSX: https://gimgs.net/motion/basic-3d-scene/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/camera/basic-3d-scene/Basic3DScene.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 6.5s

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

4,497 chars · Apache-2.0Open raw template ↗
// ===== demos/camera/basic-3d-scene/Basic3DScene.tsx =====
// basic-3d-scene — Impress 3D Steps 空间步进(motion-lab 定稿转原生 Remotion)
// impress.js 式演示:卡片散布 3D 空间各处(不同 xyz+旋转+缩放),相机依次飞到
// 每个 Step 的姿态对齐观看;核心配方 camera = stepTransform.inverse(),
// 非当前 step 半透明+模糊做 enter/exit。
// 设计坐标 480×270(DesignStage 等比放大),参数表数值以此坐标系标定。
import React from 'react';
import { DesignStage, E, lerp, seg, useT } from '../../_fixtures/Motion';

export const BASIC_3D_SCENE_DURATION = 180; // 6000ms @30fps

// 每个 step:位置 + 姿态 + 缩放(相机将取其逆)
const POSES = [
  { x: 0,   y: 0,   z: 0,    rx: 0, ry: 0,   rz: 0,  s: 1,   hue: 215, tt: 'STEP 01',  sub: 'Position the idea' },
  { x: 520, y: -60, z: -180, rx: 0, ry: -40, rz: 0,  s: 1,   hue: 265, tt: 'STEP 02',  sub: 'Rotate the view' },
  { x: 160, y: 300, z: -520, rx: 0, ry: 0,   rz: 90, s: 1,   hue: 165, tt: 'STEP 03',  sub: 'Spin the frame' },
  { x: 220, y: 90,  z: -260, rx: 0, ry: 0,   rz: 0,  s: 3.1, hue: 25,  tt: 'OVERVIEW', sub: 'See everything' },
];

// 飞行时刻表:0 停在 step0,之后三段飞行
const FLY_AT = [0.22, 0.48, 0.76];
const FLY = 0.16;

export const Basic3DScene: React.FC = () => {
  const t = useT();

  // activeFloat = 已完成的飞行进度之和 → 当前相机在哪个 step 之间
  let af = 0;
  const cam = { ...POSES[0] };
  for (let i = 0; i < FLY_AT.length; i++) {
    const f = seg(t, FLY_AT[i], FLY_AT[i] + FLY, E.inOutCubic);
    af += f;
    const p = POSES[i + 1];
    cam.x = lerp(f, cam.x, p.x); cam.y = lerp(f, cam.y, p.y); cam.z = lerp(f, cam.z, p.z);
    cam.rx = lerp(f, cam.rx, p.rx); cam.ry = lerp(f, cam.ry, p.ry); cam.rz = lerp(f, cam.rz, p.rz);
    cam.s = lerp(f, cam.s, p.s);
  }
  // overview 段进度(此时全部卡片点亮)
  const over = seg(t, FLY_AT[2], FLY_AT[2] + FLY);

  return (
    <DesignStage bg="#0a0b10">
      <div
        style={{
          position: 'absolute',
          inset: 0,
          overflow: 'hidden',
          background: 'radial-gradient(ellipse at 50% 40%,#141828 0%,#0a0b10 70%)',
          perspective: '1000px',
        }}
      >
        {/* world:相机 = 场景逆变换 —— 先反平移,再反旋转,再 1/scale */}
        <div
          style={{
            position: 'absolute',
            left: '50%',
            top: '50%',
            width: 0,
            height: 0,
            transformStyle: 'preserve-3d',
            willChange: 'transform',
            transform: `scale(${1 / cam.s})
              rotateZ(${-cam.rz}deg) rotateY(${-cam.ry}deg) rotateX(${-cam.rx}deg)
              translate3d(${-cam.x}px,${-cam.y}px,${-cam.z}px)`,
          }}
        >
          {POSES.map((p, i) => {
            const last = i === POSES.length - 1;
            // enter/exit:距当前视点越远越暗越糊(overview 时全部点亮)
            const d = Math.min(1, Math.abs(af - i));
            const focus = Math.max(1 - d, over);
            return (
              <div
                key={i}
                style={{
                  position: 'absolute',
                  left: last ? -160 : -110,
                  top: last ? -100 : -70,
                  width: last ? 320 : 220,
                  height: last ? 200 : 140,
                  boxSizing: 'border-box',
                  borderRadius: 10,
                  padding: '18px 20px',
                  willChange: 'opacity,filter',
                  background: `linear-gradient(150deg,hsl(${p.hue},45%,16%),hsl(${p.hue},55%,9%))`,
                  border: `1px solid hsl(${p.hue},60%,34%)`,
                  boxShadow: `0 18px 50px rgba(0,0,0,.55), inset 0 1px 0 hsla(${p.hue},70%,70%,.25)`,
                  transform: `translate3d(${p.x}px,${p.y}px,${p.z}px) rotateX(${p.rx}deg) rotateY(${p.ry}deg) rotateZ(${p.rz}deg) scale(${p.s / (last ? 2.2 : 1)})`,
                  fontFamily: '-apple-system,system-ui,sans-serif',
                  color: '#eef1f8',
                  opacity: 0.28 + focus * 0.72,
                  filter: `blur(${(1 - focus) * 3.5}px)`,
                }}
              >
                <div style={{ fontSize: 11, letterSpacing: 3, color: `hsl(${p.hue},80%,68%)`, fontWeight: 700 }}>
                  {p.tt}
                </div>
                <div style={{ fontSize: last ? 26 : 21, fontWeight: 800, marginTop: 8 }}>{p.sub}</div>
                <div style={{ marginTop: 12, height: 5, width: '56%', borderRadius: 3, background: `hsl(${p.hue},70%,45%)` }} />
                <div style={{ marginTop: 7, height: 5, width: '34%', borderRadius: 3, background: `hsla(${p.hue},50%,60%,.4)` }} />
              </div>
            );
          })}
        </div>
      </div>
    </DesignStage>
  );
};


  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/basic-3d-scene/Basic3DScene.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
—

More in this category