Blender 动画 — Python驱动的3D工作流
通过后台模式下的 bpy 驱动 Blender,使用可重新运行的脚本。将 .blend 文件视为脚本的构建产物:脚本是真相来源,文件是可交付物。
阅读 references/BLENDER-PLAYBOOK.md 了解架构和判断原则,以及 references/BLENDER-RECIPES.md 获取可直接复用的代码模式。请对照已安装的 Blender 版本验证每个 API 细节;Python API 在不同版本间会变化。
1. 首先验证环境
blender --version
blender --background --factory-startup --python-expr \
"import bpy, sys; print('BLENDER', bpy.app.version_string); print('PY', sys.version.split()[0]); print('ENGINES', [e.bl_idname for e in bpy.types.RenderEngine.__subclasses__()][:5])"
规则:
bpy属于 Blender 自带的 Python。永远不要pip install bpy,也永远不要用系统 Python 运行场景脚本。- 使用
--python-exit-code 1以便 Python 异常导致 shell 命令失败,而非静默成功。 - 使用
--factory-startup进行干净构建;切勿用它清除用户已打开的项目。 - 每次渲染开始时记录 Blender 版本、渲染引擎、帧范围、分辨率、FPS 和输出路径。
2. 标准调用方式
# 从头构建场景并保存 .blend
blender --background --factory-startup --python-exit-code 1 --python blender/scripts/build_scene.py
# 修改现有项目
blender --background project.blend --python-exit-code 1 --python blender/scripts/update_scene.py
# 渲染已保存的动画
blender --background project.blend --render-anim
# 渲染单帧
blender --background project.blend --render-frame 120
# 安全渲染一段范围(图像序列)
blender --background project.blend --python-exit-code 1 --python blender/scripts/render_frames.py
长时间运行的作业请将输出重定向到 logs/,并且对于可能需要恢复的任何内容,始终优先使用图像序列。
3. 脚本架构
对于小型场景,从一个文件开始。当场景超过大约300行或包含多个镜头时,进行拆分:
blender/
config.py # fps, 帧范围, 分辨率, 调色板, 路径, 种子
build.py # 入口点:重置 → 设置 → 构建镜头 → 保存
scene_setup.py # 世界, 单位, 集合, 渲染设置
geometry.py # 基本体, 曲线, 参数化网格
materials.py # 节点构建器, 可复用的材质工厂
animation.py # 关键帧, 驱动, 程序化运动
camera.py # 绑定, 轨道/推拉, 景深, 构图检查
lighting.py # 主光/补光/轮廓光, HDRI, 自发光点缀
simulation.py # 粒子, 刚体, 布料 — 缓存设置
render.py # 引擎, 采样, 输出, 联系表, 编码
shots/ # 每个镜头一个模块:shot_01.py, shot_02.py
不可协商的原则:
- 幂等性。 重新运行构建仅更新你命名的集合/对象;它永远不会复制几何体或擦除无关的用户内容。
- 命名。
Main_Logo,Cam_Orbit,Light_Key,Emitter_Particles。永远不要使用Cube.003。 - 配置化。 所有可调整的值都存在于
config.py或数据类中,因此一次修订就是一行更改。 - 断言式。 如果资产、相机、字体或输出目录缺失,则大声失败。
- 范围限定。 清理仅删除你拥有名称的集合,前缀例如
MS_。
4. 场景构建
- 优先使用数据 API(
bpy.data.objects.new,bpy.data.meshes.new,collection.objects.link),而不是依赖选择、模式和区域上下文的bpy.ops。 - 当确实需要某个操作符时,请为该版本提供正确的上下文覆盖,并说明原因。
- 通过节点和一个小型工厂函数构建材质;不要为每个对象复制粘贴20个节点的树。
- 使用真实世界比例并设置场景单位系统;错误的比例会破坏景深、物理和光照。
- 保持世界节点树明确(颜色、强度、HDRI),以便渲染结果可在另一台机器上复现。
- 打包或相对化资产。
//assets/...路径加上一个打包文件夹,胜过使用某人下载目录的绝对路径。
5. 动画
有意识地选择机制:
| 需求 | 使用 |
|---|---|
| 手动设定的位置/旋转/缩放/材质变化 | 带有显式插值的关键帧 |
| 跟随另一个属性或表达式的属性 | 驱动(简单且有文档说明的表达式) |
| 重复/参数化运动(轨道、波浪、网格、螺旋) | 基于帧的数学计算,无状态求值 |
| 成千上万的实例、图案、散布 | 几何节点 |
| 物理涌现的运动 | 模拟(缓存,验证完整范围) |
| 沿路径运动的相机 | 曲线 + 跟随路径约束,或关键帧绑定 |
关键帧规范:
obj.location = (0, 0, 0)
obj.keyframe_insert("location", frame=1)
obj.location = (4, 0, 0)
obj.keyframe_insert("location", frame=48)
for fc in obj.animation_data.action.fcurves:
for kp in fc.keyframe_points:
kp.interpolation = 'BEZIER'
kp.easing = 'EASE_IN_OUT'
kp.handle_left_type = kp.handle_right_type = 'AUTO_CLAMPED'
- 有目的地设置插值。默认贝塞尔适用于有机运动;使用
LINEAR表示恒定速度和机械运动;使用CONSTANT表示步进/像素风格。 - 切勿在循环周期上留下意外的缓动——它会在循环点产生卡顿。
- 对于循环:使第1帧和第N+1帧相同,然后渲染
[1, N]。 - 所有程序化内容必须是帧的纯函数:
f(frame)。没有累积状态,没有未设置种子的random。
6. 几何节点和实例化
当你需要规模、变化或非破坏性控制时,使用几何节点:
- 散布/实例化(树木、碎片、星星、人群方块)。
- 参数化阵列、网格、径向图案、伊斯兰几何镶嵌。
- 动画位移、场和每个实例的旋转/缩放。
- 将一个庞大的10,000个对象的Python循环转换为一个节点树。
通过名称使用 nodes.new(...) 和 links.new(...) 构建节点,而不是脆弱的索引。将用户可能要求的旋钮(数量、半径、种子、速度)暴露为修改器输入,以便修订无需代码更改。
7. 材质、灯光、相机
- 材质: Principled BSDF 基础;明确的粗糙度/金属度;自发光用于点缀(保持克制——炫光无法挽救平淡的画面)。仅在需要时使用真正的透明,并在最终引擎中验证它。
- 灯光: 主光 → 补光 → 轮廓光 → 环境光。形状和深度优先于装饰。检查曝光和高光剪切,而不仅仅是视口。
- 相机: 根据主体选择焦距和构图。轨道、推拉、升降或固定——每个必须有理由。在极端帧处验证构图,而不仅仅是第1帧。优先使用平滑的贝塞尔手柄;添加微妙的静止而非硬停止。
- 景深: 聚焦于故事点;切勿模糊小文本。
8. 引擎和渲染设置
| 引擎 | 用途 | 成本 |
|---|---|---|
| EEVEE | 动态图形、风格化工作、快速迭代、预览 | 低 |
| Cycles | 照片级光照、反射、全局光照、玻璃、最终美图 | 高 |
| Workbench | 分镜、剪影、动画分镜 | 非常低 |
不确定时测试两者;引擎速度取决于场景,而非宣传。GPU取决于构建、驱动和操作系统——始终保留CPU路径。
明确设置并记录:
scene.render.engine = 'BLENDER_EEVEE_NEXT' # 4.2+;旧版本可能使用 'BLENDER_EEVEE'
scene.render.resolution_x, scene.render.resolution_y = 1920, 1080
scene.render.resolution_percentage = 100
scene.render.fps, scene.render.fps_base = 30, 1.0
scene.frame_start, scene.frame_end = 1, 300
scene.render.image_settings.file_format = 'PNG'
scene.render.image_settings.color_mode = 'RGBA'
scene.render.filepath = "//renders/shot_01/frame_"
- 预览:25–50% 分辨率,低采样,EEVEE 或 Workbench。
- 最终:全分辨率,根据外观选择引擎,如有帮助则降噪,正确的视图变换。
- 颜色:一次确定视图变换(AgX/Filmic/Standard),应用于所有通道,并在下游的 Remotion/FFmpeg 中匹配。不匹配的变换是“合成看起来发白”的最常见原因。
- 长时间渲染 → PNG(需要 HDR 时使用 EXR)序列,然后编码。直接视频输出仅用于快速审查。
9. 预览阶梯
- 在代表性帧处渲染一张静帧。
- 关键帧:第一帧、揭示、峰值、每次过渡、最后一帧。
- 包含最难运动段的低采样、低分辨率片段。
- 缩小尺寸、真实 FPS 的完整预览。
- 仅在 1–4 看起来正确后进行最终渲染。
通过联系表加上目标帧进行审查:
ffmpeg -i preview.mp4 -vf "fps=2,scale=320:-1,tile=4x4" -frames:v 1 previews/contact.jpg
检查闪烁、z-fighting、跳动、交叉、纹理漂移、相机剪裁、曝光变化和不可读的文本。
10. 文字、多语言和标志工作
- 仅在确认文案和字体许可证后再将文本转换为网格;保留源文件中的可编辑文本对象。
- 在制作动画前验证字形覆盖范围。对于波斯语/阿拉伯语,在渲染帧中检查连字、连接、从右到左顺序、变音符号和混合脚本标点——即使字符串正确,如果文本路径未正确整形,也可能渲染错误。
- 对于品牌/标志工作,尽量获取矢量源文件;如果是位图,则有意识地描摹并在4K下检查边缘。
- 对文字进行倒角/加粗,使其能够承受光照;避免纯黑对黑或纯白对白的边缘。
11. 性能和可靠性
- 实例化,不要复制。在降低采样之前先降低细分级别。
- 限制透明层和体积效果;它们通常是渲染时间的杀手。
- 缓存模拟,并确认缓存位于项目内部或已记录在案。
- 对脆弱的工作进行帧批次渲染;验证帧数并在编码前捕获缺失帧。
- 监控 RAM/VRAM/磁盘。4K EXR 序列体积很大;在长时间渲染前检查可用空间。
- 在使用较新 Blender 主版本打开/保存
.blend之前先备份——较新文件可能无法在旧版本中打开。
12. 最小工作示例
# build.py — 使用以下命令运行:
# blender --background --factory-startup --python-exit-code 1 --python build.py
import bpy, bmesh, math, os
scene = bpy.context.scene
scene.frame_start, scene.frame_end = 1, 120
scene.render.fps = 30
scene.render.resolution_x, scene.render.resolution_y = 1920, 1080
scene.render.engine = 'BLENDER_EEVEE_NEXT'
scene.render.filepath = "//renders/frame_"
# 清理已拥有的集合
col = bpy.data.collections.get("MS_Main") or bpy.data.collections.new("MS_Main")
if col.name not in scene.collection.children:
scene.collection.children.link(col)
def add_sphere(name, loc, radius, mat):
"""数据 API 球体:无操作符,无上下文依赖。"""
mesh = bpy.data.meshes.new(name)
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, radius=radius)
bm.to_mesh(mesh)
bm.free()
obj = bpy.data.objects.new(name, mesh)
obj.location = loc
col.objects.link(obj)
obj.data.materials.append(mat)
return obj
mat = bpy.data.materials.new("MS_Emission")
mat.use_nodes = True
bsdf = mat.node_tree.nodes["Principled BSDF"]
bsdf.inputs["Base Color"].default_value = (0.05, 0.6, 0.55, 1)
bsdf.inputs["Emission Color"].default_value = (0.1, 0.9, 0.8, 1)
bsdf.inputs["Emission Strength"].default_value = 3.0
bsdf.inputs["Roughness"].default_value = 0.3
core = add_sphere("MS_Core", (0, 0, 1), 0.8, mat)
core.keyframe_insert("scale", frame=1)
core.scale = (1.15, 1.15, 1.15)
core.keyframe_insert("scale", frame=40)
core.scale = (1.0, 1.0, 1.0)
core.keyframe_insert("scale", frame=90)
# 相机
cam_data = bpy.data.cameras.new("MS_Cam")
cam = bpy.data.objects.new("MS_Cam", cam_data)
col.objects.link(cam)
scene.camera = cam
cam.location = (6, -6, 3)
cam.rotation_euler = (math.radians(75), 0, math.radians(45))
# 保存
os.makedirs(bpy.path.abspath("//renders"), exist_ok=True)
bpy.ops.wm.save_as_mainfile(filepath=bpy.path.abspath("//scene.blend"))
print("BUILD OK", len(col.objects), "objects")
更多内容请参阅 references/BLENDER-RECIPES.md:轨道绑定、缓动、几何节点散布、粒子、文本、Alpha 通道、渲染农场脚本和编码命令。
13. Blender MCP(可选便利性)
后台模式下的脚本是生产路径:可重现、可差异比较、可调试、适合 CI。MCP 对于交互式场景检查和已在打开的会话中进行实时调整非常有用。它属于第三方集成,而非 Blender 组件。
如果用户需要:审查仓库和许可证,检查与已安装 Blender 的兼容性,理解它授予在 Blender 内执行任意 Python 的权限,将其绑定到回环接口,并且永远不要将其暴露给不受信任的网络。当前包名为 mcp-for-blender;请遵循仓库当前的设置说明,而非旧的 blender-mcp 片段。不要将其作为任何交付项目的依赖项。
14. 交付物
完成一项 Blender 工作后,你需要:
output/final.mp4(或请求的格式),并使用ffprobe验证。blender/scripts/*.py— 构建源代码。scene.blend— 可编辑的项目,资产已打包或使用相对路径。renders/图像序列(或已记录的主文件)在有帮助时保留。README.md— 精确的 Blender 版本、安装、构建、预览、最终渲染和修订命令。- 一份简短报告:工具、分辨率、FPS、时长、帧数、视觉上验证了哪些内容。
当用户要求一个他们可能需要更改的动画时,永远不要只交付 MP4。