初试Processing——我的第一幅“码绘”作品

前言

Processing是一种灵活的软件写生本,是一种学习如何在视觉艺术的环境中编写代码的语言。自2001年以来,Processing促进了视觉艺术中的软件素养和技术中的视觉素养。有成千上万的学生、艺术家、设计师、研究人员和业余爱好者使用加工来学习和制作原型。

恰逢正在上《互动媒体技术》一课,刚好可以用到Processing来进行创意编程,即“码绘”,废话不多说,接下来介绍我的第一幅“码绘”作品。

临摹作品

首先根据老师的要求,我选择了其中一幅作品进行临摹。


临摹动图

在编写代码前,先观察其规律性。可以看出,虽然动图看上去是由不断变化的圆和正方形组成,但是仔细观察可以看出,其实这幅图是由36个圆和36个扇形组成的,变化的仅仅是扇形的位置。
观察完毕后,开始编写代码。
首先,根据面向对象编程的思想,可以将每个圆形和扇形看作一个对象,这样将减少很多的代码量,还可以轻松利用图片的规律来简化编程。

  public class Circle
  {
     Circle(int col,int row)
     {
      noStroke();
      fill(255);
      ellipse(row*100+50,col*100+50,90,90);
     }
}

这里定义了一个圆的类,用它的构造方法来绘图,比较简单明了,然后在draw函数里面生成对象:

   for(int i=0;i<6;i++)
      {
         for(int j=0;j<6;j++)
          {
              new Circle(i,j);
          }
      }

结果如下图所示:


效果图1

下一步是添加扇形,扇形很明显是四个一组的,2*2的圆形中,每个扇形所在圆的位置都不同,可以用arc函数来进行扇形的绘制。
arc函数的语法如下:

arc(a, b, c, d, start, stop)

根据圆形所处的位置不同,扇形所在的位置也不同,得出如下代码:

public class Circle
{
  Circle(int col,int row)
  {
    noStroke();
    fill(255);
    ellipse(row*100+50,col*100+50,90,90);
    
    if(col%2==0 && row%2==0)
    {
      noStroke();
      fill(0);
      arc(row*100+50,col*100+50,90,90,0,0+HALF_PI);
    }
    
    if(col%2==0 && row%2==1)
    {
      noStroke();
      fill(0);
      arc(row*100+50,col*100+50,90,90,0+HALF_PI,0+PI);
    }
    
    if(col%2==1 && row%2==1)
    {
      noStroke();
      fill(0);
      arc(row*100+50,col*100+50,90,90,0+PI,0+PI+HALF_PI);
    }
    
    if(col%2==1 && row%2==0)
    {
      noStroke();
      fill(0);
      arc(row*100+50,col*100+50,90,90,0+PI+HALF_PI,0+2*PI);
    } 
  }
}

输出结果如下:


效果图2

然后就是想办法让他动起来,也很简单,让arc函数中的最后两个参数,也就是起始点和结束点动态变化就行了,代码如下:

float time;
public class Circle
{
  Circle(int col,int row,float curAngle)
  {
    noStroke();
    fill(255);
    ellipse(row*100+50,col*100+50,90,90);
    
    if(col%2==0 && row%2==0)
    {
      noStroke();
      fill(0);
      curAngle=2*PI-curAngle;
      arc(row*100+50,col*100+50,90,90,curAngle,curAngle+HALF_PI);
    }
    
    if(col%2==0 && row%2==1)
    {
      noStroke();
      fill(0);
      arc(row*100+50,col*100+50,90,90,curAngle+HALF_PI,curAngle+PI);
    }
    
    if(col%2==1 && row%2==1)
    {
      noStroke();
      fill(0);
      curAngle=2*PI-curAngle;
      arc(row*100+50,col*100+50,90,90,curAngle+PI,curAngle+PI+HALF_PI);
    }
    
    if(col%2==1 && row%2==0)
    {
      noStroke();
      fill(0);
      arc(row*100+50,col*100+50,90,90,curAngle+PI+HALF_PI,curAngle+2*PI);
    } 
  }
}

