p5-vue:使用柏林噪声生成有趣的动态图案
前面的博文中通过封装了一个 p5-vue
组件,使得在 Vue 项目中应用 p5.js 的强大绘图能力成为可能。本篇主要介绍一下 p5.js 中的柏林噪声,以及通过柏林噪声来绘制一些极富创造力的美丽图案。
柏林噪声
Perlin噪声 ( Perlin noise )指由Ken Perlin发明的自然噪声生成算法 。
一个噪声函数基本上是一个种子随机发生器。它需要一个整数作为参数,然后根据这个参数返回一个随机数。如果你两次都传同一个参数进来,它就会产生两次相同的数。这条规则非常重要,它确定了柏林噪声的确定性。
一般来说,通过柏林噪声可以产生连续性很好的数据,原因在于其生成的随机数会基于上一条随机数来计算,所以其随机噪声不是完全杂乱无章的。
p5-vue
基于上篇文章封装的组件,在外部组件中引用 p5-vue 。
<template>
<div id="app">
<p5 :sketch="sketch"></p5>
</div>
</template>
定义绘图函数的来源。
import p5 from '@/components/P5'
import p5_demo from './lib/p5/demo'
export default {
name: 'App',
components: {
p5
},
data() {
return {
width: 600,
height: 600
}
},
methods: {
sketch(p5) {
return p5_demo(p5, this);
}
}
}
然后定义绘图函数,绘图的尺寸由外部组件的 data 对象来定义,通过 context 上下文对象传入到绘图函数中。
module.exports = (p5, context) => {
let margin = 44;
let density = 1 / 650;
let nLines = 64;
let s;
let interLines;
p5.setup = () => {
p5.createCanvas(context.width, context.height);
p5.colorMode(p5.HSB, 1);
p5.noFill();
p5.stroke(0);
p5.strokeWeight(2.5);
p5.strokeCap(p5.SQUARE);
s = p5.width - 2 * margin;
interLines = s / (nLines - 1);
}
p5.draw = () => {
p5.background(0.15, 0.9, 1);
p5.translate(margin, margin);
for (let i = 0; i < nLines; i++) {
let y = interLines * i;
p5.beginShape();
for (let x = 0; x < s; x++) {
let offset = 0;
let noize = p5.noise(x * density, y * density, p5.frameCount / 700);
let factor = 0;
if (p5.floor(noize * 50) % 2 === 0) {
factor = interLines / 3;
}
offset = p5.cos(x / 2 - p5.frameCount / 10) * factor;
p5.curveVertex(x, y + offset);
}
p5.endShape();
}
}
}
最后得到的绘图效果:
其他效果
修改绘图函数,通过柏林噪声加上粒子系统,可以绘制更复杂的图案,例如下图。
绘图函数如下:
module.exports = (p5, context) => {
let particles_a = [];
let particles_b = [];
let particles_c = [];
let nums = 200;
let noiseScale = 800;
p5.setup = () => {
p5.createCanvas(context.width, context.height);
p5.background(21, 8, 50);
for (let i = 0; i < nums; i++) {
particles_a[i] = new Particle(p5.random(0, p5.width), p5.random(0, p5.height));
particles_b[i] = new Particle(p5.random(0, p5.width), p5.random(0, p5.height));
particles_c[i] = new Particle(p5.random(0, p5.width), p5.random(0, p5.height));
}
}
p5.draw = () => {
p5.noStroke();
p5.smooth();
for (let i = 0; i < nums; i++) {
let radius = p5.map(i, 0, nums, 1, 2);
let alpha = p5.map(i, 0, nums, 0, 250);
p5.fill(69, 33, 124, alpha);
particles_a[i].move();
particles_a[i].display(radius);
particles_a[i].checkEdge();
p5.fill(7, 153, 242, alpha);
particles_b[i].move();
particles_b[i].display(radius);
particles_b[i].checkEdge();
p5.fill(255, 255, 255, alpha);
particles_c[i].move();
particles_c[i].display(radius);
particles_c[i].checkEdge();
}
}
function Particle(x, y) {
this.dir = p5.createVector(0, 0);
this.vel = p5.createVector(0, 0);
this.pos = p5.createVector(x, y);
this.speed = 0.4;
this.move = function () {
let angle = p5.noise(this.pos.x / noiseScale, this.pos.y / noiseScale) * p5.TWO_PI * noiseScale;
this.dir.x = p5.cos(angle);
this.dir.y = p5.sin(angle);
this.vel = this.dir.copy();
this.vel.mult(this.speed);
this.pos.add(this.vel);
}
this.checkEdge = function () {
if (this.pos.x > p5.width || this.pos.x < 0 || this.pos.y > p5.height || this.pos.y < 0) {
this.pos.x = p5.random(50, p5.width);
this.pos.y = p5.random(50, p5.height);
}
}
this.display = function (r) {
p5.ellipse(this.pos.x, this.pos.y, r, r);
}
}
}