使用Manim实现镜面反射特效

本文将介绍如何使用ManimCE框架实现镜面反射特效,让你的动画更加生动有趣。

1. 实现原理

镜面成像最主要的问题还是需要借助Python,借助Python实现镜面成像,需要引入函数库文件

1
2
from manim import *
import numpy as np

1.1. 对称点计算

实现镜面反射的核心是计算点关于直线的对称点。代码中的symmetry_point函数通过向量投影的方法计算对称点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def symmetry_point(p1, p2, p):
"""计算点p关于由两点p1和p2确定的直线的对称点"""
# 转换为numpy数组确保计算正确
p1 = np.array(p1)
p2 = np.array(p2)
p = np.array(p)
# 计算直线的方向向量
direction = p2 - p1
# 计算从p1到p的向量
vec_p1p = p - p1
# 计算投影长度(点p在直线上的投影点到p1的距离)
proj_length = np.dot(vec_p1p, direction) / np.dot(direction, direction)
# 计算点p在直线上的投影点
projection = p1 + proj_length * direction
# 对称点 = 2 * 投影点 - 原点
symmetric_point = 2 * projection - p
return np.array([symmetric_point[0], symmetric_point[1], 0])

这个函数使用了向量投影的数学原理,先找到点在直线上的投影,然后根据投影点计算对称点。具体步骤是:

  1. 计算直线的方向向量
  2. 计算从直线上一点到目标点的向量
  3. 计算该向量在直线方向上的投影长度
  4. 找到投影点坐标
  5. 通过公式 2 * 投影点 - 原点 计算对称点

1.2. 反射动画类设计

MirrorReflection 类继承自 ManimAnimation 类,用于创建和管理镜面反射效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class MirrorReflection:
"""
镜面反射管理器
参数:
mobject: 需要反射的原始对象
mirror: 镜面直线(默认为水平线)
reflect_opacity: 反射的填充不透明度因子 (0-1)
reflect_stroke_opacity: 反射的描边不透明度因子 (0-1)
"""
def __init__(
self,
mobject,
mirror: Line = None,
reflect_opacity=0.3,
reflect_stroke_opacity=0.5,
):
# 初始化参数
self.original = mobject
self.mirror = mirror if mirror else Line(LEFT, RIGHT)
self.reflect_opacity = reflect_opacity
self.reflect_stroke_opacity = reflect_stroke_opacity

# 创建反射对象并设置初始属性
self.reflection = mobject.copy()
self._update_reflection_position()
self.reflection.set_fill(opacity=self.reflect_opacity * mobject.get_fill_opacity())
self.reflection.set_stroke(opacity=self.reflect_stroke_opacity * mobject.get_stroke_opacity())

# 添加更新器以实现动态跟随
self.reflection.add_updater(lambda m: self._update_reflection(m))
super().__init__(mobject, **kwargs)

1.3. 动态更新机制

为了确保反射效果能够跟随原始对象的变化而实时更新,代码实现了几个关键方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def _update_reflection_position(self):
"""更新反射对象的位置(计算每个点的对称点)"""
points = self.original.get_points()
new_points = []
p1, p2 = self.mirror.get_start(), self.mirror.get_end()
for p in points:
# 使用对称点计算函数计算每个点的反射位置
new_points.append(symmetry_point(p1, p2, p))
# 应用新位置
self.reflection.set_points(new_points)

def _update_reflection(self, reflection_mobject):
"""更新反射对象的属性(位置、透明度)"""
temp_reflection = self.original.copy()
reflection_mobject.become(temp_reflection)
# 更新反射对象位置和透明度
self._update_reflection_position()
reflection_mobject.set_fill(
opacity=self.reflect_opacity * self.original.get_fill_opacity()
)
reflection_mobject.set_stroke(
opacity=self.reflect_stroke_opacity * self.original.get_stroke_opacity()
)

def get_reflection(self):
"""获取反射对象,用于添加到场景"""
return self.reflection

这些方法确保了无论原始对象如何移动、缩放或旋转,反射效果都会相应地更新,保持视觉上的一致性。

2. 使用示例

让我们看看如何在实际场景中使用这个镜面反射特效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class MirrorReflectionDemo(Scene):
def construct(self):
# 1. 创建对象和镜面
triangle = Triangle(color=BLUE, fill_opacity=0.8).shift(LEFT * 2)
mirror = Line(UP * 3, DOWN * 3, color=WHITE, stroke_width=4)

# 2. 创建反射效果
mirror_effect = MirrorReflection(
triangle,
mirror=mirror,
reflect_opacity=0.4, # 调整反射透明度
reflect_stroke_opacity=0.6
)
reflection = mirror_effect.get_reflection()

# 3. 添加到场景
self.add(mirror, triangle, reflection)
self.wait()

# 4. 演示动画 - 反射会实时更新!
self.play(triangle.animate.shift(UP))
self.wait(0.5)

self.play(triangle.animate.scale(1.5))
self.wait(0.5)

self.play(triangle.animate.shift(RIGHT * 3))
self.wait(0.5)

self.play(Rotate(triangle, angle=PI/2))
self.wait(0.5)

# 5. 复杂形状演示
square = Square(color=RED, fill_opacity=0.8).shift(RIGHT * 2 + DOWN)
square_reflection = MirrorReflection(square, mirror=mirror).get_reflection()
self.play(FadeIn(square), FadeIn(square_reflection))
self.wait()

self.play(square.animate.rotate(PI/3).shift(UP))
self.wait(2)

