文章目录

  • 引言
  • 代码
    • 主要结构
    • 交互
    • 绘制圆
      • 圆心
      • 半径
      • 颜色
  • 小结

引言

生活中有很多积少成多的现象,很多微不足道的东西聚集起来会出现意想不到的效果。举个最常见的例子,比如一张普通的打印纸非常的薄,但是它们构成的书却可以很厚。

而我们这次要介绍的作品也具有这样的特点,请大家先看以下的效果。

看上去可以是非常的炫酷,但实现起来却非常的简单。其实就是绘制很多的圆,然后这些圆组合起来就出现了以上的效果。只不过我们需要借助三角函数设置每一个圆的圆心、半径、颜色,使得它们的移动、样子呈现一定的规律性和周期性。

这个作品 Instanced WebGL Circles 是作者基于 WebGl 写的,我用 processing 模拟了近似效果,并取名为“圆圈的华尔兹”——看上去就是一群圆圈们在舞蹈。

心急的小伙伴可以先去我的 open processing 看看在线效果。接下来我们就一起看看如何“教”这些圆圈跳“三角函数”的华尔兹。
  

代码

这个作品的代码量很小,正常写的话也就是40~50行左右。但是为了增加代码的可读性,之后要介绍的代码大概在80行左右。
  

主要结构

前面提到这个作品就是绘制一系列的圆圈,不过这里绘制每一个圆圈的方式不是调用 ellipse 函数,而是用一系列点去近似一个圆。也就是说会在圆周上取指定个数的点,然后把这些点用线段连接起来。

这样做的好处就是我们可以控制绘制图形的形状。假设取了 n 个点 :当 n = 3 时,这是个三角形;当 n = 4 时,这是个正方形…… n 越大,那么这个图形就越接近圆。如此就可以增强作品的交互性和趣味性,这个接下来会看见。

最开始我们设置了4个全局常量,用来限制绘制的圆圈的数量和构成圆圈点的数量。之后我们设置了2个全局变量,用来保存当前时刻需要绘制的圆圈的数量(在接下来的文章中用 circleCnt 来代替)和构成这圆圈点的数量(在接下来的文章中用 vertexCnt 来代替)。

draw 函数中,主要完成了两件事情。第一,调用 updateCntByMouse 函数根据鼠标位置设置当前 circleCntvertexCnt。第二,调用 displayCircle 函数对每一个圆进行绘制。

下面是上面所介绍部分的代码。

// 设置一些常量
final int MAX_CIRCLE_CNT = 2500, MIN_CIRCLE_CNT = 100, MAX_VERTEX_CNT = 30, MIN_VERTEX_CNT = 3;// 设置两个全局变量:画面中圆的个数、以及构成圆的顶点的数
int circleCnt, vertexCnt;
void setup() {size(700, 700);
}void draw() {background(0);translate(width / 2, height / 2);// 根据鼠标更新圆的个数、构成圆的顶点数updateCntByMouse();// 渲染每一个圆for (int i = 0; i < circleCnt; i++) {displayCircle(i);}
}

  

交互

首先来看看 updateCntByMouse 函数。这个函数非常的简单,就是根据鼠标到画面中心的距离来设置 circleCntvertexCnt

这样当鼠标离画面的四个角越近,画面中的圆越少,同时更接近多边形;反之,当鼠标离画面的中心越近,画面中的圆越多,同时更接近圆。大家可以对比一下下面的两张图。

void updateCntByMouse() {float xoffset = abs(mouseX - width / 2), yoffset = abs(mouseY - height / 2);circleCnt = int(map(xoffset, 0, width / 2, MAX_CIRCLE_CNT, MIN_CIRCLE_CNT));vertexCnt = int(map(yoffset, 0, height / 2, MAX_VERTEX_CNT, MIN_VERTEX_CNT));
}

  

绘制圆

