火柴人音乐MV(绘画3D + 代码人群)
example/中的示例:"人群中的孤独者"——越南书街的两个镜头(白天、黄昏),日本雨夜商业街的四个镜头,以及结束时镜头转向天空的延时摄影片段。30秒,720帧,24fps。
该技能基于painted-3d-short(https://github.com/thuann-vn/claude-painted-3d-shorts):其`p3d.py`构建粗模、渲染静帧、投影绘画并每帧调用你的设置器。请先阅读该技能。此技能增设人群。
文件
tools/stick.py— 人群。setter = stick.make(globals(), "<location>"),将其传递给render(SHOTS, [setter])。tools/gait.py— 行走动画(仅依赖numpy,无需Blender即可测试)。tools/post.py— 对渲染帧进行最终后期处理。example/scene.py、example/book.py— 两个场景及其镜头。example/jobs.json— 重绘提示词。example/paint/— 绘画文件。example/m2m.json— 空闲动作片段(Mesh2Motion,CC0协议)。
工作流程
- 先选音乐:剪辑所需的30秒片段,找出歌词时间点,根据时间切割镜头(
scene.py和stick.py中的CUT)。 - 粗模 + 静帧 + 重绘 + 投影:步骤与painted-3d-short完全相同。绘画中不包含人物。
- 在
stick.py的SHOTS中设置每个镜头:主角起始位置、方向、walk或stand、面部俯仰角、车道半宽、气泡半径、边缘站立人群、侧街行人(cross)。 - 测试单帧:
NTEST=2 STICK_OFF=<frame> blender -b --factory-startup -P scene.py -- --proj --shots 4。检查效果。 - 完整渲染(最多启动3个Blender进程),运行
post.py,使用ffmpeg与音乐片段拼接。 - 在宣称流畅前进行量化评估:计算每镜头的连续帧间绝对差平均值;若最大值÷中位数超过约2,说明有异常(找到该帧并检查)。
人群构建方式(stick.py)
- 真实几何体置于场景内部,因此绘画街道能正确遮挡。所有人的肢体合并为一个六棱柱网格,每帧通过numpy →
foreach_set重写顶点。头部为球体,使用相机空间的3色调Cel着色器加反向轮廓线。200人在EEVEE中每帧渲染时间远低于一秒。 - 头部中心位于颈线(
neck + dir × radius × 0.86),或头部脱离身体漂浮。 - 照明:每个位置一组灯光(位置、颜色、强度)。每个头部获得总和作为其对象颜色;按最大通道归一化,不截断(截断会使所有颜色变白)。投射阴影 = 从最近顶灯(长度限制)或日光下的太阳方向将相同肢体网格投影到地面;附有软接触斑点。
- 雨伞(雨景):每人一个8片圆顶对象,颜色取自对象颜色,三分之一为透明乙烯材质;右手调整为握伞柄姿势;伞面投下圆形阴影。主角不持伞。
- 手机:左手贴耳,脸颊处一块薄板,夜间发光。
- 雨:镜头前方至少1.9米处的一个面向相机的薄四边形盒子。
行走动画(gait.py)
在线条人物上使用动作捕捉效果杂乱(颈部连接的手臂过度展开,手部噪声大)。因此采用程序化行走:
- 脚部植足:支撑脚停留在原地,身体从上方经过;地面位置由行走距离计算,因此任何速度均无滑步(实测:每帧小于0.2毫米)。
- 髋部在支撑腿上方抬高,双支撑阶段降低;使用2骨骼腿部IK,膝盖向前。
- 手臂从颈部自然垂落,紧贴身体,与腿部反向摆动;头部保持平稳。
- 参数:
normal、stroll、brisk(步幅、步频、手臂摆动幅度、身体前倾因人而异)和hero(小步、低头、手臂自然下垂)。实际步频:stroll 80–105步/分钟,normal 105–125,brisk 125–140。
高价换来的人群规则
- 每个行人在整个镜头中保持单一直行车道。 在镜头中转向(绕主角、绕镜头)会导致人物侧向冲撞。相反,在镜头开始前,模拟每个行人在镜头中的运动,如果他会进入主角的空圈、靠近镜头或站在镜头与主角之间,则将其车道移动一次。
- 循环必须延伸至视线之外。 从相机后方约6米填充至前方约50米,仅在包装处淡出。在画面内包装是人物跳跃到另一位置。
- 绝不缩小或淡出画面内的任何人物。
- 呈现“拥挤”感的密度:每米6米宽的街道2–2.7人。
- 只有两个关键帧的姿势循环会使人物抖动:将其保持静止(
timeScale = 0)。 - 站立的人物从不静止:呼吸、缓慢重心转移、面部转动。
- 情侣并肩行走,使用相同参数,间隔半个周期。
延时摄影(整部影片)
- 每个镜头中人群的时钟以10倍速度运行(
LAPSE、warp());只有主角生活在实时中。这种对比是影片的核心创意。 - 每帧渲染6个子帧(
SUB;scene.py中镜头帧数×6);post.py在线性光域中平均它们,使人流变成拖影,灯光保持明亮。30秒 = 4320帧渲染,三个Blender进程约需25分钟。
绘画内的相机运动
投影绘画只能承受小幅前向推轨。但渲染相机可以自由地在绘画内进行旋转(TURN)、变焦(ZOOM IN)和滚动(ROLL):纯旋转无视差。stick.py中的CAMK为每个镜头保存关键帧——(镜头片段、变焦、平移、倾斜、滚动、对准主角)——在p3d的look()之后应用;平移和倾斜被限制在变焦后剩余的画面空间内,滚动帧则通过变焦刚好填满画面。每个镜头只做一种有理由的运动:
- 起始紧贴主角,向上倾斜并拉开,展现拥挤街道;
- 他朝我们走来时,缓慢推进至面部;
- 从霓虹灯向下进入街道,直至找到他;
- 推进时倾斜地平线然后稳定;
- 横摇穿过过往雨伞,落在他身上;
- 从上方:紧贴光秃头顶,旋转,在雨伞上方拉开;
- 结尾:一个极宽的绘画画面(14mm,向上瞄准约20°)和一个28mm渲染相机,从他身上向上倾斜至天空。
如需环绕或真正的起重机运动,请为该镜头额外绘制宽幅绘画。
最终后期处理(post.py)
对真正明亮区域添加辉光、暗角、颗粒(暗部更强)、少许色差、缓慢手持漂移(延时摄影除外)、每个片段切换时放大+闪光,以及随歌曲节拍(音频低频段,逐帧分析)的小幅变焦和辉光脉冲。每个特效保持轻微;组合起来效果足够。
音乐
使用您拥有版权的音乐;此仓库不包含音频。CLIP=/path/clip.m4a python post.py读取歌曲用于节拍脉冲。如需为已渲染的镜头适配新曲目,在曲目上滑动30秒窗口,选取剪辑时间点起始强度最高的偏移量。示例最终剪接使用了"Thunder & Rain Inst. & Construction Kits" by coruscate(https://ccmixter.org/files/Coruscate/61228),CC BY 3.0。