void setup()
{
  size(600,600);
}

void draw()
{  
      background(600,600);
      background(0);
      time=millis()/10%360;
     
      for(int i=0;i<6;i++)
      {
         for(int j=0;j<6;j++)
          {
              new Circle(i,j,time*3.1415926/180);
          }
      }
}

这里,使用了millis函数,millis的官方文档如下:

millis() :Returns the number of milliseconds (thousandths of a second) since starting the program. This information is often used for timing events and animation sequences.

简单来说,就是获取当前的微秒数,但是因为微妙单位太小了,所以我除了10,在取余360,这样一来time变量的值就会在0到360的范围内变化。
至此,最基本的样子已经有点出来了:


GIF1.gif

但是仔细观察不难发现,这个图和原图还是有点差别,原图中扇形的旋转并不是匀速的,而是一种类似正弦运动的形式。
不过,直接用整段的正弦函数是不行的,原因是正弦函数的变化是周期性的,而且函数值先增加后减少。而我们只需要[0,π/2]上的这一段就可以了。
由于在四个象限都要进行变化,所以要进行四次[0,π/2]上的正弦变换。代码如下:

       if(time>0&&time<=90)
       {
          angle=90*sin(time*PI/180);
       }
       if(time>90&&time<=180)
       {
          angle=90+90*sin((time-90)*PI/180);
       }
       if(time>180&&time<=270)
       {
          angle=180+90*sin((time-180)*PI/180);
       }
       if(time>270&&time<=360)
       {
          angle=270+90*sin((time-270)*PI/180);
       }      

最后效果如下:


GIF2.gif

最后附上所有源代码:

float time;
float angle;
public class Circle
{
  Circle(int col,int row,float curAngle)
  {
    noStroke();
    fill(255);
    ellipse(row*100+50,col*100+50,90,90);
    
    if(col%2==0 && row%2==0)
    {
      noStroke();
      fill(0);
      curAngle=2*PI-curAngle;
      arc(row*100+50,col*100+50,90,90,curAngle,curAngle+HALF_PI);
    }
    
    if(col%2==0 && row%2==1)
    {
      noStroke();
      fill(0);
      arc(row*100+50,col*100+50,90,90,curAngle+HALF_PI,curAngle+PI);
    }
    
    if(col%2==1 && row%2==1)
    {
      noStroke();
      fill(0);
      curAngle=2*PI-curAngle;
      arc(row*100+50,col*100+50,90,90,curAngle+PI,curAngle+PI+HALF_PI);
    }
    
    if(col%2==1 && row%2==0)
    {
      noStroke();
      fill(0);
      arc(row*100+50,col*100+50,90,90,curAngle+PI+HALF_PI,curAngle+2*PI);
    }
    
  }
}

void setup()
{

  size(600,600);
  //frameRate(60);
}

void draw()
{  
      background(600,600);
      background(0);
      time=millis()/10%360;
       if(time>0&&time<=90)
       {
          angle=90*sin(time*PI/180);
       }
       if(time>90&&time<=180)
       {
          angle=90+90*sin((time-90)*PI/180);
       }
       if(time>180&&time<=270)
       {
          angle=180+90*sin((time-180)*PI/180);
       }
       if(time>270&&time<=360)
       {
          angle=270+90*sin((time-270)*PI/180);
       }      
      for(int i=0;i<6;i++)
      {
         for(int j=0;j<6;j++)
          {
              new Circle(i,j,angle*3.1415926/180);
          }
      }
}

原创作品

在临摹完第一幅作品之后,我开始制作第一幅原创作品。在openprocessing上看了很多其他人的作品后,有了一点思路。
先来看看最后的效果吧:


GIF3.gif

是会生成一些渐变色的点,跟随鼠标而移动的效果。
那么下面来介绍这个作品的制作过程。
首先,来介绍一下PVector,描述文档如下:

