manim第一階段總結

manim是一個基於python的數學動畫製作擎,想要深入瞭解的朋友,可以去3b1b看看


關於manim的入門,我已經在我的另一篇文章中介紹了(manim入門),下面的主要內容是和視頻具體創作過程相關的。這篇文章的內容主要涉及到幾何圖形、文本、公式的創建、佈局及動畫展示。下面不會有具體的動畫展示內容,更多的是一種直接的功能說明。我將會介紹在這些基本對象的創建與展示的過程中用到的方法,同時會對這些方法有一個創作流程級(每一個行業,每一個民族,每一個個人,都有它們的一條流程,行業中叫做行業規範,民族中叫做民風民俗,個人叫做個性特徵,同樣manim也有它的創作所遵循的基本流程與思想)的總結。如果你是想照着文章內容寫程序,也許可以成功,也許不行。成功當然OK,如果失敗了,那麼就去多查一些其它的資料解決它(最好是官方推薦的),畢竟找問題並解決它是學習的最好方法


對一些基本概念的認識

我們從一個小例子說起

class Shapes(Scene):
    #A few simple shapes
    def construct(self):
        circle = Circle()
        square = Square()
        line=Line(np.array([3,0,0]),np.array([5,0,0]))
        triangle=Polygon(np.array([0,0,0]),np.array([1,1,0]),np.array([1,-1,0]))

        self.add(line)
        self.play(ShowCreation(circle))
        self.play(FadeOut(circle))
        self.play(GrowFromCenter(square))
        self.play(Transform(square,triangle))

這個例子雖然小,但它其實已經包含了創建一個manim動畫所需要的一切基本組件,我們看到Shapes這個類繼承自Scene,我們就把這個類叫做Shapes Scene,也可以說Shapes是一個Scene,這個Scene將會告訴manim在哪裏,如何,放置哪些對象。據說,3b1b上的動畫並不是一體化成型的,而是由一個個這樣的Scene通過視頻剪輯軟件拼接起來的,每一個Scene中都有一個construct函數,在執行腳本的時候,它會被默認調用。在這個函數裏,你需要實例化所有的對象,控制對象所需的代碼,放置對象在屏幕上所需的代碼以及與對象相關的動畫代碼。

在這個Scene中,我在construct函數裏創建了一個圓、一個矩形,一條線,和一個多邊形。下面是一個空行,然後就是這些對象相關的動畫,其中的.play()方法是manim中最重要的方法之一,它是用來具體執行與對象相關的各種動畫的方法。可以被執行的變換有很多,這裏有ShowCreation(circle)、FadeOut(circle)、GrowFromCenter(square)、Transform(square,triangle),這些變換方法都可以在相關的文件中找到,具體是哪些文件,我們後面會具體展示。

創建幾何圖形對象

先展示一個例子

class MoreShapes(Scene):
    def construct(self):
        circle = Circle(color=PURPLE_A)
        square = Square(fill_color=GOLD_B, fill_opacity=1, color=GOLD_A)
        square.move_to(UP+LEFT)
        circle.surround(square)
        rectangle = Rectangle(height=2, width=3)
        ellipse=Ellipse(width=3, height=1, color=RED)
        ellipse.shift(2*DOWN+2*RIGHT)
        pointer = CurvedArrow(2*RIGHT,5*RIGHT,color=MAROON_C)
        arrow = Arrow(LEFT,UP)
        arrow.next_to(circle,DOWN+LEFT)
        rectangle.next_to(arrow,DOWN+LEFT)
        ring=Annulus(inner_radius=.5, outer_radius=1, color=BLUE)
        ring.next_to(ellipse, RIGHT)

        self.add(pointer)
        self.play(FadeIn(square))
        self.play(Rotating(square),FadeIn(circle))
        self.play(GrowArrow(arrow))
        self.play(GrowFromCenter(rectangle), GrowFromCenter(ellipse), GrowFromCenter(ring))

我們可以以各種各樣的方式創建各種各樣的幾何圖形,在geometry.py這個文件中,我們可以找到各種幾何圖形的類(圓弧Arc,兩點圓弧ArcBetweenPoints,曲線箭頭CurvedArrow,曲線雙箭頭CurvedDoubleArrow,圓Circle,點Dot,小點SmallDot,橢圓Ellipse,環扇AnnularSector,扇形Sector,環Annulus,線Line,多邊形Polygon,三角形Triangle,矩形Rectangle等等),基於這些類我們還可以創建屬於自己的新的幾何圖形。這些幾何圖形有多種參數供我們設置,我們以上面的例子來說明。我這裏僅僅介紹如何改一些基本的參數,具體改動這些參數之後有什麼效果,可以自己去實踐觀察。
先看圓,我們給了它一個color=PURPLE_A參數,這是一個關鍵字參數,它會被傳遞給Circle這個類的一個實例變量,具體細節可以去看看源碼,其實我也只是看了個大概,我這裏要介紹的是在哪裏可以知道能夠設置哪些變量。我們在進入一個類的時候,都會看到裏面有一個CONFIG字典,這個字典裏面包含了這個類的一些屬性,我們可以一層一層地向上追溯這些類的父類爺爺類等,找到所有的CONFIG,這些CONFIG裏面的內容基本上便可以滿足我們的修改需求了。
有了上面的基本認識,再看Square裏面的參數便不難理解了,那些參數都是在它的類或者更上層的類中找到的。代碼中的其它一些需要位置參數的圖形需要根據具體的參數意義去進行參數設定。

