开一朵花,基于贝塞尔曲线和 p5.js 的实现

本次教程我们将介绍如何基于 p5.js 来绘制一朵花儿,教程将会涉及到贝塞尔曲线、颜色的操作、三角函数以及坐标系的旋转、缩放等

更多关于 p5.js 和生成艺术的教程,请查阅 beauty-of-pixel.tech

画一个花瓣

花是由很多花瓣组成,我们先看看怎么画出一朵花瓣,花瓣的形状是不规则的,我们可以使用贝塞尔曲线来绘制。

简单地解释一下上面几行代码:

  1. setup 函数中
    1. createCanvas 创建了一个和窗口一样大小的画布。
    2. noLoop(true) 禁用了渲染循环,让 draw 只会调用一次。
    3. fill 填充一个颜色
  2. draw 函数中
    1. translate 将画布的原点转移到了画布的中心点。
    2. 定义了贝塞尔曲线的起点、终点、控制点,并调用 bezier 进行绘制。

下面就来具体看看 bezier 曲线是如何通过几个点来绘制出上面的花瓣形状的。

贝塞尔曲线

贝塞尔曲线是一种数学曲线,由法国数学家皮埃尔·贝塞尔在19世纪提出。它是通过控制点来定义的,这些控制点决定了曲线的形状和方向。贝塞尔曲线在计算机图形学和艺术设计中被广泛使用,因为它们可以产生平滑的曲线和复杂的形状。本节内容将主要介绍二次贝塞尔曲线三次贝塞尔曲线

二次贝塞尔曲线

二次贝塞尔曲线由三个点来定义:起始点、控制点和终止点,这些点通过插值算法确定曲线的形状。

quadraticVertex 绘制

在 p5.js 中,我们可以通过 quadraticVertex 方法来定义顶点的坐标。如下面的示例:

在使用 quadraticVertex 时,需要先调用 beginShape 和 vertex 方法。beiginShape 表示开启图形的绘制,而 vertex 用来定义二次贝塞尔曲线的起始点。

插值法绘制

还可以通过对曲线点的差值方法来绘制二次贝塞尔曲线,二次贝塞尔曲线的数学表示如下:

B(t)=(1t)2P0+2(1t)tP1+t2P2B(t) = (1 – t)² * P0 + 2 * (1 – t) * t * P1 + t² * P2

其中 P0 是起始点,P1 是控制点,P2 是终止点。参数 t 的取值范围是 [0, 1] 闭合区间。

基于此公式,代入 x 坐标和 y 坐标,即可以通过以下坐标点公式计算出二次贝塞尔曲线每个点的坐标值:

x=(1t)2P0.x+2(1t)tP1.x+t2P2.xx = (1 – t)² * P0.x + 2 * (1 – t) * t * P1.x + t² * P2.x

y=(1t)2P0.y+2(1t)tP1.y+t2P2.yy = (1 – t)² * P0.y + 2 * (1 – t) * t * P1.y + t² * P2.y

通过遍历从 0 到 1 之间的 t 的值,就可以得到曲线上的点。

将其转换为代码如下:

三次贝塞尔曲线

bezier 方法绘制

和二次贝塞尔曲线相似,三次贝塞尔曲线包含了起始点、控制点 1、控制点 2 和终止点。在 p5.js 可以使用 bezier 方法来实现,如下代码:

上面的画布上绘制出了本节教程里的花瓣,同时上面显示有两个蓝色的控制点和一个红色的点(实际上有两个,因为起始点和终止点坐标一样,绘制在了同一个地方,所以看起来只有一个)。

使用 bezier 方法可以很方便地绘制出三次贝塞尔曲线,只需要传入 4 个坐标位置即可,除此之外也可以通过 bezierVertex 方法来绘制,类似于二次曲线的调用方式。

beginShape();
vertex(startPoint.x, startPoint.y);
bezierVertex(ctrlPoint1.x, ctrlPoint1.y, ctrlPoint2.x, ctrlPoint2.y, endPoint.x, endPoint.y);
endShape();

插值方法绘制

和二次贝塞尔曲线相似,也可以通过插值方法来绘制三次贝塞尔曲线。

三次贝塞尔曲线的数学公式如下:

B(t)=(1t)3P0+3(1t)2tP1+3(1t)t2P2+t3P3B(t) = (1 – t)³ * P0 + 3 * (1 – t)² * t * P1 + 3 * (1 – t) * t² * P2 + t³ * P3

其中 P0 是起始点,P1 是控制点,P2 是控制点,P3 是终止点。参数 t 的取值范围是 [0, 1] 闭合区间。

基于此公式,代入 x 坐标和 y 坐标,即可以通过以下坐标点公式计算出三次贝塞尔曲线每个点的坐标值:

x=(1t)3P0.x+3(1t)2tP1.x+3(1t)t2P2.x+t3P3.xx = (1 – t)³ * P0.x + 3 * (1 – t)² * t * P1.x + 3 * (1 – t) * t² * P2.x + t³ * P3.x

y=(1t)3P0.y+3(1t)2tP1.y+3(1t)t2P2.y+t3P3.yy = (1 – t)³ * P0.y + 3 * (1 – t)² * t * P1.y + 3 * (1 – t) * t² * P2.y + t³ * P3.y

通过遍历从 0 到 1 之间的 t 的值,可以获得曲线上的一系列点,从而绘制出整条曲线。

