前言

简介

Manim是一个用于解释数学的开源动画制作引擎,由Manim社区进行维护。
在这里插入图片描述

Manim由斯坦福大学数学高材生/教授Grant Sanderson创建,其最经典的成果就是发布于Youtube的3Blue1Brown(简称:3b1b)频道的一系列视频,用于解说高等数学、线性代数、概率统计等内容。
3Blue1Brown

Youtube-3Blue1Brown

Grant Sanderson已经将Manim动画引擎的源代码开源到github,但其本人不承诺对后续的代码进行维护、升级。
Github-3Blue1Brown
较为幸运的是,3b1b系列视频发布之后,在广大数学爱好者和数据分析师中有巨大的反响,不断有维护者对初代Manim进行升级,甚至给出了Manim的安装使用例程,便于更方便的应用Manim系列引擎进行数学讲解。

发展

最初由Grand Sanderson发布的Manim只能在命令行下运行,安装时依赖较多的Python包和第三方工具进行制作和渲染,很多初学者在不了解Python和Latex的情况下,几乎迈不出第一步。

Manin的开发者们后期一直致力于精确动画引擎的升级与优化,在这两年中,manim 的 Github 项目发生了很大的变化,并衍生出了三个主要版本,分别是 manimCairo、manimGL、manimCE。

manimCairo

manim 的早期版本,该版本自 2020 年 末已经停止了更新维护。

manimGL

该版本由 Grant Sanderson 主要负责维护。该版本已近趋于稳定,但还尚未完成。

manimCE

由 2020 年中旬的一个 manim 分支演化而来,该分支后来社区化,被称为是 manim Community Edition ,缩写为 manimCE。
这是 manim 当前的一个最为稳定的版本,相对于早期版本,其在语法结构上做了大量优化,并简化了安装步骤。由于参与者更多,所以其更新维护最为频繁,一些常见的 BUG 能在较短的时间内被解决,所以更加适合新手上手。

组成

Manim主要由以下这些部分组成
组成Manim
在其中,Python是连接Manim各个部件的核心;然后是Latex,是一种公式编辑语言,常用于书写学术论文;再然后是Cairo,是Python的一个包,用于绘制图形的程序;还有FFmpeg,是图像的渲染工具,如果你使用过Captura这个录像工具,就会看到FFmpeg这个渲染选项;最后是SoX,用于处理音频。

尽管Manim有这么多组成部分,但实际上并不需要用户去学会每一个部件的原理和使用,而是只要懂一些Python的基本语法,其他部件只要安装在你的电脑上就可,甚至像SoX这个音频处理软件,可以不使用。这样你就可以使用Manim系列引擎制作自己短视频了。

注意事项

本文主要讲解ManimCE的安装与调试过程,尽管ManimCE和Manim出自同一个引擎,但是ManimCE和Manim可能会有相互不兼容的语句或者组件存在,这意味着你必须完全按照ManimCE的一整套流程进行操作。
最终二者可以做出相同的动画。

本文进行所有组件的安装的时候,都不要有中文路径,否则会出现错误。

安装过程

安装Python

一般来说,一台电脑基本不会随时去更新Python到最新的版本,而ManimCE仅仅对Python3版本有较好的兼容性。当你电脑中安装有Python2的时候,也许你不会愿意去卸载重新安装Python3。

在这里,我推荐使用Anaconda的虚拟环境,相当于一个独立的小房间,里面的Python和Python包可以随意安装,并不会和电脑本身的Python版本冲突。
你可以直接搜索Anaconda3,双击进行安装。
我们要使用的是Anaconda Prompt这个工具,打开之后是黑色的命令行。

需要注意的是,这里安装好Anaconda之后,需要找到conda.exe这个文件,然后将其路径复制到环境变量中。

创建并激活虚拟环境

在Anaconda Prompt下输入如下命令(若有提示,请输入y):

conda create -n manimCE

执行完成之后,Anaconda会创建一个虚拟环境,然后运行如下命令,进入到该虚拟环境下,运行成功后你会看见前面有一个圆括号,里面是(manimCE)字样,这说明你成功的进入了manimCE虚拟环境。

conda activate manimCE

ManimCE仅支持Python3.7-3.9,我们在虚拟环境下运行:

conda install python=3.9

这样,虚拟环境下会安装3.9版本的Python。

然后输入:

python --version

查看python版本,如果出现python 3.9.7或者类似的,只要是Python3.9即说明虚拟环境下Python安装成功。

安装FFmPeg

在虚拟环境下,直接输入如下命令:

conda install ffmpeg

