主要内容
珀林噪音
一个好的随机数生成器生成的数字之间没有联系、也看不出什么模式。正如我们开始看到的,在对系统的、符合现实的行为进行编程时,一点随机性可能是一件好事。然而,将随机性作为单一的指导原则却不符合自然规则。
有一种能产生更自然的结果的算法,就是常说的 "柏林噪声"。在20世纪 80年代初,Ken Perlin 在拍摄原创电影时开发了噪音功能;在利用计算机生成特效时,他用这种方法的来创建程序的质感。1997年,Perlin 因这项工作的技术成就获得了奥斯卡奖。柏林噪声可用于生成具有符合自然特点的各种效果,比如云、景观和大理石等图案纹理。
柏林噪声看起来也更自然形象,因为它生成的是一系列自然有序 ("平滑") 的伪随机数。下图显示了随时间变化的 Perlin 噪声,其中x 轴表示时间;注意观察曲线的平滑度。
与之相对照的,下图表示的是随时间变化的纯粹随机数。
ProcessingJS 有一个 Perlin 噪声算法的内置程序:函数
noise()
。 noise()
函数可以设定一个、两个或三个参数,以便分别用于计算一维、二维或三维度噪声。我们从一维噪声开始,看看具体情形吧。噪声详情
噪声参考资料 告诉我们,噪音的音域跨越若干个 “八度。” 调用
noiseDetail()
函数既会改变声音的音域,也会改变声音之间的高低对比。通过这种方式反过来改变了噪声函数的结果。假设我们在 processingJS 窗口随机选择一个 x 位置绘制一个圆:
var x = random(0, width);
ellipse(x, 180, 16, 16);
现在,我们想要的不是随机的 x 位置,而是将圆画在 "更顺畅" 的 Perlin 噪声 x 位置。你可能会认为你需要做的只是把
random()
替换成 noise()
,程序变成如下:var x = noise(0, width);
ellipse(x, 180, 16, 16);
虽然从概念上讲,这正是我们要做的——根据 Perlin 噪声计算出介于0和宽度之间的 x 值——但在实际编程是这样做是错误的。虽然
random()
函数的参数可以在最小值和最大值之间取值, 但noise()
函数取值不是这样的。相反,我们在noise()
函数中输入的参数表示的是 "时间时刻" ,是一个始终介于0和1之间的值。 我们可以把一维的 Perlin 噪声看作是一个随着时间变化的一系列线性值。下面是一个输入和返回值的示例:时间 | 噪声值 |
---|---|
0 | 0.469 |
0.01 | 0.480 |
0.02 | 0.492 |
0.03 | 0.505 |
0.04 | 0.517 |
为了访问 processingJS 中的一个噪声值,我们要将特定的时间时刻传递给
noise()
函数。例如:var n = noise(0.03);
根据上表,
noise()
函数在时间值为0.03 时,返回0.505的噪声值。我们可以编写一个程序,存储一个时间变量,并在draw()
函数中连续请求噪声值作为其输入值。上述代码会导致反复输出相同的值。之所以会出现这种情况,是因为我们在同一个时间点上,反复询问
noise()
函数的结果。 但是,如果我们将时间变量
t
不断增大, 我们得到的结果却不同。我们增加
t
的变动速率也会影响噪声的平滑度。如果我们给定的时间变化间隔大, 那么数值就会跳跃性强,得出的值将更加随机。尝试运行几次上面的代码,将时间变化间隔分别设为 0.01,0.02,0.05,0.1,0.0001,你会看到不同的结果。
映射噪声
现在我们看看可以怎么使用噪声值的问题了。一旦我们有了范围在0到1之间的噪声值, 我们就可以将它用于映射出我们想要的范围了。
我们可以乘以范围内的最大数字,但更好的方式是新学习使用 processingJS 的
map()
函数,我们在以后更多的情况下会经常用到它。map()
函数有五个参数。首先是我们要映射的值,就是上面的n
。然后,我们必须给出它的取值的当前范围(最小值和最大值),接着是给出我们想要的范围。在这种情况下,虽然我们知道噪声的范围在0和1之间,但我们希望绘制一个宽度介于0和当前宽度之间的矩形。
我们可以将相同的逻辑应用到随机漫步上,并用 Perlin 噪声方法为位置分配其 x 和 y 值。
请注意上面的示例,这里需要一对额外变量:
tx
和ty
。这是因为我们需要跟踪两个时间变量, 一个用于Walker
对象的 x 轴位置,另一个用于 y 轴位置。 但这些变量有些奇怪。 为什么
tx
从0开始,ty
从 10, 000 开始?尽管这些数字是主观的选择,但我们非常具体地设置了具有不同值的两个初始时间变量。这是因为噪声函数是确定性的: 对于一个特定时间t
,它每次都为您返回相同的结果。如果我们在时间 t
点,同时要求获取 x
和 y
的噪声值,那么 x
和 y
会始终相等,这意味着 Walker
对象只会沿对角线移动。相反,我们只是使用噪声空间的两个不同部分,让 x
从0开始,y
从 10, 000 开始,这样x
和 y
就可以相互独立地进行绘制。事实上,这里并没有实际的时间概念。我们用时间只是比喻,帮助我们理解解噪声函数是如何运作的,但我们真正拥有的是空间,而不是时间。上图描述了一维空间中的一系列线性噪声值,我们可以随时在特定的 x 轴位置获取数值。在实例中,您经常会看到一个名为
xoff
的变量,它表示在噪声图上的 x 偏移量,而不是时间的变化 t
(如图中所示)。在接下来的挑战中,你会尝试用稍有不同的方式将噪声与
Walker
结合起来。好好享受编程乐趣!“自然模拟”系列课程是由 Daniel Shiffman 的 "编程的本质" 衍生而来,基于 知识共享 著名-非商用性 3.0 本地化许可协议。