mcpField
3
3d-toy-character

3d-toy-character

github.com/phantomtonyt创意与媒体24175 次安装4 周前更新
代码仓库

关于 3d-toy-character

3D Toy Character 是用于制作软圆润玩具角色短视频的视觉风格技能,面向需要以角色演绎创意的制作者。它提供从分镜、动作到 Three.js 等制作路线的指导。

主要功能

  1. 01玩具质感角色造型规范
  2. 02分镜与文字可读性规划
  3. 03因果式角色动作设计
  4. 04Three.js、SVG 或 Blender 路线
  5. 05镜头、布景与光照指导

适用场景

  1. 01制作竖屏玩具角色短片
  2. 02让品牌角色演绎单一创意
  3. 03规划角色动作与视频节拍

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/phantomtonyt/motion-design-skills --skill 3d-toy-character

安装后在 Claude Code 中输入/3d-toy-character确认已加载。

详细介绍

3D 玩具角色

制作短视频,让一个柔和圆润、玩具般的 3D 角色演绎一个想法。整体应有触感、友好且略带俏皮感,就像在摄影棚灯光下的高端乙烯基玩具。这是一项视觉风格技能,而非通用 3D 建模技能。

选项

选项默认值备注
format竖屏 1080×1920或横屏 1920×1080、方形 1080×1080
duration15 s8–30 s 效果最佳
toneplayfulcalm(缓和的缓动、更长的停留)、playful(默认)、hyper(更明显的挤压、更快的节奏、更多镜头移动)
character圆形玩具角色或用户的品牌角色(匹配其轮廓和颜色)

简述(规划前回答)

  1. 角色想要什么,或者核心信息是什么?
  2. 第一秒的钩子是什么(姿势加标题)?
  3. 出了什么差错或有什么意外,最终回报是什么?
  4. 视频以哪句台词收尾?

然后编写节拍列表(时间、画面内容、角色动作、文字、声音),各段时长相加应等于目标时长。

阅读时间表(与节拍列表一起编写)

为每个节拍写明观众必须理解的一件事(“机器人不会飞”“缓存未命中”),以及这项信息保持清晰可读的时长。一次只传达一项信息。文字时长底线:标签至少停留 0.8 s;句子完整显示后,每个词约需 0.3 s。如果文字无法在给定时间内读完,就删减文字,而不是加快速度。

动作规范

  • 动作应有因果关系:角色会对画面中的事物作出反应,每个动作都有起因和结果。
  • 角色不能完全静止:等待时要呼吸、眨眼、改变重心。这些待机动作本身就是表演,而非填充内容,因此要轻微且不规则。
  • 避免动作同步:手臂不要像镜像一样移动,眼睛、触角和身体也不要在同一帧开始动作。各部分之间错开 2–4 帧。
  • 过冲和挤压应体现在角色身上(落地、弹出、惊讶)。镜头和文字则应使用平滑的缓出。
  • 每个场景只保留一个主要移动方向,并在切换场景时让角色或道具延续移动,不要交叉淡化。
  • 在最终回报出现前,加入 0.3–0.75 s 的定格姿势,让效果充分呈现。

风格规则(始终遵守)

这些规则定义了此风格;即使更改颜色或故事,也要保留。

  1. 头部占总高度的 55–70%,因为大头会显得可爱且像玩具。
  2. 所有形体都应圆润,不带尖锐边缘,让轮廓呈现为几团柔和的形体。
  3. 材质为光滑乙烯基:粗糙度 0.35–0.5、轻微清漆层、金属度为 0。金属质感或光泽感会让画面像产品渲染图。
  4. 角色下方始终要有柔和的接触阴影,这能让角色看起来是 3D 的。
  5. 使用大型柔和的主光、较弱的补光和一条纤细的轮廓光。不要使用硬阴影。
  6. 每个动作都要经历预备、移动、过冲、稳定几个阶段,这样才有重量感。

默认值(可自由更改)

颜色、角色设计、背景、镜头角度、时长和文字。用户提供品牌配色或角色时,以用户提供的为准。优先顺序:用户的品牌或规格,其次是明确请求,最后是此文件中的默认值。

设置(一次性)

node -v                      # needs 22+
ffmpeg -version              # needs any recent build
npx hyperframes init my-video --non-interactive --resolution portrait && cd my-video   # landscape for 16:9

在现有的空文件夹中使用 npx hyperframes init . --non-interactive --resolution portrait。然后用下面的模板替换生成的 index.html。Three.js 和字体会在合成内容中从 CDN 加载(渲染时可使用 Google Fonts <link>);无需安装其他内容。Blender 仅用于离线流程。

