Motion · Outro

Grain dissolve to wordmark

A full line of text bursts into boiling grain noise with a selection box appearing, then the noise cloud rapidly condenses into a larger glowing short wordmark, drift decaying to zero as it freezes.

1920×108016:930 fps2.5s · 75 framesremotionLicense: Apache-2.0#shotcraft#remotion#outro

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

When to use

An "XX. Now Live" style closing launch announcement; the energy-condensing beat where a long sentence compresses into a short brand mark.

Intention

Shoot "one sentence condensing into one word" as a physical event: the sentence first destabilizes into boiling grain (information disintegrating), a selection box implies "being selected and extracted", then all the grain's energy collapses toward the center into a bigger, brighter short wordmark — what the audience reads is "all of this boils down to this".

Duration & energy

  • Duration: About 2.0s (60f@30fps; granulate 0.26–0.56s · condense 1.2–1.42s · settle and freeze)
  • Energy: Medium-high (a brief, one-time energy pulse, a natural outro beat)

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

意图

把"一句话浓缩成一个词"拍成物理事件:句子先失稳沸腾成颗粒(信息解体),选区框暗示"正在选中提取",然后所有颗粒能量向中心坍缩成更大更亮的短字标——观众读到的是"这一切归结为它"。

动效核心

  • 滤镜链是唯一引擎:feTurbulence(fractalNoise,baseFrequency 0.9→1.3)+ feDisplacementMap(scale 0→52)+ feGaussianBlur(0→1.1px),三者由同一条 burst 曲线(t=0.13–0.28,outCubic)驱动
  • 噪点"沸腾"靠 seed = floor(t*46) 逐帧换种子——每帧一幅新噪声图,颗粒永不静止
  • t=0.60–0.71 交叉淡化:整行字(33px)淡出、短字标(54px 粗体)淡入,两者共用同一滤镜——切换发生在最沸腾时,观众看不到换字
  • t=0.68–0.90 lock 曲线把 displacement scale 和 blur 拉回 0,颗粒"凝固"成清晰字形
  • 白辉光三段式:砂化期 0.3 弱光 → 凝聚冲高 0.7 → t=0.88 后回落 0.45,drop-shadow 半径 4–24px 同步呼吸
  • 斜纹选区框(45° 线阵 + 四角像素棋盘手柄)随 burst 浮现、t=0.55–0.64 撤掉——在凝聚前退场,不抢主体

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | displacement scale | 峰值 52 | <30 只是"轻微毛边";>80 颗粒散到认不出轮廓,"隐约可辨"是砂化质感的底线 | | baseFrequency | 0.9 → +0.4 | 越高颗粒越细;低于 0.5 变成大块扭曲不是"砂" | | seed 换帧率 | floor(t*46),2s 内 46 次 | 降低会看出噪声图重复;保持 ≥20 次/秒的换图率 | | 砂化窗口 | 0.13–0.28(outCubic) | 前 0.13 的干净字停留是"失稳前的常态",砍掉则没有对比 | | 凝聚交叉 | 0.60–0.71(inOutCubic) | 必须落在最沸腾段内完成换字;提前会穿帮看到两行字叠影 | | 辉光包络 | burst·0.3 + cond·0.7 − settle·0.45 | 凝聚冲高是卡点位,压 BGM 重音;回落别到 0,留柔光"余温" |

已知坑

  • 换字标文案(ACME → 项目短标)时两行 <text> 都要改:整行句式 { XX. Now Live } 与短标 XX 必须指同一对象,否则叙事断裂
  • 短标变长(>6 字符)需同步缩 54px 字号或加宽选区框 bw,否则凝聚后超出框位视觉重心偏移
  • feDisplacementMap 在 Safari 上性能较差,这张卡是 2s 短拍可接受;拉长时长需实测帧率
  • 选区框撤场(0.55–0.64)必须早于凝聚完成(0.71)——框留到凝固后会读作"还没选完",语义反了

参考实现

demos/outro/grain-dissolve/ (GrainDissolve.tsx)

Reference implementation (Remotion)

  • demos/outro/grain-dissolve/GrainDissolve.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/outro/grain-dissolve/GrainDissolve.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/grain-dissolve.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/outro/grain-dissolve/GrainDissolve.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("grain-dissolve")

Prompt

Use the video-shotcraft shot recipe "Grain dissolve to wordmark" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/grain-dissolve.json
Raw TSX: https://gimgs.net/motion/grain-dissolve/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/outro/grain-dissolve/GrainDissolve.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 2.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

5,034 chars · Apache-2.0Open raw template ↗
// ===== demos/outro/grain-dissolve/GrainDissolve.tsx =====
// grain-dissolve — Grain Dissolve → Condense 文字砂化凝聚(motion-lab 定稿转原生 Remotion)
// 干净的整行字 "{ ACME. Now Live }" 先爆裂成沸腾颗粒噪点(轮廓隐约可辨、白色辉光),
// 同时出现带 45° 斜纹填充和像素方块角柄的选区框;噪点沸腾约半程后选区框消失,
// 噪点云急速凝聚成更大号的颗粒短字标(占位词 "ACME"),位移量衰减归零、辉光冲高回落,
// 凝固为清晰发光短字标。四角 HUD 括角/圆点与左右中线短划全程常驻。
// 滤镜链:feTurbulence seed 逐帧 + displacement scale 双向动画,终字同走滤镜再解除。
// 设计坐标 480×270(DesignStage 等比放大),SVG viewBox 640×360 铺满全幅。
import React, { useId } from 'react';
import { E, DesignStage, seg, useT } from '../../_fixtures/Motion';

