![58dd83f62470a9d752e8f47e303f9dc1.png](https://img-blog.csdnimg.cn/img_convert/58dd83f62470a9d752e8f47e303f9dc1.png)
最近在网上看到一张图,据说是通过Perlin噪声实现的,今天我们就来实现这张图的效果吧。这次教程不难,大家可以先自行尝试尝试。
![682c8251243d69565aac518e9c18c9b8.png](https://img-blog.csdnimg.cn/img_convert/682c8251243d69565aac518e9c18c9b8.png)
我在之前的教程中已经介绍过Perlin噪声,感兴趣的同学可以移步 一天一点Processing|04平滑的Perlin噪声 了解一下基础内容。
首先,我们需要准备一张方形的黑色画板,如果你比较有主见, 可以尝试各种颜色和尺寸的画板。
同时,我们可以准备多条线,每条线会有多个控制点来控制它的形状。并通过向量的二维数组来存储线上控制点的坐标信息。第一个[]信息为第几根线,[]表示线上的第几个控制点。
int lineNum = 64; //设置线的总数
int controlPointNum = 32; //设置每条线控制点总数
PVector[][] line = new PVector[lineNum+1][controlPointNum+1]; //二维数组记录线上点的参数,第几根线,第几个点(加1代表多一个点)
//直线尾会多一个点,所有要加1
void setup() {
size(640, 640); //设置画板尺寸
smooth();
initializeLine(); //初始化线
frameRate(5); //设置帧率方便观察
}
void draw() {
background(0); //黑色背景
movePoint(); //移动线上的点
drawLines(); //绘制线条
}
我们先搭建好大框架,然后我们需要来思考如何绘制线条,如何移动线上的点的坐标。
首先,我们需要初始化各个控制点的坐标。这里用到循环的方式给点逐个赋值。
//初始化线条
void initializeLine() {
for (int i=0; i<lineNum+1; i++) {
for (int j=0; j<controlPointNum+1; j++) {
line[i][j] = new PVector(width/controlPointNum*j, height/lineNum*i);
}
}
}
然后根据坐标点绘制线条。仍是用循环。这次用过beginShape(); 和endShape(); 中间运用vertex();加入每条线的控制点来绘制线条。
//绘制线条
void drawLines() {
//线条参数
noFill();
stroke(255);
strokeWeight(0.7);
//绘制线条
for (int i=0; i<lineNum+1; i++) {
beginShape();
for (int j=0; j<controlPointNum+1; j++) {
vertex(line[i][j].x, line[i][j].y);
}
endShape();
}
}
![e9c2d2cef60e13ca2a982474774979bf.png](https://img-blog.csdnimg.cn/img_convert/e9c2d2cef60e13ca2a982474774979bf.png)
我们现在就得到初始的线条。下一步就要让控制点进行偏移,让线条扭起来!!
我们可以用过movePoint()更改控制点的y坐标。
void movePoint() {
for (int i=0; i<lineNum+1; i++) {
for (int j=0; j< controlPointNum+1; j++){
line[i][j].y = height/lineNum*i + random(-20, 20);
}
}
}
![f083877673053624579c623d0de7c4b5.png](https://img-blog.csdnimg.cn/img_convert/f083877673053624579c623d0de7c4b5.png)
OMG!!!超级尖锐的线条,一定是我们连接线的方式不对。drawLines()函数中,用curveVertex()来连接控制点,就可以得到曲线。
curveVertex(line[i][j].x, line[i][j].y);
![950e657e82acdb715c4c21c3e6fc3c58.png](https://img-blog.csdnimg.cn/img_convert/950e657e82acdb715c4c21c3e6fc3c58.png)
线条变得光滑了,但问题又产生了,为什么线条这么乱呢?有什么方法让它们变化得更顺滑呢??这里我们就要用到Perlin噪声中noise()函数来替代random()函数了。
line[i][j].y = height/lineNum*i + map(noise(0.1*i, 0.1*j), 0, 1, -80, 80);
我们会得到这张结果。乍一眼,比上面好多了,但是问题是,你为什么不动啊!!!
![21c3f8ce4409a2b8fc86c94c20e05f1d.png](https://img-blog.csdnimg.cn/img_convert/21c3f8ce4409a2b8fc86c94c20e05f1d.png)
这其实和noise()函数有关,noise()函数在同一点的取值是固定的。在我们应用时,每次都从noise(0, 0)开始,得到的数其实是一致的,所以我们需要在每一次偏移之前让noise()稍稍偏移一些。
float off = random(50);
line[i][j].y = height/lineNum*i + map(noise(0.1*i, 0.1*j+off), 0, 1, -120, 120);
现在图片就能够动起来了!!
但是仔细看会发现有一点点不完美的地方,有一些线会交叠在一起。所以我们可以在绘制线条之前看一看线是否会交叠,如果下一条和上一条交叠,给下一条线超出的几个控制点重新赋值。
//通过判断点的上下,判断线条是否会交叉,如果交叉就把点降到上一条的下面
if (i>0) {
if (line[i][j].y <= line[i-1][j].y) {
line[i][j].y = line[i-1][j].y + random(5);
}
}
![8aad0a0cf78e8397659a019fbf937694.png](https://img-blog.csdnimg.cn/img_convert/8aad0a0cf78e8397659a019fbf937694.png)
现在,得到的图形就不会存在线条交叉的现象了。
略有不完美就是两边出现了黑边。原因是因为通过curveVertex()绘制线条,起始点和终点只作为控制点,改进的方法就是在绘制线条时重新输入起点和终点。
//绘制线条
for (int i=0; i<lineNum+1; i++) {
beginShape();
curveVertex(line[i][0].x, line[i][0].y);
for (int j=0; j<controlPointNum+1; j++) {
curveVertex(line[i][j].x, line[i][j].y);
}
curveVertex(line[i][controlPointNum].x, line[i][controlPointNum].y);
endShape();
}
![3c27ffd35b522e544a377a03b76ac3e9.png](https://img-blog.csdnimg.cn/img_convert/3c27ffd35b522e544a377a03b76ac3e9.png)
![2820a8296c6605b07f68d2cba1496072.png](https://img-blog.csdnimg.cn/img_convert/2820a8296c6605b07f68d2cba1496072.png)
最后附上代码供大家学习~~~~
int lineNum = 64; //线的总数
int controlPointNum = 32; //每条线控制点总数
PVector[][] line = new PVector[lineNum+1][controlPointNum+1]; //二维数组记录线上点的参数,第几根线,第几个点(加1代表多一个点)
void setup() {
size(640, 640); //设置画板尺寸
smooth();
initializeLine(); //初始化线条
frameRate(5); //设置帧率方便观察
}
void draw() {
background(0); //黑色背景
movePoint(); //移动线上的点
drawLines(); //绘制整条线
}
//初始化线条
void initializeLine() {
for (int i=0; i<lineNum+1; i++) {
for (int j=0; j<controlPointNum+1; j++) {
line[i][j] = new PVector(width/controlPointNum*j, height/lineNum*i);
}
}
}
//绘制线条
void drawLines() {
//线条参数
noFill();
stroke(255);
strokeWeight(0.7);
//绘制线条
for (int i=0; i<lineNum+1; i++) {
beginShape();
curveVertex(line[i][0].x, line[i][0].y);
for (int j=0; j<controlPointNum+1; j++) {
curveVertex(line[i][j].x, line[i][j].y);
}
curveVertex(line[i][controlPointNum].x, line[i][controlPointNum].y);
endShape();
}
}
void movePoint() {
float off = random(50);
for (int i=0; i<lineNum+1; i++) {
for (int j=0; j< controlPointNum+1; j++) {
//line[i][j].y = height/lineNum*i + random(-20, 20); //随机偏移
line[i][j].y = height/lineNum*i + map(noise(0.1*i, 0.1*j+off), 0, 1, -120, 120);
//通过判断点的上下,判断线条是否会交叉,如果交叉就把点降到上一条的下面
if (i>0) {
if (line[i][j].y <= line[i-1][j].y) {
line[i][j].y = line[i-1][j].y + random(5);
}
}
}
}
}