构建前先确定

在编写代码前,先确定以下四项,并用一行告诉用户。

  1. 画幅比例: Reels、Shorts 和 TikTok 用 9:16(1080×1920);YouTube、网页和幻灯片用 16:9(1920×1080)。默认使用 9:16。
  2. 方案: 参见表格。默认使用 Three.js。
  3. 角色: 剪影、主色,配饰最多一种(天线、耳朵、围巾)。如果用户有品牌角色,请匹配其剪影和颜色。
  4. 节拍: 15 秒设置 3–6 个节拍。每个节拍只安排一个动作。
需求SVG + GSAPThree.js(默认)Blender
简单的吉祥物反应可以可以可以
真实的转身或纵深旋转不可以可以可以
柔和光照和接触阴影可模拟可以可以
光泽或清漆材质可模拟可以可以
快速迭代、随处渲染可以可以较慢
电影级效果、复杂几何体不可以部分支持可以

选择能实现用户所需外观的最简单方案。如果角色从不转身,使用带渐变的 SVG 即可。如果角色会转身,或光照效果是重点,则使用 Three.js。

视觉语言

比例

  • 头部:占总高度的 55–70%。头部可以就是整个身体。
  • 眼睛:大而简洁,位置偏低,约在脸部高度的 55–60% 处。每只眼睛加一个大的白色高光和一个小的白色高光。
  • 四肢:短、粗、圆润;手做成连指手套状;不显示关节。
  • 避免尖锐边缘。所有边缘都做倒角或圆滑处理,让剪影呈现为几个圆润的形体。
  • 面部部件应贴在头部表面,而不是嵌入其中。对于半径为 r、深度缩放系数为 sz 的球形头部,将眼睛、嘴巴和面罩放在 z ≈ r·sz 再加上部件自身深度的位置。放大查看快照,确认没有部件被埋入头部。

材质

  • 使用光滑的乙烯基或塑料材质,粗糙度约为 0.35–0.5,并添加轻微清漆效果(约 0.3)。除非用户提出要求,否则不加纹理。
  • 切勿使用金属材质。金属度保持为 0。

光照

  • 从左上方打入一盏大型柔光主光;从另一侧补一盏较弱的补光(约为主光的 30%);从后方打入一盏细窄的轮廓光(约为主光的 60%)。
  • 在角色下方添加柔和的接触阴影。这一细节能让角色呈现出 3D 效果。
  • 避免硬边点光源阴影、戏剧化的低调光照、强烈泛光和恐怖风格的彩色灯光。