使用步骤非常简单:

  1. 创建需要添加反射效果的原始对象(这里是一个蓝色三角形)
  2. 创建镜面(这里是一条白色垂直线)
  3. 创建MirrorReflection实例,并传入原始对象和镜面
  4. 通过create_reflection_mobject()方法获取反射对象
  5. 将原始对象、镜面和反射对象添加到场景中
  6. 对原始对象执行各种动画操作,观察反射效果的实时更新

在这个例子中,我们演示了对象的上移、缩放、下移和旋转四种操作,反射效果都会实时跟随更新,保持与原始对象的对称关系。

3. 内容总结

从上面的视频演示效果来看,镜面成像的代码还是比较实用的,代码可以实现我们的制作需求,并有如下特点

3.1. 特效特点

这个镜面反射特效具有以下特点:

  1. 实时更新:无论原始对象如何变换,反射效果都会实时更新,保持视觉一致性
  2. 高度可定制:可以调整反射对象的填充透明度和描边透明度,创建不同的视觉效果
  3. 灵活的镜面设置:可以自定义镜面的位置、方向和样式,适应不同场景需求
  4. 易用性:封装成了独立的动画类,使用简单,只需几行代码就能添加专业的反射效果

3.2. 使用场景

镜面反射特效适用于多种场景:

  1. 数学教学:用于几何对称、坐标系变换等概念的可视化教学
  2. 物理模拟:模拟光的反射、镜像对称等物理现象
  3. 艺术效果:为动画添加美感和层次感,提升视觉吸引力
  4. 交互演示:用于展示对称关系、变换过程等
  5. Logo和品牌展示:创建镜像效果的动态Logo动画

通过这个简单而强大的镜面反射特效,可以为你的Manim动画增添更多的视觉魅力和专业感。本文转自 https://www.cnblogs.com/wang_yb/p/19109427,如有侵权,请联系删除。附带我修改之后的代码,仅供朋友们参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
from manim import *
import numpy as np

def symmetry_point(p1, p2, p):
"""计算点p关于由两点p1和p2确定的直线的对称点"""
p1 = np.array(p1)
p2 = np.array(p2)
p = np.array(p)
direction = p2 - p1
vec_p1p = p - p1
proj_length = np.dot(vec_p1p, direction) / np.dot(direction, direction)
projection = p1 + proj_length * direction
symmetric_point = 2 * projection - p
return np.array([symmetric_point[0], symmetric_point[1], 0])

class MirrorReflection:
"""
镜面反射管理器

参数:
mobject: 需要反射的原始对象
mirror: 镜面直线(默认为水平线)
reflect_opacity: 反射的填充不透明度因子 (0-1)
reflect_stroke_opacity: 反射的描边不透明度因子 (0-1)
"""
def __init__(
self,
mobject,
mirror: Line = None,
reflect_opacity=0.3,
reflect_stroke_opacity=0.5,
):
self.original = mobject
self.mirror = mirror if mirror else Line(LEFT, RIGHT)
self.reflect_opacity = reflect_opacity
self.reflect_stroke_opacity = reflect_stroke_opacity

# 创建反射对象
self.reflection = mobject.copy()
self._update_reflection_position()
self.reflection.set_fill(opacity=self.reflect_opacity * mobject.get_fill_opacity())
self.reflection.set_stroke(opacity=self.reflect_stroke_opacity * mobject.get_stroke_opacity())

# 添加更新器以实现动态跟随
self.reflection.add_updater(lambda m: self._update_reflection(m))

def _update_reflection_position(self):
"""更新反射对象的位置(计算每个点的对称点)"""
points = self.original.get_points()
new_points = []
p1, p2 = self.mirror.get_start(), self.mirror.get_end()
for p in points:
new_points.append(symmetry_point(p1, p2, p))
self.reflection.set_points(new_points)

def _update_reflection(self, reflection_mobject):
"""更新反射对象的属性(位置、透明度)"""
temp_reflection = self.original.copy()
reflection_mobject.become(temp_reflection)
self._update_reflection_position()
reflection_mobject.set_fill(
opacity=self.reflect_opacity * self.original.get_fill_opacity()
)
reflection_mobject.set_stroke(
opacity=self.reflect_stroke_opacity * self.original.get_stroke_opacity()
)

def get_reflection(self):
"""获取反射对象,用于添加到场景"""
return self.reflection
class MirrorReflectionDemo(Scene):
def construct(self):
# 1. 创建对象和镜面
triangle = Triangle(color=BLUE, fill_opacity=0.8).shift(LEFT * 2)
mirror = Line(UP * 3, DOWN * 3, color=WHITE, stroke_width=4)

# 2. 创建反射效果
mirror_effect = MirrorReflection(
triangle,
mirror=mirror,
reflect_opacity=0.4, # 调整反射透明度
reflect_stroke_opacity=0.6
)
reflection = mirror_effect.get_reflection()

# 3. 添加到场景
self.add(mirror, triangle, reflection)
self.wait()

# 4. 演示动画 - 反射会实时更新!
self.play(triangle.animate.shift(UP))
self.wait(0.5)

self.play(triangle.animate.scale(1.5))
self.wait(0.5)

self.play(triangle.animate.shift(RIGHT * 3))
self.wait(0.5)

self.play(Rotate(triangle, angle=PI/2))
self.wait(0.5)

# 5. 复杂形状演示
square = Square(color=RED, fill_opacity=0.8).shift(RIGHT * 2 + DOWN)
square_reflection = MirrorReflection(square, mirror=mirror).get_reflection()
self.play(FadeIn(square), FadeIn(square_reflection))
self.wait()

self.play(square.animate.rotate(PI/3).shift(UP))
self.wait(2)

好了,感谢大家来到老刘博客,希望今天分享的内容对您有所帮助。