接下来我们来看看 dispalyCircle 函数。对于每一个圆圈,我们将它们均匀分布在以画面中心为圆心的圆周上。然后分别通过 getCenterByThetagetSizeByThetagetColorByTheta 获得当前圆的圆心、半径和颜色。最后根据这三个函数返回的结果绘制圆圈。

这里需要注意一下在调用 endShape 函数的时候我们传入了 CLOSE,这样做的目的是让绘制的最后一个点和第一点连接起来,形成一个封闭的图形。

void displayCircle(int ci) {// 分别控制圆圈的移动速度、分布、大小float time = float(frameCount) / 20,thetaC = map(ci, 0, circleCnt, 0, TAU),scale = 300;// 获得每一个圆的圆心、半径、颜色PVector circleCenter = getCenterByTheta(thetaC, time, scale);float circleSize = getSizeByTheta(thetaC, time, scale);color c = getColorByTheta(thetaC, time);// 绘制每一个圆的所有顶点stroke(c);noFill();beginShape();for (int vi = 0; vi < vertexCnt; vi++) {float thetaV = map(vi, 0, vertexCnt, 0, TAU);float x = circleCenter.x + cos(thetaV) * circleSize;float y = circleCenter.y + sin(thetaV) * circleSize;vertex(x, y);}endShape(CLOSE);
}

  

圆心

现在我们来看如果确定圆圈圆心位置。首先根据传入的 theta 确定该圆相对于画面中心的方向,然后计算出到画面中心的距离。因为我们知道余弦函数的取值范围是-1~1,所以这个距离的取值范围是0.4~0.8,它会随着时间按照余弦函数的方式周期性变化。最后将这个值通过 scale 放缩成最后需要的值即可。

PVector getCenterByTheta(float theta, float time, float scale) {PVector direction = new PVector(cos(theta), sin(theta));float distance = 0.6 + 0.2 * cos(theta * 6.0 + cos(theta * 8.0 + time));PVector circleCenter = PVector.mult(direction, offset * distance);return circleCenter;
}

  

半径

现在我们来看如何确定圆圈的半径。圆圈的半径的取值范围是0.08~0.32,它同样会随着时间按照余弦函数的方式周期性变化。最后将这个值通过 scale放缩成最后需要的值即可。

float getSizeByTheta(float theta, float time, float scale) {float offset = 0.2 + 0.12 * cos(theta * 9.0 - time * 2.0);float circleSize = offset * scale;return circleSize;
}

  

颜色

现在我们来看如何确定圆圈的颜色。

下面代码中颜色的 rgb 通道的取值范围都是0.2~1。每一个圆圈的颜色同样会随着时间按照余弦函数的方式周期性的变化,最后每个通道的值再乘一个255就是返回的结果。

至于透明度我们可以根据画面中圆圈的个数来设定,圆圈越多,透明度越低。

color getColorByTheta(float theta, float time) {float th = 8.0 * theta + time * 2.0;float r = 0.6 + 0.4 * cos(th), g = 0.6 + 0.4 * cos(th - PI / 3), b = 0.6 + 0.4 * cos(th - PI * 2.0 / 3.0), alpha = map(circleCnt, MIN_CIRCLE_CNT, MAX_CIRCLE_CNT, 150, 30);return color(r * 255, g * 255, b * 255, alpha);
}

  

小结

这个作品虽然代码量特别少,但是这种”积少成多“的思想特别值得我们学习。

另一方面,这种用三角函数设置圆圈的圆心位置、半径长度和颜色的方法特别的巧妙,大家可以自己思考一下每一个三角函数的参数(振奋、周期等)为什么要这样设置,然后将这种方法运用到自己以后自己的作品中。

