Loading

浅谈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'的原生字符串形式。

未完待续...

posted @ 2020-03-24 16:47  yudoge  阅读(2289)  评论(1编辑  收藏  举报