3Blue1Brown 复刻(二):线性变换与矩阵的几何意义
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 乘好一次变,分两次才能传达"先后作用"