Communitygithub.com

透视错觉动画

制作日式透视错觉风格的循环3D视频(例如 @garments__),可为任何场景如街道、路口、车站、商店过道、房间、公园或屋顶生成动画。

透视错觉动画 是什么?

透视错觉动画技能用于生成日式透视错觉风格的循环3D视频,这种风格通过长焦镜头将场景压扁成平面,再通过推拉变焦和旋转镜头揭示深度。用户输入任意地点(街道、路口、商店过道、房间、公园或屋顶)后,模型会输出一个遵循此风格的结构化代码项目。该技能适用于 Claude Code、Cursor 等支持构建和渲染 3D 场景的 AI 助手。它基于 Three.js 在浏览器中渲染,通过调整相机参数(焦距、位置、视角)和精心编排的动画时间线,实现“扁平插画 → 3D 深度”的视觉效果。技能还提供了完备的模板(白天街道、超市过道、夜晚路口)、渲染脚本和检查工具,确保输出视频达到专业水准的循环动效。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/wsxiaolin/perspective-illusion/tree/main

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

透视错觉动画 是做什么的?

风格概述

一架水平放置的相机使用超长焦镜头(从400米外约2°视角)将景深场景压扁成一张平面绘画。然后进行推拉变焦:镜头焦距变宽,同时相机向前移动,使主体大小保持不变,但背后的空间被拉伸打开。相机还会旋转到转角视角。每个转角视角初始是扁平的,然后展开为3D。在视角扁平时,物体在深度方向悄悄滑动,无人察觉,因此当深度恢复时,一排物体变成了斜线。整个场景以干净的黑白线稿风格绘制,使用平涂二分着色、排线阴影和手绘天空,并完美循环。

快速开始