特别的,当把三次贝塞尔曲线的两个控制点设置为同一个点的时候,曲线即变为二次贝塞尔曲线,并且两者形状保持一致。

下面我们来实现一个贝塞尔曲线编辑器,帮助我们生成更多的花瓣形状。

绘制一层的花瓣

有了单个的花瓣,接下来就可以绘制一层花瓣了,一层花瓣即是将花瓣平均分布在圆周上。

通俗地说,就是将圆分成几等份,每等份上画一个花瓣即可,比如一层有 5 个花瓣,我们即是将圆分成 5 份,每份里绘制一个花瓣。

上述代码中,起到关键作用的是 rotate( 1 / petalCount * Math.PI * 2)

  1. rotate 是 p5.js 提供的旋转画布的方法,画布是以原点为中心点进行旋转的。
  2. draw 方法里的 translate(width / 2, height / 2) 即是将画布的默认的左上角原点转移到画布的中心点。
  3. 完整圆周的角度是 Math.PI * 2,将其除以花瓣的个数,即可以得到每片花瓣所占的角度。

每绘制一个花瓣,就将圆周旋转每一份花瓣所占的度数。圆周的总度数是 Math.PI * 2,那么每一份的度数即是 1 / petalCount * Math.PI * 2

下面以动画的形式来看看一层花瓣的绘制过程

绘制多层花瓣

有了一层花瓣的绘制方法 drawLayerOfPetal 后,绘制多层花瓣就简单了,重复地调用绘制一层花瓣的方法即可。

通过多次调用 drawLayerOfPetal 函数,实现了多层花瓣的绘制,随着花瓣层数的增多,花瓣的长度也随之变小。

	drawLayerOfPetal(240)
	drawLayerOfPetal(192)
	drawLayerOfPetal(144)
	drawLayerOfPetal(96)
	drawLayerOfPetal(48)

这里可以将其简化为一个 for 循环如下:

	const layerCount = 5
	for (let i = 0; i < layerCount; i++) {

		drawLayerOfPetal(240 * (1 -  i / layerCount))
	}

每绘制一层花瓣,就让花瓣半径减少 1 / layerCount,这里即减少 20%,第一层的半径是 240,也就是每绘制一层花瓣,花瓣半径减少 48。

让每层的花瓣有层次感

现在每一层花瓣都是重叠在一起的,为了让每层的花瓣有层次感,可以在绘制每一次花瓣之前,将画板进行一定角度的旋转,这样就可以让花瓣绘制在不同的位置上。

修改绘制每层花瓣的代码,在前面加上 rotate 对花瓣进行旋转。

	const rotateRadian = 0.7
	for (let i = 0; i < layerCount; i++) {

		rotate(rotateRadian)
		drawLayerOfPetal(radius * (1 -  i / layerCount))
	}

通过在绘制每层花瓣时将花瓣旋转 0.7,可以看到每一层的花瓣都是错开分布的了。

现在花瓣是处于静止状态,因为所有的数值都是写死的,可以引入部分变量进来,比如让每层花瓣旋转的角度发生改变,这样我们的花瓣就会动起来。

起到关键作用的是这三行代码

let rotateRadian = 0
let rotateRadianDelta = 0.001

rotateRadian += rotateRadianDelta

为了在每次运行时获取最新的旋转角度,我们将 rotateRadian 移到了 draw 函数的外部,并新添加了一个 rotateRadianDelta 变量,用于表示每次渲染后旋转角度的变化量。

在渲染结束后,rotateRadian 将被更新。在下次渲染时,画板将以上次旋转角度为基础进行再次旋转。

需要注意的是,在 p5.js 中调用 draw 函数进行绘制时,会先保存绘制环境(即 push() 操作),绘制完成后恢复绘制环境(即 pop() 操作)。

因此,在进行与画板相关的操作(如 translate、rotate 和 scale)时,如果需要保持视觉上的一致性,就需要将相关数据保存下来,如本例子中的 rotateRadian 值。

现在你可以试试修改一下上面的代码,比如将 layerCount = 5 改成更大的数值,点击运行看看会出现什么效果。

给花瓣加点颜色

到现在为止,花瓣已经完成了,但是看起来很单调,是时候来一点多姿多彩的花瓣了。

我们准备了两个颜色,一个是花瓣最底层的颜色,一个是花瓣最上层的颜色,然后在绘制每层花瓣的时候,获取两种颜色之间对应的插值来作为当前花瓣的颜色。

在 p5.js 里面获取颜色插值可以直接使用 lerpColor(c1, c2, amt) 方法,下面就来看看怎么使用:

在绘制每层花瓣的 for 循环中,通过 lerpColor 获取当前层花瓣的颜色值,然后在 drawPetal 函数中,就可以使用这个颜色值来绘制当前层花瓣。

可以看到现在我们的花瓣还有一个黑色的边框,这个可以通过 noStroke 函数把这个描边去掉。

更进一步

本教程我们基于贝塞尔曲线实现了一个花瓣的效果,涉及到了花瓣的旋转操作等。

你还可以基于本教程的代码做一些其他的修改,如下是一些可能的点:

  1. 最后我们的实现里,花瓣有个黑色的描边,看看怎么去掉它。
  2. 在教程中,我们实现了一个贝塞尔曲线来绘制花瓣的形状,看看怎么结合起来,实现不一样的花瓣形状。
  3. 花瓣的美丽在于颜色,看看怎么添加更多的颜色,来呈现更好看的视觉效果。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYUYnego' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片