佈局幾何圖形對象

關於佈局,任何的佈局,我們首先要了解的是座標系統,下面的代碼是是constants.py裏面的內容

FRAME_HEIGHT = 8.0
FRAME_WIDTH = FRAME_HEIGHT * DEFAULT_PIXEL_WIDTH / DEFAULT_PIXEL_HEIGHT
FRAME_Y_RADIUS = FRAME_HEIGHT / 2
FRAME_X_RADIUS = FRAME_WIDTH / 2

我們可以看到,高度默認被分成了8份(通過更改這個值,可以實現座標系統精度的控制,也就是屏幕被切割成多少個小方塊),寬度按照與高度的比例被分成相應的份數(如果寬高比是2,那麼,寬度將被分成16份),最終我們應該在腦海中有一個由小方塊組成的座標系統,座標系統原點在視頻中心。
有了manim座標系統的知識之後,還是以上面的小例子來說明幾何圖形對象的佈局

class MoreShapes(Scene):
    def construct(self):
        circle = Circle(color=PURPLE_A)
        square = Square(fill_color=GOLD_B, fill_opacity=1, color=GOLD_A)
        square.move_to(UP+LEFT)
        circle.surround(square)
        rectangle = Rectangle(height=2, width=3)
        ellipse=Ellipse(width=3, height=1, color=RED)
        ellipse.shift(2*DOWN+2*RIGHT)
        pointer = CurvedArrow(2*RIGHT,5*RIGHT,color=MAROON_C)
        arrow = Arrow(LEFT,UP)
        arrow.next_to(circle,DOWN+LEFT)
        rectangle.next_to(arrow,DOWN+LEFT)
        ring=Annulus(inner_radius=.5, outer_radius=1, color=BLUE)
        ring.next_to(ellipse, RIGHT)

        self.add(pointer)
        self.play(FadeIn(square))
        self.play(Rotating(square),FadeIn(circle))
        self.play(GrowArrow(arrow))
        self.play(GrowFromCenter(rectangle), GrowFromCenter(ellipse), GrowFromCenter(ring))