输入y完成安装,这里可能会比较慢,请耐心等待。

安装Manim

直接在虚拟环境下输入:

pip install manim

输入y等待安装完成。

安装MikTeX

实际上MikTex是latex的一个发行版本,由于原作者Grand即使用MikTeX,为了降低出错的概率,这里同样使用MikTeX。
进入如下网站下载并且安装MikTeX即可,安装时候默认安装即可,也可以更改安装路径,但是路径不要有中文。
MikTeX网址

完成引擎安装

当你成功完成如上步骤,并且没有报错的时候,你已经完成了ManimCE动画引擎的安装。这时候你可以退出Anaconda Prompt了。

测试

打开Anaconda Prompt,输入

conda info --envs

查看环境变量。

输入

activate manimCE

进入虚拟环境。成功运行且不报错即可。

总结

尽管你到这里已经成功安装好了ManimCE,但是使用ManimCE引擎进行动画的制作还是路途漫漫,ManimCE的作者(维护团队)也给出了ManimCE的官方例程,但是我并不满足于只运行例程,而是想自己随心所欲的应用ManimCE。我自己也在不断地对Manim引擎进行学习,希望读者也能在自己的使用过程中有进步。

附录——ManimCE例程

#!/usr/bin/env python

from manim import *

# To watch one of these scenes, run the following:
# python --quality m manim -p example_scenes.py SquareToCircle
#
# Use the flag --quality l for a faster rendering at a lower quality.
# Use -s to skip to the end and just save the final frame
# Use the -p to have preview of the animation (or image, if -s was
# used) pop up once done.
# Use -n <number> to skip ahead to the nth animation of a scene.
# Use -r <number> to specify a resolution (for example, -r 1920,1080
# for a 1920x1080 video)


class OpeningManim(Scene):
    def construct(self):
        title = Tex(r"This is some \LaTeX")
        basel = MathTex(r"\sum_{n=1}^\infty \frac{1}{n^2} = \frac{\pi^2}{6}")
        VGroup(title, basel).arrange(DOWN)
        self.play(
            Write(title),
            FadeIn(basel, shift=DOWN),
        )
        self.wait()

        transform_title = Tex("That was a transform")
        transform_title.to_corner(UP + LEFT)
        self.play(
            Transform(title, transform_title),
            LaggedStart(*(FadeOut(obj, shift=DOWN) for obj in basel)),
        )
        self.wait()

        grid = NumberPlane()
        grid_title = Tex("This is a grid", font_size=72)
        grid_title.move_to(transform_title)

        self.add(grid, grid_title)  # Make sure title is on top of grid
        self.play(
            FadeOut(title),
            FadeIn(grid_title, shift=UP),
            Create(grid, run_time=3, lag_ratio=0.1),
        )
        self.wait()

        grid_transform_title = Tex(
            r"That was a non-linear function \\ applied to the grid",
        )
        grid_transform_title.move_to(grid_title, UL)
        grid.prepare_for_nonlinear_transform()
        self.play(
            grid.animate.apply_function(
                lambda p: p
                + np.array(
                    [
                        np.sin(p[1]),
                        np.sin(p[0]),
                        0,
                    ],
                ),
            ),
            run_time=3,
        )
        self.wait()
        self.play(Transform(grid_title, grid_transform_title))
        self.wait()


class SquareToCircle(Scene):
    def construct(self):
        circle = Circle()
        square = Square()
        square.flip(RIGHT)
        square.rotate(-3 * TAU / 8)
        circle.set_fill(PINK, opacity=0.5)

        self.play(Create(square))
        self.play(Transform(square, circle))
        self.play(FadeOut(square))


class WarpSquare(Scene):
    def construct(self):
        square = Square()
        self.play(
            ApplyPointwiseFunction(
                lambda point: complex_to_R3(np.exp(R3_to_complex(point))),
                square,
            ),
        )
        self.wait()


class WriteStuff(Scene):
    def construct(self):
        example_text = Tex("This is a some text", tex_to_color_map={"text": YELLOW})
        example_tex = MathTex(
            "\\sum_{k=1}^\\infty {1 \\over k^2} = {\\pi^2 \\over 6}",
        )
        group = VGroup(example_text, example_tex)
        group.arrange(DOWN)
        group.width = config["frame_width"] - 2 * LARGE_BUFF

        self.play(Write(example_text))
        self.play(Write(example_tex))
        self.wait()


