多彩的线条并排在画板上舞动,是不是看着很熟悉,这就是 ribbon line。本次教程我们将一起来实现基于 ribbon line 的生成艺术作品。
通过仔细观察,我们可以看出有如下几个特点:
基于上述特点,我们重点将其实现分为两个部分:
一条线的运动基础是圆周运动,我们一起来看看如何基于 p5.js 绘制出一个圆周运动的轨迹。
如何实现一个最简单的圆周运动?我们在点和线的乐章中已经具体地做了说明,这里我们再来回顾一下下面这个示意图。
要让 A 点做圆周运动,我们只需要确定圆心,半径,运动的夹角,每次渲染循环时,增加点 A 运动的夹角即可。
下面是示例的代码
上面的代码中,我们定义了一个变量 theta
,它代表了当前的夹角值,根据勾股定理,我们可以通过半径 radius
和夹角 theta
,计算出特定夹角所对应的坐标点的位置。
|
|
我们将此绘制过程进一步可视化一下
通过勾股定理,我们计算出了每一个夹角对应的位置,然后调用 line
方法,将上一次的位置和本次的位置连接成一条线段。当每一次渲染时 theta
变化量足够小的时候,最终就呈现出来一个圆形。
你可以试着修改一下上面的 theta += 0.01
,让每次变化增大一些,看看有什么变化。
如果只是做简单的圆周运动,那么我们将会看到一个圆环,而不会有任何变化的效果。要让线条的运动中隐藏一些变化,我们需要在规律的运动中添加一些随机性。
为了实现这一点,我们可以在每次渲染时让圆心发生变化,使用每次运动的位置作为下一次的圆心。同时我们在每次渲染时,动态地修改半径值。
关键代码如下
|
|
如此,在下一次渲染循环 draw 被调用时,圆周运动的圆心点就是上一次的位置了。
random
函数是 p5.js 的一个内置的获取随机数的函数,通过 random
函数我们获取一个介于 15 和 35 之间的随机数,并将其赋值给 radius
,这样下一次渲染时,圆环就变得更加不规则,圆心和半径都发生了变化。
你可以试着修改一下 radius = random(15, 35)
这行代码中的随机数的范围,比如改成 -35 和 35,观察下会发生什么。
现在我们的线条在做随机的圆周运动了,但是看起来很单调,和我们开篇的效果不一致,因为我们的角度变化目前是递增的,导致线条周而复始地一直往顺时针的方向运动。
要让线条随机地顺时针或者逆时针运动,我们需要对 theta += 0.2
这行代码进行一些修改,让它每次的变化量也是随机变化的。
我们引入一个 change = 1
的变量,让 change
变量随机变化为 1
或者 -1
,并将 theta
修改为 theta += 0.2 * change
,如此,theta
的值将会随机变大和缩小,当 theta
变大时,线条将顺时针运动,当其变小时,线条将逆时针运动。
在上面的代码中,我们重点来看看如下几行代码
|
|
我们新定义了一个 change
的变量,并将其初始化为 1
在 theta
角度的变化中,我们将每次的变化量和 change 结合起来,最终会动态地确定 theta
是变大还是变小
如何确定 change 是增加还是减少呢?我们使用了 frameCount
属性
frameCount
是 p5.js 里的一个内置属性,代表了当前渲染了多少次,即 draw
函数调用了多少次frameCount % 100
是一个取余的操作,即 frameCount 除以 100
后的余数,由此我们得到了一个介于 0
到 100
之间的整数30%
的概率 change
会变为 1
, 即线条以顺时针运动,有 40%
的概率 change
会变成 -1
,即线条以逆时针运动,除此以外,线条保持之前的运动方向。现在线条可以随机的顺时针或者逆时针做随机圆周运动了,可是有时候移动的幅度太大,以至于线条都移动出画板可见区域了。我们需要对线条的位置做一些修正,当线条的位置移出画板后,将其重新定位到画板上去。
黑色的线条在白色的画板上舞动着,终归是有些单调,让我们给画板加点调料吧,这次我们将让线条的颜色随着时间的变化而发生渐变。
先来回顾一下颜色,我们在颜色之美这边文章中有详细介绍了颜色模式及其运用。我们知道在 p5.js 中,我们可以设置几种颜色模式 RGB
、HSB
、HSL
,在本例中,我们将使用 HSB
模式。
如何将时间的变化映射到 HSB
模式呢?我们可以使用前面用到的 frameCount
属性,它代表着渲染次数,而渲染次数是和时间有关的。我们只需要将 frameCount 这个无限增长的整数映射到颜色色相的区间即可。
关键代码如下:
|
|
colorMode(HSB, 100)
是 p5.js 提供的设置颜色模式的方法,我们将颜色模式设置为 HSB
模式,并将每个颜色分量设置为 100
stroke
是 p5.js 提供的设置绘图颜色的方法,我们可以基于这个方法来设置接下来的绘图颜色HSB
模式的四个颜色分量:hue
、Saturation
、Brightness
、Opacity
frameCount * 0.05 % 100
是我们实现的关键,我们先将 frameCount
进行了缩放,然后再除以 100
取余,为什么是除以 100
呢,因为我们在上一步设置了颜色的分量是 100,除以 100
再取余就可以将 frameCount
映射到 0 - 100
的色相区间了下面是我们加上颜色后的代码和运行效果
可以看到,随着时间的流逝,线条舞动的颜色也在随之变化。
为了实现多条线同时运动,我们需要绘制几条平行线。我们的线段都是由点连接而成,所以我们只需要在每一个点绘制的同时,再多绘制几个平行的点即可。
那如何绘制几个平行的点呢,如下图所示:
当以点 O1 为圆心,绘制点 O2 时,我们同时绘制出另外的几个点即可,另外的几个点的坐标怎么确定呢?如图上的黄点和红点。我们可以看到图中,黄点、红点同点 O2 处于同一条线上,这条线就是 O1、O2 组成的向量的垂直向量 V12。知道了垂直向量 V12,我们就可以通过向量的运算,计算出让黄点和红点的位置了。
我们以黄点为例,从图中我们可以看出 黄点 Y2 的向量 = 点 O2 的向量 + 向量 O2Y2
,而 O2
的向量是已经算出来了的(点 O2
的坐标),所以问题的关键是计算出 O2Y2
的向量,我们可以按照如下的步骤进行计算
计算出点 O1
、O2
组成的向量
const vectorO12 = createVector(O2.x - O1.x, O2.y - O1.y)
计算出点 O1
、O2
向量的垂直向量 V12
const vectorV12 = createVector(vectorO12.y, -vectorO12.x)
将垂直向量 V12 归一化,归一化后方便计算黄点的位置
vectorV12.normalize()
计算点 O2
和黄点 Y2
之间的向量
点 O2
和黄点 Y2
之间的距离即为线条的宽度,我们假设线条的宽度为 5
,那么我们将 V12
归一化后的向量设置为 5
的长度即为 O2Y2
向量。
vectorV12.setMag(5)
计算黄点的坐标向量
从图中我们可以看出 黄点的向量 = 点 O2 的向量 + V12 向量
即 const yellowVector = p5.Vector.add(O2 + vectorV12)
由此我们计算出黄点 Y2 的坐标向量,同理可计算出红点的坐标向量。
本次教程,我们基于基础的向量、垂直向量、圆周运动以及随机游走模型,实现了一个多彩的 ribbon line 效果。
接下来,你可以做更多的尝试,比如下面一些可能的点:
theta
每次的变化量增大