export const GRAIN_DISSOLVE_DURATION = 60; // 2000ms @30fps

// 选区框几何(viewBox 坐标)
const BX = 128;
const BY = 148;
const BW = 384;
const BH = 62;

// 45° 斜纹:x 从 bx-bh 起每 34 一根,右下→左上
const HATCH_XS: number[] = [];
for (let x = BX - BH; x < BX + BW; x += 34) HATCH_XS.push(x);

// 四角像素棋盘手柄(两块 5×5 错位方块)
const Handle: React.FC<{ x: number; y: number }> = ({ x, y }) => (
  <g transform={`translate(${x - 5},${y - 5})`} fill="#cfd2d8">
    <rect width={5} height={5} />
    <rect x={5} y={5} width={5} height={5} />
  </g>
);

// HUD 括角 + 圆点(sx/sy 控制朝向)
const Corner: React.FC<{ x: number; y: number; sx: number; sy: number }> = ({ x, y, sx, sy }) => (
  <>
    <path
      d={`M${x + 14 * sx} ${y}H${x}V${y + 14 * sy}`}
      fill="none"
      stroke="#3a3a40"
      strokeWidth={1.5}
    />
    <circle cx={x + 34 * sx} cy={y + 28 * sy} r={1.6} fill="#8b8d94" />
  </>
);

export const GrainDissolve: React.FC = () => {
  const t = useT();
  // 滤镜/clipPath ID 按实例生成,同一 Composition 放多个实例时互不串引
  // (useId 的 «:» 在 CSS url() 里非法,清洗成纯字母数字)
  const uid = useId().replace(/[^a-zA-Z0-9]/g, '');
  const fid = `gd-${uid}`;
  const cid = `gd-${uid}-clip`;
  const burst = seg(t, 0.13, 0.28, E.outCubic); // 干净字 → 砂化
  const cond = seg(t, 0.60, 0.71, E.inOutCubic); // 整行噪点云 → 短字标噪点云
  const lock = seg(t, 0.68, 0.90, E.outCubic); // 位移衰减凝固
  const settle = seg(t, 0.88, 1, E.outCubic); // 辉光回落
  // 白色辉光:砂化期轻微,凝聚时冲高,凝固后回落到柔光
  const glow = burst * 0.3 + cond * 0.7 - settle * 0.45;
  return (
    <DesignStage bg="#0a0a0c">
      <svg
        viewBox="0 0 640 360"
        style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', background: '#0a0a0c' }}
      >
        <defs>
          <filter id={fid} x="-40%" y="-150%" width="180%" height="400%">
            <feTurbulence
              type="fractalNoise"
              baseFrequency={0.9 + burst * 0.4}
              numOctaves={2}
              seed={Math.floor(t * 46)}
              result="n"
            />
            <feDisplacementMap
              in="SourceGraphic"
              in2="n"
              scale={burst * 52 * (1 - lock)}
              xChannelSelector="R"
              yChannelSelector="G"
              result="d"
            />
            <feGaussianBlur in="d" stdDeviation={burst * 1.1 * (1 - lock)} />
          </filter>
        </defs>
        {/* 四角 HUD 括角 + 圆点 + 左右中线短划(全程常驻) */}
        <g>
          <Corner x={88} y={96} sx={1} sy={1} />
          <Corner x={552} y={96} sx={-1} sy={1} />
          <Corner x={88} y={264} sx={1} sy={-1} />
          <Corner x={552} y={264} sx={-1} sy={-1} />
          <line x1={52} y1={180} x2={76} y2={180} stroke="#4a4a50" strokeWidth={1.5} strokeDasharray="4 3" />
          <line x1={564} y1={180} x2={588} y2={180} stroke="#4a4a50" strokeWidth={1.5} strokeDasharray="4 3" />
        </g>
        {/* 选区框:砂化时浮现,凝聚前撤掉 */}
        <g opacity={burst * (1 - seg(t, 0.55, 0.64))}>
          <clipPath id={cid}>
            <rect x={BX} y={BY} width={BW} height={BH} />
          </clipPath>
          <g clipPath={`url(#${cid})`}>
            {HATCH_XS.map((x) => (
              <line key={x} x1={x} y1={BY + BH} x2={x + BH} y2={BY} stroke="#2c2c31" strokeWidth={1} />
            ))}
          </g>
          <rect x={BX} y={BY} width={BW} height={BH} fill="none" stroke="#55565c" strokeWidth={1} />
          <Handle x={BX} y={BY} />
          <Handle x={BX + BW} y={BY} />
          <Handle x={BX} y={BY + BH} />
          <Handle x={BX + BW} y={BY + BH} />
        </g>
        {/* 文字组:整行字与终字标同走滤镜链 + 白色辉光 */}
        <g
          style={{
            filter: `url(#${fid}) drop-shadow(0 0 ${4 + glow * 20}px rgba(255,255,255,${Math.max(0, glow) * 0.9}))`,
          }}
        >
          <text
            x={320}
            y={191}
            textAnchor="middle"
            opacity={1 - cond}
            style={{
              fill: '#eceef2',
              font: "500 33px Inter,'Helvetica Neue',system-ui,sans-serif",
              letterSpacing: '2.5px',
            }}
          >
            {'{ ACME. Now Live }'}
          </text>
          <text
            x={320}
            y={198}
            textAnchor="middle"
            opacity={cond}
            style={{
              fill: '#fff',
              font: "800 54px Inter,'Helvetica Neue',system-ui,sans-serif",
              letterSpacing: '4px',
            }}
          >
            ACME
          </text>
        </g>
      </svg>
    </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/outro/grain-dissolve/GrainDissolve.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