Manim 相机系统全解析:从 Camera 基类到 3D 视角的五种渲染视图

发布时间:2026/9/12 1:18:50
Manim 相机系统全解析:从 Camera 基类到 3D 视角的五种渲染视图 Manim 相机系统全解析从 Camera 基类到 3D 视角的五种渲染视图【免费下载链接】manimA community-maintained Python framework for creating mathematical animations.项目地址: https://gitcode.com/GitHub_Trending/man/manimCameras 是 Manim 数学动画框架中负责取景与成像的核心子系统。本指南围绕 docs/source/reference_index/cameras.rst 索引的五个相机模块展开逐一讲解Camera、MappingCamera、MovingCamera、MultiCamera与ThreeDCamera的职责、参数与底层实现并给出可直接运行的实战示例。读完后你将理解 Manim 每一帧画面是如何从 Mobject 场景树被投影为像素数组的并能根据动画需求选择正确的相机类型。相机在 Manim 中的地位从场景到像素的最后一公里在 Manim 中Scene负责组织 Mobject 与动画逻辑而真正决定屏幕上最终显示什么的是相机对象。这一点在 manim/camera/camera.py 的模块级 docstring 中被明确概括A camera converts the mobjects contained in a Scene into an array of pixels.相机将场景中包含的 Mobject 转换为一组像素数组。也就是说相机的核心职责是把抽象空间中的矢量图形、文本、点云等 Mobject通过坐标变换、投影、光栅化等一系列步骤最终写入一张pixel_array通常是 RGBA 的numpy数组供渲染器逐帧编码为视频。Camera是所有相机类型的基类其余四种相机映射、移动、多视角、3D都以不同方式继承并扩展它的能力形成如下继承关系依据各模块源码MappingCamera(Camera)、OldMultiCamera(Camera)、SplitScreenCamera(OldMultiCamera)—— manim/camera/mapping_camera.pyMovingCamera(Camera)—— manim/camera/moving_camera.pyMultiCamera(MovingCamera)—— manim/camera/multi_camera.pyThreeDCamera(Camera)—— manim/camera/three_d_camera.pyCamera 基类掌握取景、分辨率与像素输出Camera是全部相机的公共基础设施其构造函数见 manim/camera/camera.py 的Camera.__init__接收的参数决定了画面的基本属性整理如下参数默认值作用background_imageNone背景图片路径未设置时用background_color填充背景frame_centerORIGIN取景框中心点三维坐标image_modeRGBA像素数组的颜色模式n_channels4每像素通道数RGBA 即 4 通道pixel_array_dtypeuint8像素数组的数据类型cairo_line_width_multiple0.01线条宽度换算倍率矢量描边转像素时使用use_z_indexTrue是否按 z_index 决定遮挡顺序backgroundNone预置的背景像素数组pixel_height/pixel_width取config输出画面的像素高度/宽度未显式传入时回落到配置项frame_height/frame_width取config取景空间的高度/宽度以 MUnit 为单位frame_rate取config帧率background_color/background_opacity取config背景颜色与不透明度其中pixel_height、pixel_width、frame_height、frame_width、frame_rate等参数在未显式给出时会直接读取全局配置对象config源码中表现为if pixel_height is None: pixel_height config[pixel_height]。这些配置项的默认值定义在 manim/_config/default.cfg 中例如典型的frame_width 14.2222、frame_height 8.0、pixel_width 1920、pixel_height 1080、frame_rate 60。因此修改渲染质量如-ql、-qm、-qh等 CLI 参数本质上就是调整这些像素维度配置进而改变相机输出的pixel_array尺寸。描边连接与端点样式相机在把矢量对象的描边光栅化到像素数组时需要把 Manim 的抽象线型映射为 Cairo 图形库的枚举。Camera模块顶部定义了两张映射表见 manim/camera/camera.pyLINE_JOIN_MAPAUTO/ROUND/BEVEL/MITER映射到cairo.LineJoin对应值CAP_STYLE_MAPAUTO/ROUND/BUTT/SQUARE映射到cairo.LineCap对应值。AUTO在两张表中均映射为None交由 Cairo 自行选择源码中留有 this could be improved 的 TODO 注释其余取值则与矢量图形的视觉风格直接相关ROUND产生圆滑端点、BUTT平头截断、SQUARE平头外扩、MITER尖角连接。像素数组相机的画布相机渲染的最终产物是self.pixel_array一个形状为(pixel_height, pixel_width, n_channels)的numpy数组。所有子类如多相机分屏、子相机缩放都通过对这张像素数组的切片、覆盖或重置来实现叠加效果理解这一点有助于把握后续各相机的工作原理。MappingCamera对空间做函数映射的变形相机MappingCamera见 manim/camera/mapping_camera.py允许你通过一个自定义函数把 3D 点映射到新的 3D 点从而产生空间扭曲、畸变等视觉效果。其构造参数参数默认值作用mapping_funclambda p: p恒等映射将 3D 点映射为新 3D 点的可调用对象min_num_curves50VMobject 的最小曲线数避免变形后出现视觉毛刺allow_object_intrusionFalse为True时直接修改原始 Mobject否则在副本上操作底层实现要点points_to_pixel_coords覆写了父类方法先把每个点经np.apply_along_axis(self.mapping_func, 1, points)逐行映射再调用父类逻辑完成从场景坐标到像素坐标的转换即先变形、后光栅化capture_mobjects中若allow_object_intrusionFalse会对每个 Mobject 做mobject.copy()保证不污染场景中的原始对象随后对VMobject且曲线数不足min_num_curves的对象调用insert_n_curves补充曲线让变形后的轮廓足够平滑。源码中保留的 TODO 注释还设想为对象增加仅映射中心、其余不畸变的属性适用于标签等元素。一个典型用法是把相机用于平面到曲面的扭曲将默认平面坐标通过映射函数卷成圆柱面、球面等形态后再渲染从而在 2D 场景中呈现伪 3D 的变形效果。补充该模块还包含OldMultiCamera与SplitScreenCamera均标注有 should likely be deleted 的 TODOSplitScreenCamera把左右两个相机各分配半屏像素宽度实现并排分屏被MultiCamera取代前是旧版多视角方案。MovingCamera可平移、缩放、自动聚焦的运动相机MovingCamera见 manim/camera/moving_camera.py是制作镜头跟随类动画的核心工具。它的理念是把相机与一个frame通常是一个矩形 Mobject默认ScreenRectangle(heightconfig[frame_height])绑定frame 的位置与尺寸即相机的取景区域frame 移动或缩放时画面随之变化。核心参数与属性frame定义取景区域的 Mobject传入任意矩形类对象即可自定义初始视野fixed_dimension0表示以宽度为准默认1表示以高度为准用于缩放时保持某一边不变default_frame_stroke_color/default_frame_stroke_width默认边框样式默认白色、宽度 0即不显示边框。frame_height、frame_width、frame_center均实现为属性property读取时从self.frame取值写入时分别调用stretch_to_fit_height、stretch_to_fit_width、move_to同步修改 frame因此你可以直接用self.camera.frame_height 4这类写法调整视野。auto_zoom一键框选目标auto_zoom是 MovingCamera 最实用的方法之一签名如下def auto_zoom( self, mobjects, # 目标 Mobject 或 Mobject 列表 margin: float 0, # 附加边距MUnit only_mobjects_in_frame: bool False, # True 时只聚焦已在画面内的对象 animate: bool True, # False 时立即应用True 时返回动画 ) - _AnimationBuilder | Mobject其流程见 manim/camera/moving_camera.py 的auto_zoom与_get_bounding_box遍历目标对象用get_critical_point(LEFT/RIGHT/UP/DOWN)计算包围盒的四个临界坐标若only_mobjects_in_frameTrue则跳过不在画面内的对象同时始终跳过相机自身的 frame源码注释说明 frame 不应参与最终定位计算计算包围盒中心作为新frame_center并按宽度占比大则设 width否则设 height的策略等比缩放 frame使所有目标恰好入框可加margin边距。由于实现基于self.frame.animate或直接set_x/set_y/set(width...)返回的动画可以像普通 Mobject 动画一样接入场景的play调用。注意源码中的 NOTEauto_zoom仅对 XY 平面内的 2D 对象有效相机被旋转后无法正确工作。仓库中 tests/module/scene/test_auto_zoom.py 覆盖了该方法的缩放与聚焦行为。为什么它不做缓存MovingCamera覆写了get_cached_cairo_context与cache_cairo_context二者均直接返回None/ 空操作。源码注释给出了原因frame 每帧都可能移动Cairo 上下文必须每帧重新生成无法复用缓存。这也是运动相机与静态相机在性能设计上的关键差异。配合 MovingCameraScene 使用MovingCamera与场景类MovingCameraScene见 manim/scene/moving_camera_scene.py配套使用。典型实战代码from manim import MovingCameraScene, Dot, UP, DOWN class CameraMoveExample(MovingCameraScene): def construct(self): dot Dot().set_color(YELLOW) self.play(self.camera.frame.animate.move_to(dot)) # 镜头跟随 self.play(self.camera.frame.animate.set(width8)) # 镜头缩放 self.play(self.camera.auto_zoom([dot, dot.copy().shift(UP*2)])) # 自动框选get_mobjects_indicating_movement返回[self.frame]意味着任何 frame 的移动都会被系统视为相机运动从而让其他静止对象在画面中产生相对运动效果——这正是跟拍动画的数学基础。MultiCamera在同一场景中叠加多重视角MultiCamera见 manim/camera/multi_camera.py继承自MovingCamera核心思想是相机中的相机它维护若干个子相机的ImageMobjectFromCamera即从相机截取的图像Mobject定义在 manim/mobject/types/image_mobject.py每帧先让子相机独立渲染再把子相机输出作为纹理贴回主画面从而在一个场景中呈现监视器画面、画中画等多重视角。关键机制add_image_mobject_from_camera(imfc)注册一个子相机图像源码中assert isinstance(imfc.camera, MovingCamera)约束子相机必须为运动相机因为 MultiCamera 需要子相机 frame 的移动/缩放能力update_sub_cameras每帧根据imfc在主 frame 中的相对尺寸imfc.height / self.frame_height、imfc.width / self.frame_width等比调用reset_pixel_shape重置子相机的像素分辨率保证画中画清晰度与尺寸匹配capture_mobjects先更新子相机再让每个子相机渲染去掉imfc自身家族成员后的对象列表——除非设置allow_cameras_to_capture_their_own_displayTrue否则子相机不会把自己也拍进画面避免无限递归用list_difference_update剔除见 manim/utils/iterables.pyget_mobjects_indicating_movement返回[self.frame] [imfc.camera.frame ...]保证主相机与所有子相机 frame 的运动都被计入相机运动。实战示例from manim import ( Scene, Square, Circle, MovingCamera, MultiCamera, ImageMobjectFromCamera, GREEN, RED, ) class MultiCameraExample(Scene): def construct(self): sub_cam MovingCamera(frame_height2.0) sub_img ImageMobjectFromCamera(camerasub_cam).to_corner(UR).scale(0.3) self.camera MultiCamera(image_mobjects_from_cameras[sub_img]) square Square(colorGREEN) circle Circle(colorRED).shift(2 * RIGHT) self.add(square, circle, sub_img) self.play(sub_cam.frame.animate.move_to(circle)) # 子画面跟随圆ThreeDCamera可旋转、缩放、带光照与景深的三维相机ThreeDCamera见 manim/camera/three_d_camera.py把相机放入三维空间通过三个欧拉角控制相机姿态通过焦距与缩放控制透视并内置光照着色模型。它在 manim/scene/three_d_scene.py 的ThreeDScene中作为默认相机使用。构造参数与默认值参数默认值含义focal_distance20.0焦距MUnit决定透视强度shading_factor0.2光照着色强度系数default_distance5.0相机到对象的默认距离参考值light_source_start_point9*DOWN 7*LEFT 10*OUT光源初始位置三维点should_apply_shadingTrue是否启用光照着色exponential_projectionFalse是否使用指数投影否则为标准透视投影phi0极角与 Z 轴夹角弧度theta-90 * DEGREES方位角绕 Z 轴旋转弧度gamma0绕相机视轴的自旋角弧度zoom1镜头缩放倍率姿态控制ValueTracker 与欧拉角相机内部为phi、theta、focal_distance、gamma、zoom各维护一个ValueTrackerget_value_trackers()返回这五个 tracker 的列表见 manim/mobject/value_tracker.py并提供成对的get_*/set_*方法phi极角即相机视线与 Z 轴的夹角get_phi返回弧度值theta方位角绕 Z 轴旋转相机的角度默认-90 * DEGREES即初始正视 XY 平面gamma相机绕原点到相机方向的自旋角focal_distance焦距zoom缩放倍率。generate_rotation_matrix按rotation_about_z(-theta - 90°)→rotation_matrix(-phi, RIGHT)→rotation_about_z(gamma)的顺序合成 3×3 旋转矩阵capture_mobjects每帧先reset_rotation_matrix()再渲染确保矩阵与当前角度一致。ThreeDScene中的move_camera动画正是通过动画化这些 tracker 的值来实现镜头环绕的。投影与深度排序project_points是三维到二维的核心先把点平移到以frame_center为原点左乘旋转矩阵转置再按 z 值配合focal_distance与zoom做透视投影exponential_projectionTrue时改用指数投影公式源码注释说明标准透视需按深度缩放。此外get_mobjects_to_display覆写了父类排序逻辑对shade_in_3d的对象按到相机的 z 距离排序z_key用np.dot(reference_point, rot_matrix.T)[2]计算实现正确的近远遮挡非 3D 对象返回np.inf恒置于后方。光照着色modified_rgbas在should_apply_shading开启且对象shade_in_3d时生效取对象的起止角点及其单位法向量调用 manim/utils/color.py 的get_shaded_rgb结合light_source位置计算明暗再把起止两点的 RGB 写入颜色数组少于 2 个色值时用repeat(2)补齐从而在 3D 表面上产生光照立体感。实战示例from manim import ThreeDScene, ThreeDAxes, Dot, ORIGIN, PI class ThreeDCameraExample(ThreeDScene): def construct(self): axes ThreeDAxes() dot Dot3D(pointaxes.c2p(1, 2, 1), colorYELLOW) self.add(axes, dot) self.move_camera(phi75 * DEGREES, theta-45 * DEGREES, zoom1.2) self.begin_ambient_camera_rotation(rate0.2) # 持续环绕 self.wait(3) self.stop_ambient_camera_rotation()如何选择相机一张速查表需求相机配套场景类普通静态渲染、默认 2D 场景CameraScene空间扭曲/映射变形如平面卷曲MappingCameraScene自行指定self.camera镜头平移、缩放、跟随、自动框选MovingCameraMovingCameraScene画中画、监视器、多视角叠加MultiCameraScene需自行构造ImageMobjectFromCamera3D 物体、旋转环绕、光照着色、透视投影ThreeDCameraThreeDScene如何在场景中切换相机默认场景在构造时根据场景类型实例化对应相机如ThreeDScene内置ThreeDCamera。如需在普通Scene中使用特殊相机直接为self.camera赋值即可例如from manim import Scene, MovingCamera class CustomCameraScene(Scene): def construct(self): self.camera MovingCamera() # 在普通 Scene 中启用运动相机 # ... 后续逻辑与 MovingCameraScene 一致相机系统的行为在仓库测试中有充分覆盖可进一步参考 tests/test_camera.py相机基础行为、tests/module/scene/test_auto_zoom.py自动聚焦以及 tests/test_graphical_units 下与相机渲染相关的图形单元测试作为验证实现细节的第一手资料。结语Manim 的相机子系统把场景树 → 像素数组这条渲染管线设计得层次分明Camera提供像素画布与基础光栅化MappingCamera在光栅化前插入空间函数映射MovingCamera用 frame 对象解耦取景区域与渲染管线使运镜成为纯粹的几何变换MultiCamera把相机输出当作可复用纹理实现多视角嵌套ThreeDCamera则以欧拉角 焦距 光照模型完成三维世界的投影与着色。掌握这五种相机就等于掌握了 Manim 画面的最终控制权。【免费下载链接】manimA community-maintained Python framework for creating mathematical animations.项目地址: https://gitcode.com/GitHub_Trending/man/manim创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考