本文将详细介绍如何使用 Manim 框架来实现一个逼真的闪电特效。我本人对这篇文章不了解,并不知晓里面代码的编写思路,而且原作者给出的代码也不是很完整,我利用AI进行了补充,勉强的能够运转,有一些闪电的效果,但是个人不是很满意,更重要的是对原文章进行了调整,希望原作者能够谅解。
1. 实现原理
闪电特效通过LightningAnimation类实现,该类继承自 Manim 的基础Animation类,主要基于以下几个核心原理,代码开始,需要先引入Python的一些库:
1 2
| from manim import * import numpy as np
|
1.1. LightningAnimation 类的参数
LightningAnimation 类提供了丰富的参数选项,可以灵活调整闪电效果的各种特性。
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
| class LightningAnimation(Animation): """ 闪电特效动画类 通过创建多个带有尖锐转折的折线,并动态调整折线的形状、颜色和透明度来模拟闪电的效果 包含主闪电和分叉闪电效果 """ def __init__( self, start_point, end_point, num_points=10, thickness_range=(0.1, 0.5), color=YELLOW, alpha_range=(0.5, 1), speed=1, num_forks=3, fork_probability=0.3, **kwargs ): """ 初始化闪电动画
参数: - start_point: 闪电起点坐标 - end_point: 闪电终点坐标 - num_points: 闪电中的转折点数量 - thickness_range: 闪电粗细范围 (最小值, 最大值) - color: 闪电颜色 - alpha_range: 透明度范围 (最小值, 最大值) - speed: 动画速度 - num_forks: 分叉闪电的数量 - fork_probability: 分叉闪电出现的概率 """ pass # 省略... ...
|
参数主要含义:
- 位置参数:
start_point : 闪电的起点坐标,使用 NumPy 数组表示 (x, y, z)
end_point : 闪电的终点坐标,同样使用 NumPy 数组表示 (x, y, z)
- 形状与复杂度参数:
num_points : 闪电中的转折点数量,默认为 10
- 数值越大,闪电的路径越复杂,锯齿状越明显
- 数值越小,闪电越接近直线
num_forks : 分叉闪电的数量,默认为 3
fork_probability : 分叉闪电出现的概率,默认为 0.3
- 控制每次动画播放时,实际显示的分叉数量
- 值为 0 时不会显示分叉,值为 1 时所有分叉都会显示
- 视觉效果参数:
thickness_range : 闪电粗细范围,格式为 (最小值, 最大值),默认为 (0.1, 0.5)
- 控制闪电的最大和最小宽度
- 闪电在动画过程中会在此范围内动态变化
color : 闪电颜色,默认为 YELLOW
- 可以使用 Manim 提供的颜色常量,如 YELLOW、WHITE、BLUE 等
alpha_range : 透明度范围,格式为 (最小值, 最大值),默认为 (0.5, 1)
- 动画参数:
speed : 动画速度,默认为 1
- 控制闪电闪烁和动态变化的速率
- 数值越大,闪电变化越快
1.2. 折线生成与随机偏移
闪电的基本形状是通过在起点和终点之间创建多个带有随机偏移的转折点来实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| def initialize_points(self): """初始化闪电的转折点""" # 在起点和终点之间均匀分布转折点 for i in range(1, self.num_points + 1): t = i / (self.num_points + 1) # 基础位置 self.points[i] = self.start_point + t * self.vector # 添加随机偏移 perpendicular = np.array([-self.direction[1], self.direction[0], 0]) offset_strength = self.distance * 0.1 random_offset = np.random.uniform(-offset_strength, offset_strength) self.points[i] += random_offset * perpendicular # 初始化分叉点 self.initialize_forks() self.update_lightning_shape()
|
这段代码首先在起点和终点之间均匀分布转折点,然后对每个转折点添加垂直于闪电方向的随机偏移,从而模拟闪电不规则的锯齿形状。
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 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 111 112 113 114 115 116 117
| def initialize_forks(self): """初始化分叉闪电系统""" self.forks = [] for f in range(self.num_forks): # 随机决定是否生成此分叉 if np.random.random() > self.fork_probability: continue # 随机选择分叉点在主闪电上的位置 fork_start_index = np.random.randint(2, max(3, self.num_points - 2)) fork_start_point = self.points[fork_start_index] # 计算分叉方向 fork_direction = self.direction + np.array([ np.random.uniform(-0.5, 0.5), np.random.uniform(-0.5, 0.5), 0 ]) # 确保方向向量不是零向量 norm = np.linalg.norm(fork_direction) if norm > 0: fork_direction = fork_direction / norm else: fork_direction = np.array([0, 1, 0]) # 分叉长度 fork_length = self.distance * np.random.uniform(0.1, 0.3) fork_end_point = fork_start_point + fork_direction * fork_length # 为分叉创建转折点 fork_points = 5 fork_point_list = np.zeros((fork_points + 2, 3)) fork_point_list[0] = fork_start_point fork_point_list[-1] = fork_end_point # 添加随机偏移到分叉点 fork_vector = fork_end_point - fork_start_point for i in range(1, fork_points + 1): t = i / (fork_points + 1) fork_point_list[i] = fork_start_point + t * fork_vector # 分叉的随机偏移 fork_perpendicular = np.array([-fork_direction[1], fork_direction[0], 0]) fork_offset = fork_length * 0.15 random_fork_offset = np.random.uniform(-fork_offset, fork_offset) fork_point_list[i] += random_fork_offset * fork_perpendicular self.forks.append(fork_point_list)
def update_lightning_shape(self): """更新闪电形状""" # 更新主闪电的点 self.lightning_line.set_points_as_corners(self.points) # 添加分叉闪电 for fork_points in self.forks: fork_line = VMobject() fork_line.set_points_as_corners(fork_points) fork_line.set_stroke( color=self.color, width=self.thickness_range[1] * 0.7, opacity=self.alpha_range[1] * 0.8 ) self.lightning_line.add(fork_line)
def interpolate_mobject(self, alpha): """在动画过程中不断更新闪电的形状、颜色和透明度""" # 计算时间参数,使用正弦波来创造闪烁效果 time = self.speed * alpha # 动态调整转折点位置 new_points = np.copy(self.points) for i in range(1, self.num_points + 1): # 添加动态偏移,使闪电有"扭动"效果 dynamic_offset = 0.05 * self.distance * np.sin(time * 10 + i) perpendicular = np.array([-self.direction[1], self.direction[0], 0]) new_points[i] = self.points[i] + dynamic_offset * perpendicular # 更新主闪电的点 self.lightning_line.set_points_as_corners(new_points) # 动态调整透明度,创造闪烁效果 alpha_value = self.alpha_range[0] + ( self.alpha_range[1] - self.alpha_range[0] ) * (0.5 + 0.5 * np.sin(time * 20)) self.lightning_line.set_stroke(opacity=alpha_value) # 动态调整粗细 thickness = self.thickness_range[0] + ( self.thickness_range[1] - self.thickness_range[0] ) * (0.5 + 0.5 * np.sin(time * 15)) self.lightning_line.set_stroke(width=thickness) # 处理分叉闪电 for i, fork in enumerate(self.forks): new_fork_points = np.copy(fork) fork_vector = fork[-1] - fork[0] # 确保分叉向量不是零向量 if np.linalg.norm(fork_vector) > 0: fork_direction = fork_vector / np.linalg.norm(fork_vector) else: fork_direction = np.array([0, 1, 0]) # 为分叉添加动态效果 for j in range(1, len(fork) - 1): dynamic_fork_offset = 0.05 * np.linalg.norm(fork_vector) * np.sin(time * 12 + i * 3 + j) fork_perpendicular = np.array([-fork_direction[1], fork_direction[0], 0]) new_fork_points[j] = fork[j] + dynamic_fork_offset * fork_perpendicular # 更新分叉线 if i < len(self.lightning_line.submobjects) - 1: # 减去主闪电本身 fork_line = self.lightning_line.submobjects[i + 1] # +1 因为第一个是主闪电 fork_line.set_points_as_corners(new_fork_points) fork_line.set_stroke(width=thickness * 0.7, opacity=alpha_value * 0.8)
|
系统会随机选择主闪电上的点作为分叉起点,然后生成带有随机方向和长度的分叉闪电。每个分叉也有自己的随机转折点,使其看起来更加自然。
2. 使用示例
LightningAnimation类提供了丰富的参数,可以灵活调整闪电效果。下面是几个不同场景的使用示例:
2.1. 示例1:黄色对角闪电
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Example01(Scene): """示例1:黄色对角闪电""" def construct(self): # 设置背景色为深色,以突出闪电效果 self.camera.background_color = "#0d0d1a" # 深蓝色背景 # 创建闪电:从左上角到右下角,带分叉效果 start_point = np.array([-5, 3, 0]) end_point = np.array([5, -3, 0]) lightning = LightningAnimation( start_point, end_point, num_points=15, # 转折点数量 color=YELLOW, # 闪电颜色 thickness_range=(0.15, 0.6), # 粗细范围 speed=2, # 动画速度 num_forks=5, # 分叉数量 fork_probability=0.4 # 分叉出现概率 ) # 播放闪电动画 self.play(lightning, run_time=2) self.wait()
|
2.2. 示例2:白色对角闪电
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Example02(Scene): def construct(self): self.camera.background_color = "#0d0d1a" # 创建闪电:从右上角到左下角 start_point = np.array([5, 3, 0]) end_point = np.array([-5, -3, 0]) lightning = LightningAnimation( start_point, end_point, num_points=12, color=WHITE, thickness_range=(0.25, 0.7), speed=1.5, num_forks=4, fork_probability=0.3, )
self.play(lightning, run_time=2) self.wait()
|
2.3. 示例3:蓝色垂直闪电
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Example03(Scene): def construct(self): self.camera.background_color = "#0d0d1a" # 创建闪电:较短的垂直闪电 start_point = np.array([0, 4, 0]) end_point = np.array([0, 0, 0]) lightning = LightningAnimation( start_point, end_point, num_points=8, color=BLUE, alpha_range=(0.7, 1), thickness_range=(0.1, 0.4), speed=3, num_forks=3, fork_probability=0.5, )
self.play(lightning, run_time=1.5) self.wait()
|
这些示例展示了如何通过调整参数来创建不同形状、颜色和特性的闪电效果,以适应不同的场景需求。
3. 内容总结
3.1. 特效特点
- 高度可定制:通过调整参数可以创建各种不同类型的闪电效果
- 真实感强:结合随机因素和动态变化,模拟真实闪电的不规则性和闪烁效果
- 层次丰富:主闪电和分叉闪电系统共同作用,创造出复杂而自然的闪电效果
- 易于集成:作为Manim的Animation子类,可以方便地与其他Manim动画元素结合使用
3.2. 使用场景
- 天气现象模拟:在气象相关的可视化中展示雷电现象
- 科幻与奇幻场景:为魔法效果、能量释放或科幻场景增添视觉冲击力
- 强调与过渡:作为场景转换或重点内容强调的动态效果
- 教育演示:在物理课程中演示电场放电等相关概念
- 艺术创作:用于抽象动画和视觉艺术作品
通过这个LightningAnimation类,我们可以在Manim项目中轻松实现逼真的闪电特效,为动画作品增添生动的视觉元素。本文转自 https://www.cnblogs.com/wang_yb/p/19118455,如有侵权,请联系删除。