默认调色板(仅为默认值,并非固定风格):

  • 背景:浅色、略偏冷的柔和色调(例如 #e8f0f7),或柔和的双色渐变。
  • 角色:一种饱和但柔和的主色,并搭配更浅的腹部或脸部色块。
  • 点缀色:一种用于道具和 UI 的对比色系柔和色调。
  • 阴影:向背景色偏移,绝不使用纯黑。

动画语言

动作应柔和、略微夸张,并符合物理规律。每个动作都遵循预备 → 主动作 → 超调 → 稳定的过程。

动作时长说明
微小反应(眨眼、瞥视)0.2–0.5 s每隔 2–4 s 眨一次眼,间隔不规则
手势(挥手、拍手、指点)0.5–1.2 s手臂先动,身体晚 2–3 帧跟上;对于大头角色,将抬起的手臂向前倾斜(rotation.x ≈ 0.8),让手从头部前方经过;挥手时让手臂避开道具
跳跃0.5–0.8 s挤压 0.08 s,然后上升、下落、落地时挤压,最后稳定
入场0.8–1.5 s缩放弹入(0 → 1.1 → 0.95 → 1),以挤压动作结束;只有在可见的地面或画面边缘先遮住角色时,“从下方升起”的效果才明显
歪头(好奇)0.3–0.4 s倾斜 6–10°,保持,然后复位
待机持续按 2–3 s 的周期呼吸式缩放 ±2%
  • 保持体积感:身体纵向拉伸 10% 时,宽度缩小约 5%。
  • 接触阴影要随之变化:角色上升时阴影缩小、变淡,落地时恢复完整。
  • 不要让所有元素都弹跳。只在入场和落地时使用超调,并让待机动作保持平静。

构图

  • 9:16: 角色位于中间三分之一处,面部位于 y 500 到 1100 之间。文字和面部应位于 x 60–960、y 270–1250 范围内。底部约 670 px 会被平台 UI 遮挡,因此字幕应放在其上方。
  • 16:9: 角色位于一侧三分之一处,文字或 UI 占据另外三分之二;或者让角色居中,文字放在下方。保留 5% 的边距。
  • 摄像机:略高于视线水平,视场角为 30–40°,不要使用极端广角。
  • 取景:视场角为 32° 时,可见高度约为摄像机距离的 0.57 倍。对于一个约 3.3 个单位高的角色,摄像机 z ≈ 17–19 时,角色在 9:16 画面中约高 700 px。道具也在画面中时,将摄像机目标横向偏移约 0.45 个单位,并让角色的脚保持在 y ≈ 1300 以上。

文字

  • 使用粗体圆角无衬线字体(例如来自 Google Fonts 的 Nunito 800),9:16 画面中的标题字号为 64–90 px;多行字幕的行高至少为 1.15。
  • 文字对比度至少要达到 3:1(hyperframes check 会强制检查)。如果品牌色在品牌背景上的对比度不达标,就将其用于形状、填充和角色,并在文字中使用更深的同色调,或近黑色。
  • 在时间线外使用 gsap.set(...) 设置文字的隐藏初始状态,然后在时间线上将其动画显示。(在时间 0 调用 tl.set 会触发 lint 警告。)

打造吸睛效果

空背景上的角色看起来像测试渲染,而不是视频。每个输出都必须包含以下元素:

  • 布景,而非空无一物。 添加天空渐变(置于透明画布后方的 CSS),再加上一个使用相同乙烯基材质的简单布景元素(屋顶边缘、书桌、浮岛),以及几个用于体现比例的道具。
  • 三个景深层: 远景(云朵、山丘、雾气)、中景(布景)、近景(道具或从摄像机前掠过的粒子)。摄像机移动时,每层以各自的速度移动。
  • 移动的摄像机。 在开场吸引点推进,跟随动作,并在高潮处拉远。使用平滑关键帧设置动画;角色离开布景时,让摄像机继续跟随:
const smooth = (x) => x * x * (3 - 2 * x), mix = (a, b, x) => a + (b - a) * x;
const keys = (t, ks) => { if (t <= ks[0][0]) return ks[0][1];
  for (let i = 1; i < ks.length; i++) if (t <= ks[i][0]) return mix(ks[i - 1][1], ks[i][1], smooth(Math.min(1, (t - ks[i - 1][0]) / (ks[i][0] - ks[i - 1][0]))));
  return ks[ks.length - 1][1]; };
// camera.position.y = keys(t, [[0, 1.8], [1.6, 2.2], [10, 3.6], [11.4, 9], [13.4, 1.8]]);  same for x, z and the look-at target
  • 一个发光点缀。 在故事的关键时刻(“顿悟”时刻)加入一个带点缀色的发光灯泡、屏幕或道具,以及一盏小型点光源。
  • 每个节拍都要有次要运动: 飘动的云朵、撞击时扬起的尘埃、闪光或星星,以及动作之后晃动的天线和耳朵。
  • 精心设计标题出场时刻。 将粗体圆角文字放在带柔和投影的白色圆角卡片上(也能解决天空背景杂乱时的对比度问题),并以超调效果弹入。角色移动经过该区域之前,将卡片移出画面。
  • 以循环结尾: 最后的姿势和构图要与开场一致。
  • 质量门槛: 如果任何快照看起来像占位内容(纯色背景、角色静止、没有任何运动),就说明尚未完成。

构建:Three.js 路线(默认)

使用一个 HTML 组合。HyperFrames 通过 hf-seek 事件告知 Three.js 要绘制的时间,因此每一帧都根据时间计算,不会自行运行时钟。

<!doctype html><html><head><meta charset="UTF-8">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<style>html,body{margin:0;width:1080px;height:1920px;overflow:hidden;background:#e8f0f7}
#root{width:100%;height:100%;position:relative}canvas{position:absolute;inset:0;width:100%;height:100%}</style></head>
<body><div id="root" data-composition-id="main" data-start="0" data-duration="15" data-width="1080" data-height="1920">
<canvas id="three-layer"></canvas>
<div id="caption" style="position:absolute;left:60px;right:120px;top:300px;text-align:center;font:800 76px/1.1 system-ui,sans-serif;color:#1f2a30">Hello, I'm a toy!</div></div>
<script>
  // HyperFrames needs one registered paused timeline; put text/UI tweens on it, 3D is driven by hf-seek below.
  const tl = gsap.timeline({ paused: true });
  tl.fromTo("#caption", { opacity: 0, y: 30 }, { opacity: 1, y: 0, duration: 0.4, ease: "back.out(2)" }, 1.2);
  tl.to("#caption", { opacity: 0, duration: 0.3 }, 12); tl.set({}, {}, 15);
  window.__timelines["main"] = tl;
</script>
<script type="module">
import * as THREE from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
const W = 1080, H = 1920;                                   // 16:9: 1920, 1080 and move the camera back less
const renderer = new THREE.WebGLRenderer({ canvas: document.getElementById("three-layer"), antialias: true, alpha: true });
renderer.setSize(W, H, false); renderer.setPixelRatio(1);
renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(32, W / H, 0.1, 100); camera.position.set(0, 2.4, 16); camera.lookAt(0, 1.3, 0);

const vinyl = (hex, rough = 0.42) => new THREE.MeshPhysicalMaterial({ color: hex, roughness: rough, clearcoat: 0.3, clearcoatRoughness: 0.4 });
const ball = (r, mat, x, y, z, sx = 1, sy = 1, sz = 1) => { const m = new THREE.Mesh(new THREE.SphereGeometry(r, 48, 32), mat); m.position.set(x, y, z); m.scale.set(sx, sy, sz); m.castShadow = true; return m; };
const C = { body: 0x35b5a7, belly: 0xc9f2ea, cheek: 0xffb4a2, eye: 0x1f2a30 };   // defaults; replace with brand colours

const hero = new THREE.Group(); scene.add(hero);
const bodyG = new THREE.Group(); hero.add(bodyG);
bodyG.add(ball(1.25, vinyl(C.body), 0, 1.2, 0, 1, 0.92, 0.95));              // head = body
bodyG.add(ball(0.55, vinyl(C.belly, 0.5), 0, 0.62, 0.86, 1, 0.55, 0.35));     // belly patch
const eyes = [-0.4, 0.4].map((x) => {                                        // big, low eyes with two highlights
  const g = new THREE.Group(); g.position.set(x, 1.12, 1.08);
  g.add(ball(0.2, vinyl(C.eye, 0.2), 0, 0, 0, 1, 1.15, 0.55));
  g.add(ball(0.06, new THREE.MeshBasicMaterial({ color: 0xffffff }), -0.07, 0.09, 0.1));
  g.add(ball(0.03, new THREE.MeshBasicMaterial({ color: 0xffffff }), 0.07, -0.08, 0.1));
  bodyG.add(g); return g;
});
[-0.78, 0.78].forEach((x) => bodyG.add(ball(0.17, vinyl(C.cheek, 0.6), x, 0.9, 0.93, 1, 0.55, 0.4)));  // cheeks
const mouth = new THREE.Mesh(new THREE.TorusGeometry(0.1, 0.03, 12, 24, Math.PI), vinyl(C.eye));
mouth.position.set(0, 0.92, 1.17); mouth.rotation.z = Math.PI; bodyG.add(mouth);
const arm = (side) => { const p = new THREE.Group(); p.position.set(side * 1.15, 0.95, 0.15);   // pivot at the shoulder
  const m = new THREE.Mesh(new THREE.CapsuleGeometry(0.2, 0.32, 8, 16), vinyl(C.body)); m.position.y = -0.3; m.castShadow = true;
  p.add(m); p.rotation.z = side * 0.45; bodyG.add(p); return p; };
const armL = arm(-1), armR = arm(1);
[-0.45, 0.45].forEach((x) => hero.add(ball(0.3, vinyl(C.body), x, 0.12, 0.25, 1, 0.55, 1.2)));   // stubby feet

const floor = new THREE.Mesh(new THREE.PlaneGeometry(40, 40), new THREE.ShadowMaterial({ color: 0x5a6b80, opacity: 0.14 }));   // tinted, never black
floor.rotation.x = -Math.PI / 2; floor.receiveShadow = true; scene.add(floor);
// contact shadow that reacts to height (the key-light shadow alone doesn't shrink when airborne)
const blobTex = (() => { const c = document.createElement("canvas"); c.width = c.height = 128; const g = c.getContext("2d");
  const r = g.createRadialGradient(64, 64, 0, 64, 64, 64); r.addColorStop(0, "rgba(60,72,90,0.55)"); r.addColorStop(1, "rgba(60,72,90,0)");
  g.fillStyle = r; g.fillRect(0, 0, 128, 128); return new THREE.CanvasTexture(c); })();
const blob = new THREE.Mesh(new THREE.PlaneGeometry(3.2, 1.4), new THREE.MeshBasicMaterial({ map: blobTex, transparent: true, depthWrite: false }));
blob.rotation.x = -Math.PI / 2; blob.position.y = 0.01; scene.add(blob);
const key = new THREE.DirectionalLight(0xffffff, 2.3); key.position.set(-3, 9, 5); key.castShadow = true;   // large soft key, upper left
key.shadow.mapSize.set(2048, 2048); Object.assign(key.shadow.camera, { left: -6, right: 6, top: 6, bottom: -6 }); scene.add(key);
const fill = new THREE.DirectionalLight(0xdfe9ff, 0.7); fill.position.set(5, 2, 4); scene.add(fill);
const rim = new THREE.DirectionalLight(0xffffff, 1.4); rim.position.set(0, 4, -6); scene.add(rim);
scene.add(new THREE.HemisphereLight(0xffffff, 0xcfd8e3, 0.6));

// deterministic helpers: every value comes from t (no Math.random, no Date.now)
const clamp = (x) => Math.min(1, Math.max(0, x));
const seg = (t, a, b) => clamp((t - a) / (b - a));
const backOut = (x, s = 1.7) => 1 + (s + 1) * (x - 1) ** 3 + s * (x - 1) ** 2;
const pulse = (t, a, b) => Math.sin(Math.PI * seg(t, a, b));

function renderAt(t) {
  const enter = backOut(seg(t, 0.2, 0.9), 2.2);                               // scale pop with overshoot
  const hop = pulse(t, 4.0, 4.6), land = pulse(t, 4.6, 4.8), antic = pulse(t, 3.85, 4.0);
  const y = 1.1 * hop;
  hero.position.y = y;
  const squash = 0.1 * (land + antic + pulse(t, 0.85, 1.05));
  hero.scale.set(enter * (1 + 0.8 * squash - 0.05 * hop), enter * (1 - squash + 0.08 * hop), enter);   // keep volume
  blob.scale.setScalar(enter / (1 + 0.55 * y)); blob.material.opacity = 0.35 + 0.65 / (1 + 0.55 * y);   // shadow reacts
  bodyG.rotation.z = 0.12 * pulse(t, 2.0, 2.8);                              // curious tilt
  const blink = (a) => Math.max(0, seg(t, a, a + 0.07) - seg(t, a + 0.07, a + 0.14));
  const b = Math.max(blink(3.1), blink(6.0), blink(11.4));                    // irregular blinks
  eyes.forEach((e) => (e.scale.y = 1 - 0.9 * b));
  const raised = seg(t, 8.0, 8.2) - seg(t, 9.6, 9.8);
  armR.rotation.z = 0.45 + 1.6 * raised + 0.35 * Math.sin(Math.PI * 4 * seg(t, 8.2, 9.6));   // wave
  armR.rotation.x = 0.8 * raised;                                             // hand passes in front of the head
  armL.rotation.z = -0.45 - 0.1 * hop;                                        // follow-through
  bodyG.scale.y = 1 + 0.02 * Math.sin(t * 2.6);                               // breathing
  renderer.render(scene, camera);
}
window.addEventListener("hf-seek", (e) => renderAt(e.detail.time));    // HyperFrames sends the frame time on every seek
renderAt(window.__hfThreeTime || 0);                                      // and stores the current time here for the first draw
</script></body></html>

将文字和 UI 作为 HTML 或 SVG 叠加在画布上,并在已注册的 tl 上为其添加动画(即使所有运动都在 Three.js 中实现,HyperFrames 仍要求提供该时间线)。

此路线的规则:

  • 根元素上必须设置 data-duration。Three.js 场景本身没有时长。
  • 绝不能使用 requestAnimationFrame、setAnimationLoop、Date.now 或 Math.random 来制作动画。每个值都必须根据 t 计算。
  • 在首次渲染前加载所有模型或纹理。带动画片段的 GLTF 角色应使用 mixer.setTime(t) 定位到指定时间。

构建:其他方案

  • SVG + GSAP(无景深): 将角色绘制为带径向渐变填充的 SVG 椭圆(左上较亮,右下较暗),在头部添加白色椭圆形高光,用模糊椭圆作为接触阴影,并使用与上文相同的时序和 GSAP 缓动(入场使用 back.out(1.7),跳跃上升使用 power2.out,下降使用 power2.in)。在 window.__timelines["main"] 上注册一个暂停的时间轴。
  • Blender(离线高品质): 使用细分曲面和倒角建模,采用粗糙度为 0.4、清漆层为 0.3 的 Principled BSDF,设置三点区域灯光和阴影捕捉地面,并按目标纵横比渲染 PNG 序列或 MP4。将其作为 <video> 片段导入 HyperFrames,以添加文字、字幕和声音。

向用户开放的参数

aspect · duration · characterColor · accentColor · background · accessory · beats(时间 + 动作列表)· cameraAngle · captionText。将它们作为常量放在文件顶部,这样修改时只需改一行。

避免

  • 写实的人体结构、小脑袋、细四肢、细致的鼻子或手指。
  • 金属、铬或玻璃质感。它们看起来像产品渲染,而不是玩具。
  • 纯黑阴影或轮廓。
  • 持续弹跳、线性缓动,或所有部位步调一致地运动。
  • 在脸上叠加文字,或在 9:16 画面底部 670 px 范围内放置重要内容。
  • 在屏幕文字或标题中提及工作室或系列名称。改为描述视觉风格。

故障模式

观感原因修复方法
扁平矢量插画没有接触阴影、主光太弱、材质扁平添加柔和的地面阴影,增强主光,添加清漆层和轮廓光
写实的 3D 人物头太小、面部细节过多、四肢太长头部占身高的 60% 以上,面部简化为眼睛和嘴巴,缩短四肢
塑料产品渲染金属度大于 0、边缘锐利、光泽过强金属度设为 0,粗糙度约为 0.4,将所有部分做成圆润造型
动作像机器人线性插值、没有预备动作、时序相同添加预备动作、缓动曲线和过冲,并将手臂错开 2–3 帧
失重感落地时没有挤压变形、阴影没有响应接触时挤压变形,腾空时缩小并淡化阴影,添加稳定动作
色彩浑浊背景太暗、阴影发灰使用柔和的背景色,让阴影带有背景色调,并选用一种饱和的主色
渲染抖动动画读取墙上时钟时间仅在 renderAt 中根据 t 计算

最终渲染前进行验证

npx hyperframes check                                   # lint, layout, contrast, timing
npx hyperframes snapshot --at <one time per beat at the peak of each action, plus one mid-transition>
npx hyperframes render -o renders/<name>.mp4
ffprobe -v error -show_entries stream=width,height,r_frame_rate:format=duration -of csv=p=0 renders/<name>.mp4

查看每一张快照以及 snapshots/contact-sheet.jpg(包含所有帧的单张图片),并确认:

  • 每个节拍中的轮廓都清晰可辨,面部都可见
  • 存在接触阴影,并会随跳跃产生变化
  • 光照一致(左上方有主光,边缘可见轮廓光)
  • 每个动作都有预备和收尾;没有漂浮感
  • 文字位于所选纵横比对应的安全区域内
  • 输出尺寸符合纵横比(1080×1920 或 1920×1080),时长正确

最终渲染前进行自我评估

对快照总览图按以下项目打分,范围为 1–10:吸引力(第一秒)、手机屏幕宽度下的可读性、动作质量、构图、准确性,以及“静音测试”(观众能否在没有声音的情况下用一句话概括内容?)。只修复得分为 7 分或以下的项目,然后重新生成快照并重复评估,直到所有项目都达到 8 分或以上。如果可以,请让另一个只读评估流程负责打分,以免自己给自己的作品打分。

不要应该
在两个内容繁杂的布局之间交叉淡化先移除旧内容,再引入新内容,或让一个物体贯穿整个过程
同时移动多个元素错开动作,每个时刻只突出一个重点
用漂浮或摇摆填补停顿保持静止,或让角色做出反应
为了表现“活力”添加粒子、彩虹渐变或 3D 翻转加入更有力的事件:揭示、撞击或镜头移动
为了赶上时长而急着放入文字删减文字

交付内容

  • 海报帧:选取最稳妥的一帧,并将其烘焙为第 0 帧,这样缩略图就会显示它:ffmpeg -ss <t> -i renders/<name>.mp4 -frames:v 1 poster.jpg,然后运行 ffmpeg -i renders/<name>.mp4 -i poster.jpg -filter_complex "[0:v][1:v]overlay=enable='eq(n,0)'" -c:a copy renders/<name>-final.mp4。
  • 一段 1–2 句、可直接发布的说明文字,点明创意,而非技法。
  • 告诉用户每个节拍发生了什么,以及修改时应调整哪些参数。

参考这些作品

评论

支持 Markdown