Processing 案例 | 圆圈的华尔兹相关推荐

  1. Processing 案例 | 扑面而来的满天繁星

    文章目录 引言 效果展现 原理分析 代码实现 确定代码执行流程 星星绘制在屏幕上 让星星动起来 点击鼠标,星星的速度发生改变 视角改变 完整代码 结语    引言 清明时节雨纷纷,路上行人欲断魂&qu ...

  2. Processing 案例 | 去“富士山”看樱花从树上纷纷而落

    文章目录 引言 代码 大体结构 generateNewTree class Branch class Leaf 交互 鼠标 键盘 拓展 小结 参考资料 引言 大家都知道,樱花是日本的国花,他们对于樱花 ...

  3. Processing 案例 | 郭锐文先生的 worms

    文章目录 引言 代码 大体框架 Class StageManager Class Interference Class Worm Class Vertex 小结 参考资料 引言 最近在各大美术学院做讲 ...

  4. Processing 案例 | 诡异的八爪鱼

    文章目录 引言 代码 主要结构 class Squid class Tentacle class Limb 拓展 小结 引言 冬天到了,春天还会远吗?七月到了,八月还会远吗? 我们迎来了七月的烈日炎炎 ...

  5. Processing 案例 | 用粒子系统谱写冰与火之歌

    文章目录 引言 FireBrush 分析作品 大概流程 父类:Particle 子类:Fire 子类:Smoke IceAndFire 更多拓展 Frozen Brush 愤怒的小鸟 小结 引言 前不 ...

  6. Processing 案例 | 由文字构成的球体

    文章目录 引言 准备:球体方程 代码 大体结构 构造函数 update display 小结 引言   球体一直被称为最完美的几何体,它是只有一个面并且连续曲面的立体图形,用肉眼看来球体在各个位置观看 ...

  7. Processing 案例 | 可视化网络爬虫爬遍网络

    文章目录 引言 代码 主要思想 主要结构 Grid类 BFS DFS 颜色的选取 小结 参考资料 引言 吴军先生在他的<智能时代>中曾经提到:"如果我们把资本和机械动能作为大航海 ...

  8. Processing 案例 | 字母在宠物小精灵上翩翩起舞

    文章目录 引言 原作品:Circular Hello Curves 主流程 构造函数 update display 拓展:Broken Heart 变成心形 添加眼睛 更多拓展 小结 引言 最近听见一 ...

  9. criteo 点击率预估_预处理criteo数据集以预测广告的点击率

    criteo 点击率预估 Amany Abdelhalim阿曼尼·阿卜杜勒哈林 Follow跟随 Sep 18 九月18 Preprocessing Criteo Dataset for Predic ...

最新文章

  1. C#操作WMI文章汇总
  2. 个人所得税计算,计算器.
  3. golang 多行字符串 字符串太长分行写
  4. 决策树——学习笔记(一)
  5. css技巧之如何实现ul li边框重合
  6. 计算机网络按定义分,计算机网络定义及其分类
  7. 单链表的快速排序(转)
  8. 路由交换机管理密码篇
  9. Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  10. 抑郁自评量表SDS问卷HTML版
  11. 001-markdown简介,插件的下载和导出
  12. 微信授权登录实现分析
  13. c++取模运算/求余运算
  14. 严平稳随机过程,宽平稳随机过程,二阶矩过程之间的关系
  15. #806.宝箱 思维
  16. java用springboot开发的美食菜谱网(有美食达人)附论文
  17. ruoyi框架分页总条数total返回错误解决方案
  18. 计算机用户加密,计算机硬盘加密的几种方法
  19. 【C语言 实现图书管理系统】
  20. 回望2018,这6家AI+教育公司亮了!| AI最佳掘金案例榜...

热门文章

  1. 裸奔系列之博科SAN交换机(4)---博科SAN间级联
  2. 故宫博物院首次复原“天灯”“万寿灯”迎己亥春节
  3. 飞行器设计之界限线图
  4. ANSYS APDL 命令流输入方法_51CAE_新浪博客
  5. 在测试batik源码中的问题
  6. 微软计划再裁11000人!距上次裁员仅3个月,CEO:未来两年都是苦日子
  7. kong组件_Kong
  8. 字符串拼接这个隐藏大坑,我表示不服~
  9. JQuery EasyUI Datagrid 清空排序状态(箭头)代码
  10. css修改el-button的默认样式