class UpdatersExample(Scene):
    def construct(self):
        decimal = DecimalNumber(
            0,
            show_ellipsis=True,
            num_decimal_places=3,
            include_sign=True,
        )
        square = Square().to_edge(UP)

        decimal.add_updater(lambda d: d.next_to(square, RIGHT))
        decimal.add_updater(lambda d: d.set_value(square.get_center()[1]))
        self.add(square, decimal)
        self.play(
            square.animate.to_edge(DOWN),
            rate_func=there_and_back,
            run_time=5,
        )
        self.wait()


class AnimateSyntax(Scene):
    def construct(self):
        triangle = Triangle(color=RED, fill_opacity=1)
        self.play(DrawBorderThenFill(triangle))
        self.play(triangle.animate.shift(LEFT))
        self.play(triangle.animate.shift(RIGHT).scale(2))
        self.play(triangle.animate.rotate(PI/3))
        self.play(triangle.animate.set_color(GREEN))




class OpeningManim(Scene):
    def construct(self):
        title = Tex(r"This is some \LaTeX")
        basel = MathTex(r"\sum_{n=1}^\infty \frac{1}{n^2} = \frac{\pi^2}{6}")
        VGroup(title, basel).arrange(DOWN)
        self.play(
            Write(title),
            FadeIn(basel, shift=UP),
        )
        self.wait()

        transform_title = Tex("That was a transform")
        transform_title.to_corner(UP + LEFT)
        self.play(
            Transform(title, transform_title),
            LaggedStart(*[FadeOut(obj, shift=DOWN) for obj in basel]),
        )
        self.wait()

        grid = NumberPlane(x_range=(-10, 10, 1), y_range=(-6.0, 6.0, 1))
        grid_title = Tex("This is a grid")
        grid_title.scale(1.5)
        grid_title.move_to(transform_title)

        self.add(grid, grid_title)
        self.play(
            FadeOut(title),
            FadeIn(grid_title, shift=DOWN),
            Create(grid, run_time=3, lag_ratio=0.1),
        )
        self.wait()

        grid_transform_title = Tex(
            r"That was a non-linear function \\ applied to the grid"
        )
        grid_transform_title.move_to(grid_title, UL)
        grid.prepare_for_nonlinear_transform()
        self.play(
            grid.animate.apply_function(
                lambda p: p + np.array([np.sin(p[1]), np.sin(p[0]), 0])
            ),
            run_time=3,
        )
        self.wait()
        self.play(Transform(grid_title, grid_transform_title))
        self.wait()




class PlotExample(Scene):
    def construct(self):
        plot_axes = Axes(
            x_range=[0, 1, 0.05],
            y_range=[0, 1, 0.05],
            x_length=9,
            y_length=5.5,
            axis_config={
                "numbers_to_include": np.arange(0, 1 + 0.1, 0.1),
                "font_size": 24,
            },
            tips=False,
        )

        y_label = plot_axes.get_y_axis_label("y", edge=LEFT, direction=LEFT, buff=0.4)
        x_label = plot_axes.get_x_axis_label("x")
        plot_labels = VGroup(x_label, y_label)

        plots = VGroup()
        for n in np.arange(1, 20 + 0.5, 0.5):
            plots += plot_axes.plot(lambda x: x**n, color=WHITE)
            plots += plot_axes.plot(
                lambda x: x**(1 / n), color=WHITE, use_smoothing=False
            )

        extras = VGroup()
        extras += plot_axes.get_horizontal_line(plot_axes.c2p(1, 1, 0), color=BLUE)
        extras += plot_axes.get_vertical_line(plot_axes.c2p(1, 1, 0), color=BLUE)
        extras += Dot(point=plot_axes.c2p(1, 1, 0), color=YELLOW)
        title = Title(
            r"Graphs of $y=x^{\frac{1}{n}}$ and $y=x^n (n=1, 1.5, 2, 2.5, 3, \dots, 20)$",
            include_underline=False,
            font_size=40,
        )
        
        self.play(Write(title))
        self.play(Create(plot_axes), Create(plot_labels), Create(extras))
        self.play(AnimationGroup(*[Create(plot) for plot in plots], lag_ratio=0.05))



# See many more examples at https://docs.manim.community/en/stable/examples.html

使用时候,将以下代码复制到Manim文件夹下,命名为:basic.py,并且在该路径进入Anaconda的,激活ManimCE的虚拟环境。在虚拟环境下运行:

#低帧率动画
mainm basic.py -pql

或者

#高帧率动画
mainm basic.py -pqh

也许我制作的这个教程效果并不理想,也许你遇到了其它问题,你可以随时向我的邮箱发送信息共同讨论:
xlxlqqq@163.com

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