mkdir -p ~/Movies/edits/<date>-<name>/stills && cd $_
printf '{"type":"module"}' > package.json && npm i [email protected] [email protected]
cp -r ~/simcraft/skills/video/perspective-illusion/templates/* .
cp scramble_night.html scene.html                                # 或选择最接近的模板(见下文),然后编辑
node render.mjs scene.html x --stills=0.3,1.5,3,6,8.5           # 每个镜头的静帧,保存至 stills/
node render_fast.mjs scene.html preview.mp4 --preview            # 整段循环,半尺寸,约1.5分钟
node render_fast.mjs scene.html final.mp4                        # 全质量,10秒约需30分钟
python3 ~/simcraft/skills/video/vox-explainer/scripts/motion_check.py final.mp4
python3 ~/simcraft/skills/video/perspective-illusion/scripts/blink_map.py ffmpeg final.mp4 blink.png

在静帧和预览上修复问题。仅在预览通过批准后渲染全质量。若 ffmpeg 不在 PATH 中,请设置 $FFMPEG。

模板

从最接近新场景的模板开始。保留其相机支架、时间线、后期通道和渲染钩子,仅替换场景部分(即 // ---------- kit 和 // ---------- cameras 之间的所有内容)。

模板场景渲染器
street.html, street_plus.html白天的街道,包含电线杆、圆锥筒、人行横道和红色天空。street_plus 增加了细节和转角视角。render_smooth.mjs
market.html, market_real.html超市过道,绘制风格和真实风格。render_smooth.mjs (market.html: render.mjs)
scramble_night.html夜晚大雨中的大型路口,有行走的人群、潮湿反射和广告屏幕。它包含了所有加速优化。render_fast.mjs

对于新场景,scramble_night.html 是引擎的最佳基础,因为它是唯一包含快速渲染器钩子的模板。删除其夜间附加功能(见 reference.md)可获得白天效果。

第1步:构建一个能欺骗眼睛的场景

任何地点只要具备以下要素即可:

  • 一条长轴。 沿 -z 轴布置场景(街道、过道、站台、走廊),相机沿着它看。沿此轴的深度是长焦隐藏的部分。
  • 不同深度上的重复物体。 电线杆、灯柱、货架、柱子、标志牌、树木、人。在长焦镜头下它们会叠成一个平面。排列整齐的电线杆融合为一根,远处立面上的标志堆叠成一堵墙。
  • 可交换物体。 3到6个相似物体(圆锥筒、人、垃圾桶、椅子、盒子),其排列在“一行”和“对角线”之间切换。在扁平视角中它们也可以被绘制得更大(cs),以便在压扁时比例正确。
  • 以米为单位的真实世界比例。 镜头和对焦平面只有在真实比例下才有意义:一个0.75米的圆锥筒,1.7米的人,3.2米的一层楼。
  • 在转角视角可见的细节。 每个转角视角都会揭示建筑侧面、物体背面和地面。填充它们:侧窗、排水管、电缆、路缘石、汽车下方的接触阴影。扁平视角隐藏粗糙,转角视角则会暴露。
  • 使每个细节成为一个表面的一部分(纹理、堆叠的套管),或者保持不同表面之间至少0.4米距离。从400米外看,相距几厘米的表面会闪烁。

第2步:编排相机运动

相机从不倾斜。它保持水平,使垂直线条垂直,如同建筑师的图纸。每个镜头都是一个普通对象:

{ x, y, zf, W, fov, H, el, yaw, lay, cs }

键含义
x, y相机的侧向位置和高度(米)
zf, W对焦平面,以及该平面上的画幅宽度(米)。在改变 fov 时保持 W 不变就是推拉变焦。
fov水平镜头角度。约2°是扁平,25°至90°是景深。
H镜头移位:地平线的位置(-1底部,0中部,1顶部)。
el通过抬高相机并移动镜头来实现俯视,绝不倾斜。
yaw围绕焦点旋转以获得转角视角。
lay, cs可交换物体的布局(0行,1对角线)及其扁平视角的缩放。

有效的镜头词汇表:

镜头设置
扁平fov 约2,H 0。整个场景读作一张立面图。
虫眼视角膝盖高度,fov 90,地平线压低(H 约-0.7)。
高视角相机在中等焦距下置于高处,地平线在画面之上。
转角视角yaw ±18至35°,el 9至50°。它成对出现:先是长焦版本(fov 4),然后同一个视野的广角版本(fov 25至45)。
对角线低视角中等焦距,展示可交换物体的深度。

循环结构。 扁平,然后一个景深视图,接着一个转角视角(扁平到3D),回到扁平(在此处交换物体),一个高对角线,另一个转角视角,一个低视角,再回到扁平。循环结束于它开始时的镜头。

时间。 所有参数通过一个单调三次(Fritsch-Carlson)曲线在关键帧时间过渡,因此速度从不跳跃。在 log 空间中插值 W 和 fov。添加微小的“漂移”镜头(W × 0.96)使停顿有呼吸感而非静止。10秒感觉流畅,而7秒感觉仓促。要改变时长,缩放关键帧时间而不是重写它们。

在扁平时作弊。 只在扁平停顿期间改变 lay。如果在物体移动时相机也在深度方向上移动,技巧就会暴露。

第一个转角视角应该朝主体对象(一辆车、一个标志)没有覆盖的一侧转,这样揭示时会展示新内容。

第3步:外观

页面渲染四个通道:颜色、法线带深度、纯色(无光照)和仅阳光的阴影遮罩。最终着色器将它们组合起来。

  • 墨线 在深度(二阶差分)、法线或纯色变化处生成。近处加重线条。地面标记使用细灰色线条。在单个像素覆盖数米或远处纹理变成噪点的地方,颜色线条渐淡。
  • 二分着色。 亮面和暗面,无渐变。将遮罩光照设置为 2*PI,因为 three.js 将 Lambert 除以 pi,否则一切都会变成暗面。
  • 仅投射阴影使用排线,排线锚定在世界空间(而非屏幕空间),并通过 fwidth 淡化。
  • 根据视线方向绘制天空,而非屏幕位置,以便云朵在相机移动时保持不变。保持云朵边缘柔和。
  • 调色板。 6到10种纯色加上一种强调色(红色天空、黄色标志)。要匹配参考图,使用 k-means 从其帧中提取颜色。
  • 灯光。 一个低角度硬质太阳,使主要主体保持照明并在地面上投下长阴影。来自高侧的太阳会使整个场景陷入一个大阴影中。

关于夜间变体(月光、亮窗、湿润反射、辉光、雨、调色)、人群行走或广告屏幕和标志,见 reference.md。

要从 scramble_night.html 获得白天的外观:

  • 将月亮换成温暖的太阳,提高环境光。
  • 删除点光源循环。
  • 从后期着色器中移除湿地面块、雾线和夜间调色。
  • 移除雨,并将窗户的 emissiveIntensity 设为0。
  • 绘制一个白天的 skyAt。

第4步:细节调整(决定作品优劣)

初版总是看起来很空旷。优秀的版本来自精心设计的调整轮次,每轮都在静帧上检查后再进行下一轮。

  1. 形体。 真实比例的主要体积、长轴、可交换物体和相机时间线。检查扁平镜头是否真的读起来扁平,以及每个移动是否都有揭示。
  2. 结构。 真实场所应有的一切,基于现实世界的逻辑建造。对街道而言,包括路缘石、排水沟、人孔、带支架和箱体的电线杆、双面标志、围栏、垃圾桶、自动售货机、自行车架、架空电线、窗框、窗台和空调外机。查看真实场所的照片,在建造前列出所有元素。
  3. 磨损和故事。 使其“有人居住”的小物件:贴纸、胶带、代码绘制的文字海报、水渍、靠着的自行车、晾晒的衣物、猫、咖啡杯。在相机停留的每个表面上至少添加3个。
  4. 转角视角审查。 对每个转角视角,渲染宽幅静帧并填充任何看起来空旷的地方。通常是建筑侧面、标志背面、屋顶(水箱、栏杆、天线)和物体之间的地面。
  5. 光和影。 确保每个物体都投射出可读的阴影,在停放物下方添加接触阴影,并确保每个主要形体的阴影侧都进入了二分着色步骤。仅在一个视角中出现的阴影是对相机移动的奖励。
  6. 生活(可选)。 匀速行走的人、火车或汽车、屏幕、鸟、旗帜。所有东西循环整数倍。
  7. 润色。 调色板和谐、线宽、天空、暗角和调色。然后对最终版本进行闪烁检查。

密度规则:从扁平的远焦镜头看,画面应该从边缘到边缘都感觉充实,像一张密集的插图,没有大片的空白墙壁或地面区域,除非是刻意让眼睛休息的地方。

质量标准

在全渲染之前,在每个关键时间(以及移动的中间点)检查静帧,对照以下列表。修复任何不通过项,然后再次检查。

  • 扁平镜头读起来像一张平面画。没有任何东西透露深度。
  • 每个移动都以一个揭示结束:隐藏的对角线、侧街、原本相隔很远的物体。
  • 转角视角从扁平开始然后打开,第一个转角远离主体对象。
  • 线条清晰均匀,无双线、无辉光面上的墨水、无远处纹理上的噪点。
  • 二分着色在每个主要形体上清晰可见,排线仅出现在投射阴影中。
  • 没有任何物体读起来像裸露的几何体。每个盒子都有装饰、接缝、底座或标签。
  • 文字可读且拼写正确,使用真实字体,品牌为虚构。
  • 天空与场景的光照方向和颜色匹配。
  • 调色板有限且和谐,有一个明确的强调色。
  • 循环接缝不可见(第0帧等于最后一帧),且无任何物体突然出现或消失。
  • 预览无卡顿。在最终版本上,blink_map 计数低,且闪烁的部分是预设的运动(雨、屏幕)。

在审查静帧时,像插画师一样看它们:说出每帧最弱的三个地方,修正它们,然后重复。不要满足于“它能用”。

第5步:渲染和检查

  • render_fast.mjs 在 GPU 上渲染每一帧,作为抖动子渲染的平均值:4次用于抗锯齿,在快速移动时最多32次用于无间隙运动模糊,大小由 window.probe 点移动的距离决定。使用 --preview 获得半尺寸30fps预览。--from、--to 和 --fps 也可用。
  • render_smooth.mjs 对没有 renderMany 的页面提供相同质量,但在 ffmpeg 中平均,因此慢数倍。
  • 加载时按材质合并静态网格(BufferGeometryUtils.mergeGeometries)。一个场景从7,654次绘制调用减少到530,大约快了7倍。保持任何移动的物体不合并。
  • 检查最终版本,使用 motion_check.py(无冻结)和 blink_map.py,它统计在几乎静止的帧中闪烁的像素数。在改动前后对比计数。预览默认会有闪烁,因此仅对最终版本判断闪烁。

页面接口契约

任何场景页面只要提供以下钩子,即可与渲染器配合工作:

钩子功能
#frame被截屏的画布。
window.READY在资源和字体加载完成后设置。
window.render(ms)在时间 ms 绘制帧。
window.probe(ms)返回几个世界点在屏幕上的位置,用于确定运动模糊的大小。
window.setJitter(x, y)镜头亚像素偏移量(像素),通过 projectionMatrix.elements[8] 和 [9] 设置。
window.renderMany([[ms, jx, jy], ...])在一次调用中平均多次渲染(render_fast 需要)。
?scale=.5以半尺寸渲染。

所有动画必须是 ms 的纯函数,并按循环长度周期性变化。对于步进、爬行和雨,使用每循环整数倍周期。

闪烁和故障

原因修复
屏幕空间中的任何内容(抖动、排线、噪点、雾效按相机距离)将其锚定到世界空间或视线方向。
相距几厘米的表面合并成一个表面,或保持至少0.4米距离。
重叠的共面平面(道路标记、地板块)将标记绘制到一个平面上,并使各块边缘对边缘相接。
亚像素细节(栏杆、网格、电缆、雨)每帧抖动抗锯齿,并按每像素米数淡化密集线条。
每帧一次渲染绝对不要在最终渲染中这样做。
云朵或反射上的硬边柔化它们,并在屏幕边缘淡化效果。
循环包裹或突然出现的运动物体在循环中最快相机移动时包裹它们,通过 window.probe 找到。

陷阱

  • 单行代码中的 // 注释会吞掉该行剩余部分(如 scene.add、灯光)。请使用 /* */。
  • 添加到场景根节点的辅助工具(如 plane())忽略当前组,可能落到其他物体上方。应在组内构建。
  • 将自发光面(屏幕、标志面板)排除在纯色和阴影通道之外,否则每个字形都会被上墨并变暗。
  • ffmpeg -stream_loop ... -c copy 制作的循环在某些播放器中会提前停止。请重新编码。
  • 对于 X(推特),32:9 并排展示太宽无法上传。请垂直堆叠。
  • 仅使用原创角色和虚构品牌。模仿参考技法的同时,绝不抄袭其作品。

相关技能