球体一直被称为最完美的几何体,它是只有一个面并且连续曲面的立体图形,用肉眼看来球体在各个位置观看都是完全一致的。
用Processing画一个球体是再简单不过了,只需要下面几行短短的代码,就可以绘制一个球体。
void setup(){
size(500, 500, P3D);
}
void draw(){
background(0);
translate(width / 2, height / 2);
sphere(200);
}
以上代码的效果如下。
但今天我们要绘制的球体是与众不同的,因为它是由文字构成的。请大家看下面的效果。这个作品是作者用JavaScript写的,我在这个基础上做了相应的简化,用Processing近似模拟了这个效果,接下来就给大家讲解如何用代码实现。
对于球体上的任何的一个点P来说, 我们可以用三个变量来描述:第一个是点P到球心的长度,也就是该球体的半径;第二个是点P和球心的连线与x、y轴所构成的平面所成的夹角的角度;第三个就是点P在x、y轴所构成的平面的投影点与球心的连线和x轴所成夹脚的角度。
上面所说的三个变量分别对应下面的r、B、A。
那么接下来我们要做的就是求点P在直角坐标系下的坐标(x,y,z),根据上图我们不难得出以下的等式。
x = r * cos(B) * cos(A)
y = r * cos(B) * sin(A)
z = r * sin(B)
那么在理解了如何表示球体上的一个点,并且如何求得该点在直角坐标系下的坐标后,我们就可以开始看代码了。
代码的大体结构非常的简单。
首先,在setup函数中将作品设置成3D模式,同时初始化一个球体,并且将这个球体的球心设置为屏幕的中心,然后将该球体的半径设置为500。
这之后在draw函数中对这个球进行更新和绘制。update函数主要用于对球体进行旋转,display函数主要用于将球体绘制在屏幕上。
该作品只自定义了一个Ball类,那么接下来我们就看看这个类具体是怎样的。
Ball b;
void setup() {
fullScreen(P3D);
b = new Ball(width / 2, height / 2, 500);
}
void draw() {
background(255);
b.update();
b.display();
}
class Ball{}
第一步我们来看看Ball类的构造函数。
首先是设置了球体的位置,这里需要注意的是我们将球体到屏幕的距离设置成了半径的长度。然后是设置了球体的半径。
这之后是设置了绘制球体的起始角度和球体转动的角速度。
Ball(float x, float y, float _r) {
location = new PVector(x, y, -_r);
r = _r;
startAngle = 0;
angleVelocity = -0.1;
}
看完了Ball构造函数需要的参数,下面就来看看Ball需要的成员变量。
// 位置
PVector location;
// 半径、起始角度、角速度
float r, startAngle, angleVelocity;
第二步我们来看看update函数。
前面提到了,update的函数的作用是让球体进行旋转,所以在这个函数中我们要不断的更新绘制球体的初始角度。具体实现如下,在看了接下来的display函数后,大家应该能更加理解update函数的作用。
void update() {
startAngle += angleVelocity;
}
最后我们来看这个作品中最关键的地方:display 函数,也就是如何绘制这个球体。
大体思路非常的简单:我们首先在球体上取一些点,然后在这些点绘制相应的文字即可。
在该函数中首先我们把坐标原点设置为球心,然后将字体设置为黑色,并设置字体的大小和对其方式。
接下来我们开始在球体上取点。取样的方法很将简单,我们每隔一定的间隔从0到360度枚举角度A,然后每隔一定的间隔从-90到90度枚举角度B,至于枚举的间隔由变量step决定,step越大那么点越稀疏,step越小那么点越密集。
offset的作用是让同一个角度B的点有一个向上或者向下的偏移,让它们不再同一条水平线上(下图中的红线),形成错落有致的效果。
因为注意到该作品中球体上只有朝向屏幕这一半有文字,所以只有当这个点的z大于零的时候才绘制文字。
讲解到这里,大家看懂下面的代码应该就没有问题了。
void display() {
translate(location.x, location.y, location.z);
fill(0);
textSize(40);
textAlign(CENTER);
float step = 10; // 控制字的疏密程度
float offset = step / 4;//
for (int i = 0; i < 360; i+=step) {
offset *= -1;
for (int j = -90; j < 90; j+=step) {
// startAngle控制绘制球体的起始角度
// offset让文字不再一条直线上
float theta1 = radians(i + startAngle), theta2 = radians(j + offset);
float x = r * cos(theta2) * cos(theta1);
float y = r * sin(theta2);
float z = r * cos(theta2) * sin(theta1);
// 绘制一半的球体
if (z > 0)
text("lmao", x, y, z);
}
}
}
该作品同样也有很多可以拓展的地方,比如改变在球体上取点的方式,在每一个点绘制不同的图案等。我就在这个作品的基础上,绘制了一个印有不同表情包的彩虹球,效果如下。
同时球体彩条的颜色还可以有更多的拓展, 比如参考大名鼎鼎的苹果的logo。
获得效果如下。
感兴趣的同学可以去我的openprocessing看在线效果,同时两个作品的完整代码我已经放在我的github上了,欢迎大家查看和提出问题。
最后祝大家暑假快乐!