ManimGL 快速上手:从第一个 Scene 到交互式渲染的完整实战指南
本篇围绕 ManimGL(3b1b 风格的 OpenGL 版 manim)的 Quick Start 展开,覆盖从编写第一个 Scene、预览窗口中的镜头交互(平移/缩放/3D 视角)、用 -os / -o 导出静态图片与视频,到通过 self.embed() 进入 IPython 交互模式的全部流程。读完你将能独立完成“写场景 → 实时预览 → 导出成品 → 交互式调试”的完整开发闭环,并能对照仓库源码理解每一条 manimgl 命令行参数在底层是如何生效的。
一、先分清:这是 ManimGL,不是社区版
ManimGL 是 3blue1brown 的 3b1b 维护的 OpenGL 版本,其核心特性是实时 GPU 预览窗口:运行命令后直接弹出窗口实时播放动画,而不是先渲染再播放。如果你要找的是 ManimCommunity 维护的社区版(Community Edition),两者包名与安装方式完全不同,不要混用文档。因此本篇所有内容(manimgl 命令、manimlib 包名、实时窗口交互)都只适用于当前仓库实现的 ManimGL。
当前仓库的目录结构(即文档要求你建立的场景文件布局)如下:
manim/
├── manimlib/
│ ├── animation/
│ ├── ...
│ ├── default_config.yml
│ └── window.py
├── custom_config.yml
└── start.py
可以看到,你只需要把自己的 start.py 放在 manimlib/ 同级目录下。若需要覆盖默认配置,可在同目录再放一个 custom_config.yml——manimlib/config.py 的 initialize_manim_config() 会按“manimlib/default_config.yml → custom_config.yml → --config_file 指定的文件”的顺序递归合并配置,命令行参数优先级最高。
二、编写第一个场景:一个蓝色圆圈
新建 start.py,写入以下代码(稍后逐行讲解):
from manimlib import *
class SquareToCircle(Scene):
def construct(self):
circle = Circle()
circle.set_fill(BLUE, opacity=0.5)
circle.set_stroke(BLUE_E, width=4)
self.add(circle)
然后运行:
manimgl start.py SquareToCircle
屏幕上会弹出一个预览窗口,其中已经画好了那个蓝色圆圈。
逐行代码解析
第 1 行:from manimlib import * 导入使用 manim 时可能用到的全部类。
第 3 行:class SquareToCircle(Scene): 创建 Scene 的子类,这就是你要编写并渲染的场景。Scene 基类定义在 manimlib/scene/scene.py 中。
第 4 行:def construct(self): 编写 construct() 方法,它决定如何在屏幕上创建 mobject(数学对象)以及执行哪些操作。Scene.run() 的调用链是 setup() → construct() → interact()(见 manimlib/scene/scene.py#L150-L159),其中 construct 是你的动画主体,interact 则负责在进入窗口循环后持续更新画面。
第 5 行:circle = Circle() 创建一个 Circle 实例,命名为 circle。
第 6~7 行:通过实例方法设置样式:
.set_fill(BLUE, opacity=0.5):填充色设为蓝色(BLUE,颜色常量集中定义在 manimlib/constants.py 及 manimlib/default_config.yml 的colors段),填充透明度 0.5;.set_stroke(BLUE_E, width=4):描边色设为深蓝(BLUE_E),描边宽度 4。
第 9 行:self.add(circle) 通过 Scene 的 .add() 方法把这个圆加到屏幕上。从源码看(manimlib/scene/scene.py#L314-L330),add() 会先把对象从场景移除再重新加入(保证置顶),并把对象家族的所有成员登记进 id_to_mobject_map,这也是交互模式下按 id 选回 mobject 的基础。
三、预览窗口的交互操作
窗口弹出后,可以这样操作画面(这些键位来自 manimlib/default_config.yml 的 key_bindings 段与 Scene 的事件处理):
| 操作 | 效果 | 源码依据 |
|---|---|---|
| 滚轮滚动 | 画面上下移动(以鼠标位置为中心缩放) | Scene.on_mouse_scroll 中 self.frame.scale(..., about_point=point) |
按住 z + 滚轮 |
缩放画面 | 对应 z_grab 键位,按住时拖动可沿 z 方向抓取选区 |
按住 s 移动鼠标 |
平移画面(框选/拖动) | select: "s",on_mouse_drag 中 self.frame.shift(-d_point) |
按住 d 移动鼠标 |
改变三维透视角度 | pan_3d: "d",on_mouse_motion 中 frame.increment_theta/increment_phi |
另外,直接拖动鼠标即可平移画面(drag_to_pan 默认为 True),按 q 关闭窗口并退出程序。r 键可把镜头复位到默认状态,command/ctrl + q 也可退出。这些按键处理都写在 manimlib/scene/scene.py 的 on_key_press 与 manimlib/window.py 的事件分发中:底层窗口把 GLFW 事件翻译成 manimlib/event_keys.py 中的统一键名,再交给 Scene 处理。
四、导出静态图片:-os 参数
再次运行:
manimgl start.py SquareToCircle -os
这次不会弹出窗口。程序结束后会自动打开渲染出的图片——默认保存在 start.py 所在目录的 images/ 子目录中。
结合 manimlib/config.py 的 parse_cli() 可以确认这两个字母参数的真实含义:
-o(--open):Automatically open the saved file once its done,渲染完成后自动打开产物;-s(--skip_animations):跳过动画只取最后一帧,即save_last_frame=True。
在 update_file_writer_config 中,write_to_movie 与 save_last_frame 是互斥分支:加了 -s 就走“保存最后一帧”路径,所以 -os 合起来就是“不弹窗、静默渲染、存图并自动打开”。输出目录由 manimlib/default_config.yml 的 directories 段决定(output: "videos",图片同理归到 images),并且当 mirror_module_path: True 时,输出路径会镜像源码文件的目录结构。
五、加入动画:让场景动起来
把代码改成下面这样,加入动画,让输出变成视频而不只是图片:
from manimlib import *
class SquareToCircle(Scene):
def construct(self):
circle = Circle()
circle.set_fill(BLUE, opacity=0.5)
circle.set_stroke(BLUE_E, width=4)
square = Square()
self.play(ShowCreation(square))
self.wait()
self.play(ReplacementTransform(square, circle))
self.wait()
运行 manimgl start.py SquareToCircle,窗口会播放“画出一个正方形 → 变形为圆形”的动画。若要保存视频,运行:
manimgl start.py SquareToCircle -o
同样不弹窗,结束后自动打开视频文件——默认保存在 start.py 同级的 videos/ 子目录中。
动画代码逐行解析
前 7 行与之前相同,第 8 行 square = Square() 创建一个 Square 实例。
第 10 行:self.play(ShowCreation(square)) 通过 Scene 的 .play() 方法播放动画。ShowCreation(定义于 manimlib/animation/creation.py)是一个展示“创建某个 mobject 过程”的动画,效果即沿路径逐步描绘出 square。
第 11 行:self.wait() 使用 Scene 的 .wait() 方法暂停,默认 1 秒(default_wait_time,见 manimlib/default_config.yml 的 scene 段);可传参指定时长,如 self.wait(3) 表示暂停 3 秒。
第 12 行:self.play(ReplacementTransform(square, circle)) 播放把 square 变形为 circle 的动画。ReplacementTransform(A, B)(定义于 manimlib/animation/transform.py)的语义是:把 A 变换成 B 的形状,并在动画结束后用 B 替换 A。
第 13 行:与第 11 行相同,暂停 1 秒。
从实现上看,Scene.play() 会依次执行 pre_play → begin_animations → progress_through_animations → finish_animations → post_play(manimlib/scene/scene.py#L574-L592):progress_through_animations 按 1/fps 步进时间轴,逐帧调用 animation.interpolate(alpha) 插值,再绘制并写出帧。这也解释了为什么默认帧率是 30fps(camera 段 fps: 30)。
六、启用交互模式:self.embed() 与裸 manimgl
交互是 ManimGL 的招牌特性。在场景代码末尾加一行:
self.embed()
再运行 manimgl start.py SquareToCircle。前面的动画执行完后,命令行会打开一个 IPython 终端,之后你输入的语句按下回车立即执行(该模式下 self.play 可简写为 play)。依次输入:
# Stretched 4 times in the vertical direction
play(circle.animate.stretch(4, dim=0))
# Rotate the ellipse 90°
play(Rotate(circle, TAU / 4))
# Move 2 units to the right and shrink to 1/4 of the original
play(circle.animate.shift(2 * RIGHT), circle.animate.scale(0.25))
# Insert 10 curves into circle for non-linear transformation (no animation will play)
circle.insert_n_curves(10)
# Apply a complex transformation of f(z)=z^2 to all points on the circle
play(circle.animate.apply_complex_function(lambda z: z**2))
# Close the window and exit the program
exit()
依次可以看到椭圆拉伸、旋转 90°、平移缩小、插入 10 条曲线后再施加复函数 f(z)=z² 的非线性形变。最后 exit() 关闭窗口退出。
不想写只含 self.embed() 的空场景时,也可以直接运行:
manimgl
不带任何参数即可弹出窗口并直接进入 IPython 终端。从源码看,manimlib/extract_scene.py 定义了 BlankScene(InteractiveScene),其 construct 会执行 universal_import_line(默认即 from manimlib import *,见 manimlib/default_config.yml)后自动 self.embed()。
交互模式背后的机制
Scene.embed()(manimlib/scene/scene.py#L203-L219)会先停掉跳帧、强制绘制一帧,然后启动 manimlib/scene/scene_embed.py 中的 InteractiveSceneEmbed,它注入了几个关键能力:
- 快捷键命名空间:
get_shortcuts()向 IPython 命名空间注入play、wait、add、remove、clear、undo、redo、save_state、i2g、i2m、reload、checkpoint_paste等——这就是上文play(...)简写能工作的原因; - 输入时窗口不卡死:
enable_gui()注册了名为manim的 pt_inputhooks,在 shell 等待输入的间隙按 1/fps 的节奏持续update_frame并处理窗口事件; - 每个 cell 执行后自动刷新画面:
post_run_cell钩子强制绘制一帧,所以每条语句回车后画面立即更新; - 出错时红框闪烁提示:自定义异常处理器会让全屏红色边框闪一下,方便注意到报错;
reload():不退出内核即可重新加载整个场景文件,run_scenes(manimlib/main.py)会捕获由exit_raise触发的KillEmbedded并复用同一个窗口重新执行场景,实现“热重载”;- checkpoint_paste:粘贴以注释开头的代码块时,会先回滚到该注释首次出现时的场景状态再执行,方便反复调试同一段动画。
另外,命令行也提供 -e <行号> 参数:manimlib/extract_scene.py 的 insert_embed_line_to_module() 会在源文件指定行注入 self.embed(),效果等同于手动加 self.embed(),且未指定场景名时会自动取该行上方最近的类名。
七、常用命令行参数速查
manimgl 的完整参数定义在 manimlib/config.py 的 parse_cli(),日常最常用的有:
| 参数 | 说明 |
|---|---|
file / scene_names |
场景文件路径与要渲染的 Scene 类名(可多个;不指定类名时若文件里只有一个场景则直接运行,多个则交互选择) |
-o, --open |
渲染完成后自动打开输出文件 |
-s, --skip_animations |
跳过动画,保存最后一帧(常与 -o 组合成 -os 存图) |
-w, --write_file |
渲染为视频文件(隐含开启写文件) |
-l / -m / --hd / --uhd |
分别按 480p / 720p / 1080p / 4K 渲染,分辨率映射见 manimlib/default_config.yml 的 resolution_options(默认输出 1920×1080) |
-r WxH / --fps |
自定义分辨率与帧率 |
-i, --gif |
保存为 gif |
-t, --transparent |
渲染带 alpha 通道的视频(自动改用 prores_ks 编码、输出 .mov) |
-n start[,end] |
从第 N 个动画开始渲染,"3,6" 表示渲染第 3 到第 6 个 |
-e 行号 |
在指定行插入 self.embed() 断点,进入交互会话 |
-a, --write_all |
渲染文件内所有场景 |
-f, --full_screen |
窗口全屏显示 |
-p, --presenter_mode |
wait 时保持暂停,按空格或右箭头继续,类似幻灯片放映 |
-c 颜色 |
指定背景色 |
--subdivide |
把输出拆分为每个动画一个文件 |
--config_file |
指定额外的自定义配置文件 |
--clear-cache |
清除 Tex/Text mobject 的编译缓存 |
-v, --version |
显示版本 |
从 manimlib/main.py 的入口流程可以看到整体链路:main() 打印版本 → parse_cli() 解析参数 → run_scenes() 在 show_in_window(未写文件时)时创建一个可复用的 Window(manimlib/window.py,基于 GLFW + WebGPU/wgpu 渲染管线)→ 调用 manimlib.extract_scene.main() 加载模块、筛选场景类、实例化并 run()。窗口生命周期长于场景,所以 reload 时能复用同一个窗口对象。
八、接下来去哪
完成本篇后,你已经掌握了 ManimGL 的核心工作流:编写 Scene、窗口实时交互、导出图片/视频、IPython 嵌入调试。建议按仓库文档顺序继续:
- 更多完整示例:docs/source/getting_started/example_scenes.rst,以及仓库根目录的 example_scenes.py;
- 配置体系详解(目录布局、相机参数、key_bindings 等):docs/source/getting_started/configuration.rst;
- 安装方式回顾:docs/source/getting_started/installation.rst。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00