浅谈manim-3b1b的数学视频动画引擎
之前刷了MIT Gilbert Strang
老爷子的线性代数公开课,觉得挺牛逼,然后想起以前看了但是没咋看懂的3Blue1Brown
的线性代数本质,决定再刷一遍,然后直接被里面的动画圈粉。后来发现3B1B
把这个视频框架开源了,决定学学。
传送门: github - 3b1b/manim
介绍&安装
视频框架采用Python
提供接口供开发者使用,使用Latex语言编写数学公式,哈哈哈这简直就是代码狗的福音。
学了一点发现这个框架的API设计相当简洁,采用自顶向下的模式,非常适合数学演示。
关于安装,这个链接演示了多个平台的详细安装过程,不过没有Windows的,Windows看官方仓库的README吧。
快速开始
在快速开始中通过一个最简单的例子了解manim引擎的运行模式,了解动画对象
创建一个文件exm1.py
from manimlib.imports import *
class WriteText(Scene):
def construct(self):
text = TextMobject("This is a regular text")
self.play(Write(text))
self.wait(3)
上面的代码是一个快速开始的示例,我们先看效果再讲代码
manim exm1.py WriteText -pl
然后等待,就会弹出视频了,视频中,一行This is a regular text
一点一点的被从屏幕中间写出来。
from manimlib.imports import *
这不用解释,导包
class WriteText(Scene):
manim框架中的一个动画就是一个类,继承自Scene
,这个类的类名就是这个动画的名字
def construct(self):
manim框架的每一个动画有一个construct
方法,你要编写的动画逻辑就在这里
text = TextMobject("This is a regular text")
self.play(Write(text))
self.wait(3)
第一行创建一个文本对象,并编写其中的文字,然后调用play方法播放动画,但是光有TextMobject
是不行的,play需要的是一个动画,所以Write
就是接受一个文本对象并输出手写该文本对象的动画。
第三行,wait等待3秒,至此全部逻辑走完,动画结束。
追踪Write
方法的源码,分析下
# 是一个类,继承自DrawBorderThenFill
# 可以推断出父类也是个动画,代表先绘制出轮廓然后填充的效果
class Write(DrawBorderThenFill):
# 一些动画配置,这里只定义了数据结构,并没有对值初始化
CONFIG = {
"run_time": None,
"lag_ratio": None,
"rate_func": linear,
}
# __init__,Python中当对象初始化被调用的魔法方法,可以看作构造方法但还是有点区别
def __init__(self, mobject, **kwargs):
# 从名字推测是从创建对象时传递的参数中取出配置信息并且设置的,所以我们应该可以在构造该动画时传递参数
# 并且从方法名中的digest可以推测,传入的参数会和`CONFIG`里的默认参数融合,也就是用户传入了就用用户的,否则用默认的
# 而CONFIG就相当于给开发者一个该动画有什么属性的提示
# 后来我跟踪了这个方法的代码,确实是我们推测的情况
digest_config(self, kwargs)
# 看起来是根据传入的对象长度来设置默认配置,看看这个方法里写啥了
self.set_default_config_from_length(mobject)
super().__init__(mobject, **kwargs)
# 这就是上面那个默认配置的方法,就是如果用户没设置参数,参数还是初始值的话就根据传入对象的某个方法返回的长度设置
def set_default_config_from_length(self, mobject):
length = len(mobject.family_members_with_points())
if self.run_time is None:
if length < 15:
self.run_time = 1
else:
self.run_time = 2
if self.lag_ratio is None:
self.lag_ratio = min(4.0 / length, 0.2)
上面是Write
动画的全部源码,我们从中可以知道,manim中动画也是一个类,它有一些配置,并且可以在构造时直接传入,每个动画可选的配置可以在动画源码的CONFIG
属性中查看,我们设置一下Write
的参数
self.play(Write(text,run_time=10))
动画顿时长了不少,手写的速度也慢了,所以这个参数是控制动画持续时间的。
除了Write
外,还有很多动画可以选择,manim源码的
animation包里定义了很多现成的动画效果。
介绍几个常用的:
- FadeIn 淡入
- FadeOut 淡出
- Write 手写文字
- GrowFromCenter 从中间弹出
下面说说命令行
manim exm1.py WriteText -pl
当执行这个命令时,manim会编译当前的动画并弹出预览,exm1.py
就是脚本名,WriteText
是要播放的动画,所以一个文件是可以包含多个动画的,-p
则是预览,-l
是低画质,对应的还有-m
中画质和--high_quality
高画质(因为-h
被帮助占用所以没有简写)
从Mobject说起
Mobject是manim中定义的对象,你可以看作是一个物体,常见的Mobject有Shape、Text和数学公式等
Shapes
Shape是Mobject子类,代表一个形状
- Square 方形
- Circle 圆形
- Rectangle 矩形
- Lines 线
- Annulus 环
- Dot 点
等等...mobject/geometry.py中可以查看更多形状
from manimlib.imports import *
class Shapes(Scene):
def construct(self):
# 创建一个圆形 没有传参 使用的都是默认参数
circle = Circle(color=YELLOW)
# 添加到屏幕中
self.add(circle)
self.wait(2)
运行发现是一个黄色的圆环被放在屏幕中间,我们去看看圆的定义
# 父类是Arc(弧形)
class Circle(Arc):
CONFIG = {
"color": RED,
"close_new_points": True,
"anchors_span_full_range": False
}
def __init__(self, **kwargs):
# 调用了弧形的构造器,传入了0和TAU
# 如果你看Arc类的__init__可以知道传入的0被设置给起始角度,TAU被设置给结束角度,
# 圆形的结束角度平直觉应该是360,而在manim的contacts.py中也确实是这样定义的
Arc.__init__(self, 0, TAU, **kwargs)
# ... some method
诶??我们没看到这里面有关于位置的信息哈,那它咋就被放到屏幕中间了?思考了一下,这种属性应该放在最底层的类里,通过追踪代码,我们在Mobject
类中找到了相应代码
def reset_points(self):
self.points = np.zeros((0, self.dim))
mainm中所有的物体都继承自Mobject,而Mobject在初始化时会调用这个方法,可以看到它是把自己放在空间中的原点上了,所以在屏幕中心,由此我们还能知道的信息是mainm使用的坐标系,后面会详细介绍
我们也可以像上面快速开始示例一样用动画来展示形状
from manimlib.imports import *
class Shapes(Scene):
def construct(self):
circle = Circle(color=YELLOW)
square = Square(color=DARK_BLUE)
# surround方法让当前图形包裹另一个形状
square.surround(circle)
self.add(circle)
self.play(FadeIn(square))
self.wait(2)
Text
在快速开始里我们都见过了,就没啥好介绍的,我们把官方示例贴出来就好了
from manimlib.imports import *
class makeText(Scene):
def construct(self):
#######Code#######
#Making text
first_line = TextMobject("Manim is fun")
second_line = TextMobject("and useful")
final_line = TextMobject("Hope you like it too!", color=BLUE)
color_final_line = TextMobject("Hope you like it too!")
#Coloring
color_final_line.set_color_by_gradient(BLUE,PURPLE)
#Position text
second_line.next_to(first_line, DOWN)
#Showing text
self.wait(1)
self.play(Write(first_line), Write(second_line))
self.wait(1)
self.play(FadeOut(second_line), ReplacementTransform(first_line, final_line))
self.wait(1)
self.play(Transform(final_line, color_final_line))
self.wait(2)
Math Equations
from manimlib.imports import *
class MathEq(Scene):
def construct(self):
eq = TextMobject(r'$$\begin{bmatrix}1&2&3\\4&5&6\end{bmatrix}$$')
self.add(eq)
self.wait(3)
实际上数学公式用的也是TextMobject
,也就是说TextMobject是支持Latex语言的,只不过需要转义或者用r'xxx'
的原生字符串形式。