首页 / 文档中心 / 复刻

3Blue1Brown 复刻(二):线性变换与矩阵的几何意义

发布于 2026-11-02 · 复刻系列 · 约 4 分钟读完 · 中文正文 + English TL;DR
▶ 渲染效果实录(manim 渲染输出,代码见正文,可直接复现)
English TL;DR

The signature 3Blue1Brown shot: a flat grid of squares shears, rotates and stretches while the basis vectors i-hat and j-hat fly to new positions — a matrix made visible. This recreation uses LinearTransformationScene, Manim CE's built-in scene class for exactly this: it holds a background NumberPlane that smoothly re-grids, ghost vectors that leave traces of old positions, and an apply_matrix() method that plays the transformation. We first build intuition — linear means grid lines stay parallel and evenly spaced, so knowing where i-hat and j-hat land determines everything — then render the matrix [[1,1],[-2,1]]: i-hat lands on the first column (1,-2), j-hat on the second column (1,1), with the determinant visible as a cell-area change. Code includes column-highlighting text, composition of two transforms applied back to back, and a fallback manual implementation with ValueTracker + always_redraw for readers on older Manim versions.

要复刻什么

《线性代数的本质》第一章的招牌镜头:平面网格被矩阵"揉变形",基向量 î 和 ĵ 滑向新位置。观众由此建立一句话直觉:矩阵的列就是基向量落脚的地方。

官方武器:LinearTransformationScene

Manim CE 内置了这个场景类,网格重排、残影这些细节全部自动:

from manim import *

class MatrixGrid(LinearTransformationScene):
    def __init__(self):
        LinearTransformationScene.__init__(
            self,
            show_coordinates=False,
            leave_ghost_vectors=True,      # 旧基向量留残影
            background_plane_kwargs={
                "x_range": [-8, 8, 1], "y_range": [-5, 5, 1],
                "background_line_style": {"stroke_width": 1.5}},
        )

    def construct(self):
        M = [[1, 1], [-2, 1]]     # 第1列=(1,-2) 第2列=(1,1)

        # 标注矩阵的两列 = 基向量的去向
        col1 = MathTex("\begin{bmatrix} \\ 1 \\ -2 \\ \end{bmatrix}",
                       color=YELLOW).scale(0.8).to_corner(UL)
        col2 = MathTex("\begin{bmatrix} \\ 1 \\ 1 \\ \end{bmatrix}",
                       color=RED).scale(0.8).next_to(col1, RIGHT)

        self.play(Write(col1), Write(col2))
        self.wait()

        # 网格扭曲 + 基向量飞到矩阵列的位置
        self.apply_matrix(M)
        self.wait()

        # 复合变换: 再转 90 度
        self.apply_matrix([[0, -1], [1, 0]])
        self.wait()

渲染:manim -pqm linear.py MatrixGrid。self.i_hat / self.j_hat 就是两个基向量对象,apply_matrix 一次调用完成整段变形。

这段动画讲了什么数学

  • 矩阵 M = [[1,1],[-2,1]] 第 1 列 (1,-2) 是 î 的新位置,第 2 列 (1,1) 是 ĵ 的新位置
  • "线性"= 网格线始终保持平行且等距,所以两个基向量决定全平面
  • det(M) = 1×1 − 1×(−2) = 3:每个小方格面积变 3 倍,动画里网格明显"变密"——行列式的几何含义直接可见
  • 两次 apply_matrix 连续播放 = 矩阵乘法 AB 的"先后作用"直观解释

手动实现(老版本 / 想理解原理)

LinearTransformationScene 的本质是 ValueTracker 插值矩阵 + always_redraw 重建网格:

class ManualGrid(Scene):
    def construct(self):
        A = np.array([[1.0, 1.0], [-2.0, 1.0]])
        t = ValueTracker(0)

        def M():
            return (1 - t.get_value()) * np.eye(2) \
                   + t.get_value() * A

        def T(p):   # 二维点过当前混合矩阵
            v = M() @ np.array([p[0], p[1]])
            return np.array([v[0], v[1], 0])

        grid = always_redraw(lambda: VGroup(*[
            Line(T([x, -4, 0]), T([x, 4, 0]),
                 stroke_width=1.2, stroke_opacity=0.75)
            for x in np.arange(-7, 7.1, 1)] + [
            Line(T([-7, y, 0]), T([7, y, 0]),
                 stroke_width=1.2, stroke_opacity=0.75)
            for y in np.arange(-4, 4.1, 1)]))

        ihat = always_redraw(lambda: Arrow(
            ORIGIN, T([1, 0, 0]), buff=0, color=YELLOW, stroke_width=7))
        jhat = always_redraw(lambda: Arrow(
            ORIGIN, T([0, 1, 0]), buff=0, color=RED, stroke_width=7))

        self.add(grid, ihat, jhat)
        self.play(t.animate.set_value(1), run_time=4)
        self.wait()

读懂这 30 行,你就掌握了Updater 章 + 坐标系章的全部要点在真实项目里怎么组合。

复刻技巧清单

  • 残影:leave_ghost_vectors=True 让旧位置留半透明向量,对比感是原片节奏的一半
  • 配色纪律:î 永远绿、ĵ 永远红、向量结果用蓝色——观众跨视频记忆配色
  • 慢速变形:run_time 3~5 秒,太快观众来不及感受网格保持平行的过程
  • 复合分两次 play:别把 AB 乘好一次变,分两次才能传达"先后作用"