A class to describe a two or three dimensional vector, specifically a Euclidean (also known as geometric) vector. A vector is an entity that has both magnitude and direction. The datatype, however, stores the components of the vector (x,y for 2D, and x,y,z for 3D). The magnitude and direction can be accessed via the methods mag() and heading().
In many of the Processing examples, you will see PVector used to describe a position, velocity, or acceleration. For example, if you consider a rectangle moving across the screen, at any given instant it has a position (a vector that points from the origin to its location), a velocity (the rate at which the object's position changes per time unit, expressed as a vector), and acceleration (the rate at which the object's velocity changes per time unit, expressed as a vector). Since vectors represent groupings of values, we cannot simply use traditional addition/multiplication/etc. Instead, we'll need to do some "vector" math, which is made easy by the methods inside the PVector class.

它是一个封装好的向量,可以表示位置,速度和加速度,还有封装的函数,十分方便。于是首先,我先定义了三个PVector变量,分别表示位置,速度和加速度,将它们放在Point类的构造方法中,并初始化位置变量为屏幕内的随机位置:

class Point {
  PVector Position, speed, accelerate; 
  Point(){
  Position = new PVector(random(width), random(height));
  speed= new PVector();
  accelerate= new PVector();
  }
}

接着,我在Point类里写了两个函数,一个render()用于绘画点,一个update()用于更新点的位置。

  void render(){
  fill(0);
  noStroke();
  ellipse(Position.x,Position.y,10,10);
  }
  void update(){
  accelerate = new PVector(mouseX-Position.x, mouseY -Position.y); 
  speed.add(accelerate);
  Position.add(speed);
  }

但是效果不是很理想,几乎所有的点都会因为离鼠标的距离太远导致加速度过大,都会聚集在一点,就没有环绕跟随的感觉了。我查询了API,得知PVector类里面有一个limit方法,可以限制向量的大小,于是我添加了这两句:

accelerate.limit(1);
speed.limit(20);

如此一来,最基础的效果已经实现了,接下来添加渐变色的效果。
这个也不难,只要将render函数里面的fill(0)语句改动一下。为了实现渐变色的效果,我选择了在将点实例化的过程中改变颜色,将render函数改为R,G,B三个形参的形式。然后再在draw函数里定义abc三个变量存储渐变的数据。代码如下:

void render(int R,int G,int B){
  fill(R,G,B);
  noStroke();
  ellipse(Position.x,Position.y,10,10);
  }
void draw(){
  int a=millis()/10;
  int b=millis()/50;
  int c=millis()/100;
  fill(255,255,255,50);
  rect(0,0,800,800);
  for( int i =0; i < points.length; i++){
    points[i].update(); 
    points[i].render(c,b,a); 
  } 
}

这样一来就大功告成了,最后贴出全部源代码:

class Point {
  PVector Position, speed, accelerate; 
  Point(){
  Position = new PVector(random(width), random(height));
  speed= new PVector();
  accelerate= new PVector();
  }
  void render(int R,int G,int B){
  fill(R,G,B);
  noStroke();
  ellipse(Position.x,Position.y,10,10);
  }
  void update(){
  accelerate = new PVector(mouseX-Position.x, mouseY -Position.y); 
  accelerate.limit(1);
  speed.add(accelerate);
  speed.limit(20);
  Position.add(speed);
  }
}
Point [] points = new Point[50];
void setup(){
  size(800,800);
  background(255);
  for( int i =0; i < points.length; i++){
    points[i] = new Point();
  }
}
void draw(){
  int a=millis()/10;
  int b=millis()/50;
  int c=millis()/100;
  fill(255,255,255,50);
  rect(0,0,800,800);
  for( int i =0; i < points.length; i++){
    points[i].update(); 
    points[i].render(c,b,a); 
  } 
}

总结

本次对processing的尝试可以说遇到了不少的困难,但是最后完成作品也非常的有成就感,让我深切的感受到了代码也可以和艺术很好的结合,让人产生美的观感。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容