本期知识点密度:★★★★✩
上一期留下的问题是:
一:如何实现Processing启动界面的渐变线条
二:如何复刻完整的启动界面
渐变线条
首先说说如何绘制渐变色的线条
如果去查Processing的官网,官网会给出这样的建议:https://processing.org/examples/lineargradient.html
这种方法从本质上阐述了渐变是如何实现的(把所有的颜色一点一点画出来)
但是,这种方法的计算效率很低
而且,用起来很麻烦
你可能会想:诺大一个Processing,居然连渐变函数都没有内置吗?!
答案是:确实没有
但是,我们可以用一种巧妙的方式
借助内置功能实现渐变绘制
即:使用OpenGL特性
在原生OpenGL中,渐变线条可以这样绘制:1
2
3
4
5
6glBegin( GL_LINE_STRIP );
glColor3f( 设置为起点颜色 );
glVertex3f( 起点 );
glColor3f( 设置为终点颜色 );
glVertex3f( 终点 );
glEnd();
Processing虽然没有直接公开OpenGL的函数
但是也做了相应的封装
写法类似上文:1
2
3
4
5
6beginShape(LINES); //括号里声明的是我们需要绘制的元素类型,这里写LINES即可,除此之外还有 POINTS TRIANGLES TRIANGLE_STRIP TRIANGLE_FAN QUADS QUAD_STRIP 六种绘制方法
stroke( 设置为起点颜色 );
vertex( 起点 );
stroke( 设置为终点颜色 );
vertex( 终点 );
endShape();
如果按照这个写法把代码写出来,就会出现这样的情况:1
2
3
4
5
6
7
8
9
10
11
12void setup() {
size(400, 400);
}
void draw() {
background(0);
beginShape(LINES);
stroke( 255, 0, 0 );
vertex( 100, 100 );
stroke( 0, 255, 0 );
vertex( 300, 300 );
endShape();
}
线条并没有渐变
原因是绘制模式是Java2D,而非OpenGL
这一点在 【白问】第一期 中有提及
只有当初始化的时候启用OpenGL相关模式
才能使用OpenGL的相关的特性
稍加修改:1
size(400, 400, P2D);
即可生效:
Plexus效果
实现了线条渐变
再来谈谈如何实现网状结构
更确切一点应该叫做 Plexus 效果
熟悉AE的朋友肯定会想起著名的 Plexus插件
其实Plexus的英文释义就是: n. (血管、淋巴管、神经等的)[解剖] 丛
是不是非常形象~
Plexus 的实现原理也非常简单
1:循环遍历所有点,计算每两个点间的距离
2:如果两点间距小于一定程度
3:就在两点间画一条线
用程序表达就是:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19ArrayList<PVector> points = new ArrayList<PVector>();
void setup() {
size(400, 400, P2D);
for (int i=0; i<100; i++) {
points.add(new PVector(random(width), random(height)));
} //随机生成100个点
}
void draw() {
background(255);
for (int i=0; i<points.size(); i++) {
for (int j=0; j<points.size(); j++) {
float dist = PVector.dist(points.get(i), points.get(j)); //计算两点间的距离
if (i!=j && dist < 60) { //如果距离小于60
line(points.get(i).x, points.get(i).y, points.get(j).x, points.get(j).y);
} //在两点之间画线
}
}
}
稍做美化:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20ArrayList<PVector> points = new ArrayList<PVector>();
void setup() {
size(400, 400, P2D);
for (int i=0; i<100; i++) {
points.add(new PVector(random(width), random(height)));
} //随机生成100个点
}
void draw() {
background(0);
for (int i=0; i<points.size(); i++) {
for (int j=0; j<points.size(); j++) {
float dist = PVector.dist(points.get(i), points.get(j)); //计算两点间的距离
if (i!=j && dist < 60) { //如果距离小于60
stroke(255, map(dist, 0, 60, 60, 0)); //将距离映射为线条透明度
line(points.get(i).x, points.get(i).y, points.get(j).x, points.get(j).y);
} //在两点之间画线
}
}
}
用上面提到的渐变色绘制方法替代line函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27ArrayList<PVector> points = new ArrayList<PVector>();
void setup() {
size(400, 400, P2D);
colorMode(HSB); //颜色模式调整为HSB模式
strokeWeight(2); //线条粗细调整为2
for (int i=0; i<100; i++) {
points.add(new PVector(random(width), random(height)));
}
}
void draw() {
background(0);
for (int i=0; i<points.size(); i++) {
for (int j=0; j<points.size(); j++) {
float dist = PVector.dist(points.get(i), points.get(j));
if (i!=j && dist < 80) {
beginShape(LINES);
stroke(random(255), random(255), random(255), map(dist, 0, 80, 100, 0)); //随机生成颜色
vertex(points.get(i).x, points.get(i).y);
stroke(random(255), random(255), random(255), map(dist, 0, 80, 100, 0)); //随机生成颜色
vertex(points.get(j).x, points.get(j).y);
endShape();
} //用渐变色绘制方法替代line函数
}
}
noLoop(); //暂停画面,避免每帧刷新随机颜色
}
Json数据读写
由于粒子的位置是完全随机的
所以并不是每次随机的结果都会很好看
有时候会分布的比较均匀也有时候会聚在一坨
所以为了和原图保持一致
就需要精确记录每个点的位置
首先在原图的上标记每个点的位置
然后使用Json文件来存储这些位置
标记工具代码如下:
相关下载(右键 - 另存为)
工具代码
PDE原图
前景粒子Json文件
背景粒子Json文件
将所有点绘制完毕后,按任意键即另存为json文件
(由于工具涉及篇幅过长,暂时不展开讲解,如需单独讲解可以联系我)
此时,只需把 points 从随机修改为读Json文件数据即可1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29ArrayList<PVector> points = new ArrayList<PVector>();
void setup() {
size(375, 450, P2D); //画面分辨率和原图保持一致
colorMode(HSB);
strokeWeight(1.5);
JSONArray poses = loadJSONArray("poses.json");
for (int i=0; i<poses.size(); i++) {
points.add(new PVector(poses.getJSONArray(i).getFloat(0), poses.getJSONArray(i).getFloat(1)));
} //把随机生成位置 修改为从文件中获取位置
}
void draw() {
background(0);
for (int i=0; i<points.size(); i++) {
for (int j=0; j<points.size(); j++) {
float dist = PVector.dist(points.get(i), points.get(j));
if (i!=j && dist < 100) { //最大距离调整为100
beginShape(LINES);
stroke(random(255), 255, 255, map(dist, 0, 100, 200, 20));
vertex(points.get(i).x, points.get(i).y);
stroke(random(255), 255, 255, map(dist, 0, 100, 200, 20));
vertex(points.get(j).x, points.get(j).y);
endShape();
}
}
}
noLoop();
}
美化与文字渲染
调整背景色,调整粒子颜色,添加背景粒子,添加半透明渐变图层1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65ArrayList<PVector> points = new ArrayList<PVector>();
ArrayList<PVector> bgpoints = new ArrayList<PVector>();
void setup() {
size(375, 450, P2D);
smooth(10);
colorMode(HSB);
JSONArray poses = loadJSONArray("poses.json");
JSONArray bgposes = loadJSONArray("bgposes.json");
for (int i=0; i<poses.size(); i++) {
points.add(new PVector(poses.getJSONArray(i).getFloat(0), poses.getJSONArray(i).getFloat(1)));
}
for (int i=0; i<bgposes.size(); i++) {
bgpoints.add(new PVector(bgposes.getJSONArray(i).getFloat(0), bgposes.getJSONArray(i).getFloat(1)));
}
}
void draw() {
background(#101F2D); // 调整背景色
// 添加背景粒子
for (int i=0; i<bgpoints.size(); i++) {
for (int j=0; j<bgpoints.size(); j++) {
float dist = PVector.dist(bgpoints.get(i), bgpoints.get(j));
if (i!=j && dist < 100) {
beginShape(LINES);
stroke(random(145, 150), random(170, 190), random(140, 170), random(20, 130)); //调整粒子颜色
strokeWeight(0.2);
vertex(bgpoints.get(i).x, bgpoints.get(i).y);
stroke(random(145, 150), random(170, 190), random(140, 170), random(20, 130)); //调整粒子颜色
strokeWeight(1);
vertex(bgpoints.get(j).x, bgpoints.get(j).y);
endShape();
}
}
}
for (int i=0; i<points.size(); i++) {
for (int j=0; j<points.size(); j++) {
float dist = PVector.dist(points.get(i), points.get(j));
if (i!=j && dist < 100) {
beginShape(LINES);
stroke(random(40, 160), random(20, 110), random(180, 240), random(40, 150));
strokeWeight(0.5);
vertex(points.get(i).x, points.get(i).y);
stroke(random(40, 160), random(20, 110), random(180, 240), random(40, 150));
strokeWeight(1.5);
vertex(points.get(j).x, points.get(j).y);
endShape();
}
}
}
// 添加半透明渐变图层
beginShape();
noStroke();
fill(0, 0);
vertex(0, 0);
vertex(375, 0);
fill(140, 150, 5, 210);
vertex(375, 450);
vertex(0, 450);
endShape(CLOSE);
}
最后,创建字体,绘制文字
Processing使用的封面字体名为:TheSerif HP5 Plain
字体文件可由此 下载
下载后安装到系统字库中即可调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15PFont font;
font = createFont("TheSerif HP5 Plain", 50);
fill(255);
textFont(font, 60);
text("Processing 3", 25, 216);
fill(#B0C4D0, 180);
textFont(font, 13);
text("An Open project initiated by Ben Fry and Casey Reas.", 26, 260);
text("Supported by programmers like you and the nonprofit", 26, 278);
text("Processing Foundation, 501(c)(3).", 26, 296);
textFont(font, 10);
text("© 2012-2018 The Processing Foundation", 26, 398);
text("© 2004-2012 Ben Fry and Casey Reas", 26, 411);
text("© 2001-2004 Massachusetts Institude of Technology", 26, 424);