幾何圖形佈局主要分爲兩大種方法:基於自身方法,基於外界方法,在這個例子中主要是基於自身方法的佈局,基於外界方法的佈局後面介紹,從這個例子中,我們看到了.move_to、.shift、.surround、.next_to等方法,這些都是Mobject這個類所提供的方法,由於大多數幾何圖形都是基於這個類的,所以大多數幾何圖形都能夠使用這些方法來對自身進行佈局,下面我們來說說一些常用佈局的含義,以及使用方法。
.shift():通過觀察其函數定義,我們發現,它可以接收無數個元組或者np.array類型的參數,在這裏,我們給它的是2DOWN+2RIGHT,DOWN和RIGHT都是np.array類型的對象,它們由三個數組成,每一個代表座標系統的一維(manim座標系統的三維指的是:上下、左右、內外),這些np.array對象可以進行組合以達到幾何圖形的佈局功能(關於DOWN這些常量,我們可以在constants.py這個文件中找到,可以自己去觀察一下,相信大有裨益
.move_to():通過觀察其函數定義,我們發現,我們可以傳遞一個np.array數組,也可以傳遞一個對象,傳遞np.array數組,我們將直接得到傳送目的地座標,傳遞對象,我們將在函數內部通過相應的操作得到這個對象的位置,然後把這個位置當作傳送目的地座標
.surround():通過觀察函數的定義,它必須接受一個被環繞對象,創建完之後,調用這個方法的對象就會在佈局上圍繞着這個被環繞對象
.next_to():通過觀察函數定義,我們可以發現,它必須接收一個幾何對象或者一個np.array數組,同時它可以通過direction這個關鍵字參數設定靠近方向,還有很多可以調整的參數。
.to_corner():用來將對象放置到角落,這個角落可以設置
.to_edge():用來將對象放置到視頻邊緣
這些方法mobject.py文件中。

動畫展示幾何圖形

對於這種操作,.play()這個函數十分應該是最重要的,通過觀察函數的定義,我發現,.play可以接收動畫對象,這些動畫對象主要由animation這個文件夾中的.py文件生成,我們來看一些由哪些文件。
在這裏插入圖片描述
我們來一個一個文件看

  • animation.py:這個文件中存放了了一個所有動畫類的基類:Animation
  • composition.py功能目前不清楚
  • creation.py:在這個文件中的類,可以用來創建不同的動畫出現方式類,將想要顯示出來的幾何對象傳送給對應的類從而實現一個動畫實例對象,將這個動畫實例對象傳送給.play()便可以實現幾何圖像出現時的動畫控制。
  • fading.py:這個文件中的類是用來創建淡入淡出效果的
  • growing.py:這是控制對象的如何以生長的效果出現的
  • indication.py:這個文件中展示的動畫效果很豐富,我不知道怎麼形容,但是十分有趣,可以自己去看看。
  • movement.py:這個文件讓我們可以控制對象的移動效果
  • numbers.py功能目前不清楚
  • rotation.py:用來控制幾何圖形對象的旋轉
  • specialized.py:這裏面是一些用於特定對象的動畫類,比較特殊,沒有通用性
  • transform.py:這個文件是用來控制對象從一種形態向另一種形態或者另一個對象變換的
  • update.py功能目前不清楚

創建文本對象

還是從一個小例子說起

class AddingText(Scene):
    #Adding text on the screen
    def construct(self):
        my_first_text=TextMobject("Writing with manim is fun")
        second_line=TextMobject("and easy to do!")
        second_line.next_to(my_first_text,DOWN)
        third_line=TextMobject("for me and you!")
        third_line.next_to(my_first_text,DOWN)

        self.add(my_first_text, second_line)
        self.wait(2)
        self.play(Transform(second_line,third_line))
        self.wait(2)
        second_line.shift(3*DOWN)
        self.play(ApplyMethod(my_first_text.shift,3*UP))

我們使用TextMobject這個類來實例化一個圖形化文本對象(關於這個類的內部源碼探究,後期再看),這個類接收一個字符串,我們可以控制這些字體的大小、顏色、對齊等屬性。我們還可以單獨控制某個單詞的顏色,這些控制方法都可以通過查看看它的相關類來知曉。字符串的創建方法就是這樣。

佈局文本對象

佈局方法和上面的幾何圖形類似

動畫展示文本對象

大部分的變換操作與幾何圖形類似,但這裏要講解前面的基於自身方法,基於外界方法中的基於外界方法,我們這裏用到了一個方法ApplyMethod(),在上面小例子中的最後一行。下面來仔細研究一下這個類

class ApplyMethod(Transform):
    def __init__(self, method, *args, **kwargs):
        """
        method is a method of Mobject, *args are arguments for
        that method.  Key word arguments should be passed in
        as the last arg, as a dict, since **kwargs is for
        configuration of the transform itslef

        Relies on the fact that mobject methods return the mobject
        """
        self.check_validity_of_input(method)
        self.method = method
        self.method_args = args
        super().__init__(method.__self__, **kwargs)

    def check_validity_of_input(self, method):
        if not inspect.ismethod(method):
            raise Exception(
                "Whoops, looks like you accidentally invoked "
                "the method you want to animate"
            )
        assert(isinstance(method.__self__, Mobject))

這是這個類的構造函數,作者給這個構造函數做了一些文檔字符串用以說明這個類的使用方法。從文檔字符串中我們可以知道一些信息:method是一個Mobject對象的方法(注意一定要是某個對象的方法,如circle.shift),*args是用來提供給circle.shift的參數,後面是一些關鍵字參數。在後面的代碼中,首先檢查了method參數的有效性,然後將method賦值給這個self.method,將args賦值給self.method_args,緊接着是調用父類的構造函數,它的父類是Transform

class Transform(Animation):
    CONFIG = {
        "path_arc": 0,
        "path_arc_axis": OUT,
        "path_func": None,
        "replace_mobject_with_target_in_scene": False,
    }

    def __init__(self, mobject, target_mobject=None, **kwargs):
        super().__init__(mobject, **kwargs)
        self.target_mobject = target_mobject
        self.init_path_func()

由此我們得到一個等價邏輯,ApplyMethod(circle0.shift, UP) 等價於使用Transform將當前的cricle0變換成一個由circle0移動一個UP後的新的對象
下面再介紹一些相關的操作(不限於文本使用)

  • .set_color():可以用來設置顏色
  • .scale():可以用來設置大小,這裏的大小是基於當前大小的倍數
  • .get_corner(): 傳入的參數是UP或者DOWN,這個可以用來獲取一個相對於某個對象的位置的座標,比如說,- circle0.get_corner(DOWN+RIGHT)是獲取一個圓的左下角的座標,通過閱讀源碼,我發現它的返回值是一個np.array數組。
  • .match_color():用來將對象設置爲某個對象的顏色
  • .set_color_by_gradient():用來設置對象的顏色爲彩虹漸變色
  • BackgroundRectangle(label,fill_opacity=0.5):爲label文本對象創建一個背景矩形,只能爲黑色
  • SurroundingRectangle(label2,color=BLUE,fill_color=RED, fill_opacity=.5):爲文本創建一個矩形框,顏色可以設定。
  • VGroup():這個是用來將多個對象進行組合

創建公式對象

下面是一個小例子

class BasicEquations(Scene):
    #A short script showing how to use Latex commands
    def construct(self):
        eq1=TextMobject("$\\vec{X}_0 \\cdot \\vec{Y}_1 = 3$")
        eq1.shift(2*UP)
        eq2=TexMobject(r"\vec{F}_{net} = \sum_i \vec{F}_i")
        eq2.shift(2*DOWN)

        self.play(Write(eq1))
        self.play(Write(eq2))

第一種方法是使用TextMobject(),這種方法不好。我們使用TexMobject方法,這種方法要求我們提供原始latex字符串(就是在字符串前面加個r)。可能有的朋友對latex不太熟悉,那沒辦法,只能自己去學習一下了(不用害怕,很簡單)。

佈局公式對象

下面是一個小例子

class UsingBraces(Scene):
    #Using braces to group text together
    def construct(self):
        eq1A = TextMobject("4x + 3y")
        eq1B = TextMobject("=")
        eq1C = TextMobject("0")
        eq2A = TextMobject("5x -2y")
        eq2B = TextMobject("=")
        eq2C = TextMobject("3")
        eq1B.next_to(eq1A,RIGHT)
        eq1C.next_to(eq1B,RIGHT)
        eq2A.shift(DOWN)
        eq2B.shift(DOWN)
        eq2C.shift(DOWN)
        eq2A.align_to(eq1A,LEFT)
        eq2B.align_to(eq1B,LEFT)
        eq2C.align_to(eq1C,LEFT)

        eq_group=VGroup(eq1A,eq2A)
        braces=Brace(eq_group,LEFT)
        eq_text = braces.get_text("A pair of equations")

        self.add(eq1A, eq1B, eq1C)
        self.add(eq2A, eq2B, eq2C)
        self.play(GrowFromCenter(braces),Write(eq_text))

如果前面理解得不錯的話,那麼一直到braces = Brace(eq_group,left)應該都夠流暢地理解(如果不能也沒關係,要麼重新看看本篇文章,要麼去找別的資料看看,可能我的的思路或者表達風格不適合你)。Brace是大括號的意思,我們在eq_group左邊創建了一個大括號,同時這個大括號跟着一些文本,在後面的動畫顯示中,我們會把這個大括號和文本顯示出來。
有沒有覺得上面的方法過於繁瑣,我第一眼見到這樣的操作就感覺這實在是過於繁瑣,一定有優雅一點的方法實現這些效果,下面是一個小例子

class UsingBracesConcise(Scene):
    #A more concise block of code with all columns aligned
    def construct(self):
        eq1_text=["4","x","+","3","y","=","0"]
        eq2_text=["5","x","-","2","y","=","3"]
        eq1_mob=TexMobject(*eq1_text)
        eq2_mob=TexMobject(*eq2_text)
        eq1_mob.set_color_by_tex_to_color_map({
            "x":RED_B,
            "y":GREEN_C
            })
        eq2_mob.set_color_by_tex_to_color_map({
            "x":RED_B,
            "y":GREEN_C
            })
        for i,item in enumerate(eq2_mob):
            item.align_to(eq1_mob[i],LEFT)
        eq1=VGroup(*eq1_mob)
        eq2=VGroup(*eq2_mob)
        eq2.shift(DOWN)
        eq_group=VGroup(eq1,eq2)
        braces=Brace(eq_group,LEFT)
        eq_text = braces.get_text("A pair of equations")

        self.play(Write(eq1),Write(eq2))
        self.play(GrowFromCenter(braces),Write(eq_text))

這裏用到了python中的解包操作與enumerate()函數以及.set_color_by_tex_to_color_map()成員函數,理解這幾個函數其餘的應該就沒問題了

動畫展示公式對象

這個和前面的幾何圖形對象與文本對象的展示是一樣的方法,如果有特殊的操作,我後面會補充


總結

我在這裏對上面manim做一個創作流程級的總結。
manim的基本創作流程是創建對象、佈局對象、動畫展示對象,其中創建對象有的人喜歡用到什麼創建什麼,有的人喜歡一次性創建完,這要看個人喜好。佈局對象,根據我們上面的總結,我們可以發現大概有兩種方法:基於自身方法(如.next_to),基於外界方法(如VGroup),動畫展示對象中有兩個最重要的方法.add()和.play()所有創建的動畫都要通過.play()展示出來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章