Processing粒子系统-用烟雾画出来的世界名画

Processing粒子系统-烟雾画出来的世界名画

目录:

  • 效果欣赏
  • 参考示例
  • 程序思想以及代码实现
  • 作者有话说

效果欣赏

注:烟雾的的扩散方向随着鼠标位置的不同而不同,构成烟雾的基础图元可以是普通几何体,也可以是材质贴图。

鼠标位置出现的蓝色或者黄色的圆为录屏软件所添加,程序运行时并不会有显示。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

参考示例

注:本程序的参考案例均来自《代码本色》一书的第四章

参考案例一:4-02

ArrayList<Particle> particles;

void setup() {
  size(640, 360);
  particles = new ArrayList<Particle>();
}

void draw() {
  background(255);

  particles.add(new Particle(new PVector(width/2, 50)));

  // Looping through backwards to delete
  for (int i = particles.size()-1; i >= 0; i--) {
    Particle p = particles.get(i);
    p.run();
    if (p.isDead()) {
      particles.remove(i);
    }
  }
}
// The Nature of Code
// Daniel Shiffman
// http://natureofcode.com

// Simple Particle System

class Particle {
  PVector position;
  PVector velocity;
  PVector acceleration;
  float lifespan;

  Particle(PVector l) {
    acceleration = new PVector(0, 0.05);
    velocity = new PVector(random(-1, 1), random(-2, 0));
    position = l.copy();
    lifespan = 255.0;
  }

  void run() {
    update();
    display();
  }

  // Method to update position
  void update() {
    velocity.add(acceleration);
    position.add(velocity);
    lifespan -= 2.0;
  }

  // Method to display
  void display() {
    stroke(0, lifespan);
    strokeWeight(2);
    fill(127, lifespan);
    ellipse(position.x, position.y, 12, 12);
  }

  // Is the particle still useful?
  boolean isDead() {
    if (lifespan < 0.0) {
      return true;
    } 
    else {
      return false;
    }
  }
}

示例代码的运行效果如下:
在这里插入图片描述

示例代码的参考作用主要是熟悉绘制粒子系统的一个完整的流程(出生-更新-死亡)。在该示例中,每个粒子从出生起获得的向下加速度一定,但是初始速度不同,每一帧都在更新粒子的位置(update函数)直至粒子的生命值被消耗完,就移除粒子。

为了维持粒子系统的稳定,在移除粒子的同时不断往粒子系统里添加新的粒子,这就是一个简单粒子系统的思想。

参考示例二:4-08

// The Nature of Code
// Daniel Shiffman
// http://natureofcode.com

// Smoke Particle System

// A basic smoke effect using a particle system
// Each particle is rendered as an alpha masked image

/* @pjs preload="processingjs/chapter04/_4_08_ParticleSystemSmoke/data/texture.png"; */

ParticleSystem ps;

void setup() {
  size(640,360);
  PImage img = loadImage("texture.png");
  ps = new ParticleSystem(0,new PVector(width/2,height-75),img);
}

void draw() {
  background(0);
  
  // Calculate a "wind" force based on mouse horizontal position
  float dx = map(mouseX,0,width,-0.2,0.2);
  PVector wind = new PVector(dx,0);
  ps.applyForce(wind);
  ps.run();
  for (int i = 0; i < 2; i++) {
    ps.addParticle();
  }
  
  // Draw an arrow representing the wind force
  drawVector(wind, new PVector(width/2,50,0),500);

}

// Renders a vector object 'v' as an arrow and a position 'loc'
void drawVector(PVector v, PVector pos, float scayl) {
  pushMatrix();
  float arrowsize = 4;
  // Translate to position to render vector
  translate(pos.x,pos.y);
  stroke(255);
  // Call vector heading function to get direction (note that pointing up is a heading of 0) and rotate
  rotate(v.heading2D());
  // Calculate length of vector & scale it to be bigger or smaller if necessary
  float len = v.mag()*scayl;
  // Draw three lines to make an arrow (draw pointing up since we've rotate to the proper direction)
  line(0,0,len,0);
  line(len,0,len-arrowsize,+arrowsize/2);
  line(len,0,len-arrowsize,-arrowsize/2);
  popMatrix();
}
// The Nature of Code
// Daniel Shiffman
// http://natureofcode.com

class Particle {
  PVector pos;
  PVector vel;
  PVector acc;
  float lifespan;
  PImage img;

  Particle(PVector l,PImage img_) {
    acc = new PVector(0,0);
    float vx = randomGaussian()*0.3;
    float vy = randomGaussian()*0.3 - 1.0;
    vel = new PVector(vx,vy);
    pos = l.get();
    lifespan = 100.0;
    img = img_;
  }

  void run() {
    update();
    render();
  }
  
  // Method to apply a force vector to the Particle object
  // Note we are ignoring "mass" here
  void applyForce(PVector f) {
    acc.add(f);
  }  

  // Method to update position
  void update() {
    vel.add(acc);
    pos.add(vel);
    lifespan -= 2.5;
    acc.mult(0); // clear Acceleration
  }

  // Method to display
  void render() {
    imageMode(CENTER);
    tint(255,lifespan);
    image(img,pos.x,pos.y);
    // Drawing a circle instead
    // fill(255,lifespan);
    // noStroke();
    // ellipse(pos.x,pos.y,img.width,img.height);
  }

  // Is the particle still useful?
  boolean isDead() {
    if (lifespan <= 0.0) {
      return true;
    } else {
      return false;
    }
  }
}
// The Nature of Code
// Daniel Shiffman
// http://natureofcode.com

// Smoke Particle System

// A class to describe a group of Particles
// An ArrayList is used to manage the list of Particles 

class ParticleSystem {

  ArrayList<Particle> particles;    // An arraylist for all the particles
  PVector origin;        // An origin point for where particles are birthed
  PImage img;

  ParticleSystem(int num, PVector v, PImage img_) {
    particles = new ArrayList<Particle>();              // Initialize the arraylist
    origin = v.get();                        // Store the origin point
    img = img_;
    for (int i = 0; i < num; i++) {
      particles.add(new Particle(origin, img));    // Add "num" amount of particles to the arraylist
    }
  }

  void run() {
    for (int i = particles.size()-1; i >= 0; i--) {
      Particle p = particles.get(i);
      p.run();
      if (p.isDead()) {
        particles.remove(i);
      }
    }
  }

  // Method to add a force vector to all particles currently in the system
  void applyForce(PVector dir) {
    // Enhanced loop!!!
    for (Particle p: particles) {
      p.applyForce(dir);
    }
  }  

  void addParticle() {
    particles.add(new Particle(origin, img));
  }

}

示例的运行效果如下(鼠标位置的蓝色是录屏软件自动添加的,程序运行不带这个效果):
在这里插入图片描述
该示例中将将多个粒子的管理封装成类,形成粒子系统类,在用这个类可以方便地对添加粒子、更改粒子受力以及销毁粒子,本例子里面粒子的受力除了重力外还有水平方向的力,具体的受力大小和方向由鼠标的位置来决定。

最后程序的运行结果是一个被被风吹动的烟雾。

程序思想以及代码实现

在看到以上两个示例后,我灵感一现:如果把每个烟雾粒子系统当成一个小小的图元,图元的颜色来自给出的名画,烟雾的方向随着鼠标变化,那么就可以得到一幅动态的名画。

换成编程上的思想就是:根据读入的“名画”长宽,每间隔固定像素,生成一个个粒子系统,每个粒子系统的颜色来自对应位置名画的像素颜色。读取鼠标所在的位置,更改粒子系统受力的方向

参考根据上面的示例,我们需要一个粒子类,需要以下这些属性,其中颜色属性是特有的。

  PVector pos;//粒子的位置
  PVector vel;//速度
  PVector acc;//加速度
  float lifespan;//生命
  PImage img;//图片
  color mycolor;//颜色

在初始化粒子的时候应该赋予粒子初始位置、速度、生命以及颜色(如果使用贴图的话还应该有图片)。

 Particle(PVector l,PImage img_,color c) {
    acc = new PVector(0,0);
    float vx = randomGaussian()*0.3;      //高斯噪声产生随机数
    float vy = randomGaussian()*0.3 - 1.0;
    vel = new PVector(vx,vy);
    pos = l.get();                       //位置
    lifespan = 50.0;                    //生命周期
    img = img_;
    mycolor=c;
  }

在渲染粒子的时候,粒子的颜色应该随着粒子的生命衰减而变浅,这里采用的是降低透明度的方式来体现这一点。本程序中设定的图元为方形,也可以更改为圆形,三角或者用户自定义的图形或材质。

 // 展示 选择材质贴图或者直接用简单几何
  void render() {
    //imageMode(CENTER);
    //tint(mycolor,lifespan);//控制颜色以及透明度
    //image(img,pos.x,pos.y);
    // Drawing a circle instead
     fill(mycolor,lifespan);
     noStroke();
     rect(pos.x,pos.y,5,5);
  }

接下来设定粒子系统,粒子系统首先得获取到颜色才能将颜色给粒子,为此有参数mycolor。

class ParticleSystem {

  ArrayList<Particle> particles;    // 粒子列表
  PVector origin;                   // 粒子的出生点
  PImage img;                       //材质
  color mycolor;
  ParticleSystem(int num, PVector v, PImage img_,color c) {
    particles = new ArrayList<Particle>();              // 初始化粒子列表
    origin = v.get();                                   // 保存初始点
    img = img_;
    mycolor=c;
    for (int i = 0; i < num; i++) {
      particles.add(new Particle(origin, img,mycolor));        // 为粒子系统添加添加num个粒子
    }
  }
//管理粒子的活动
  void run() {
    for (int i = particles.size()-1; i >= 0; i--) {
      Particle p = particles.get(i);
      p.run();
      if (p.isDead()) {
        particles.remove(i);//粒子死亡,移除
      }
    }
  }
  //为粒子添加力
  void applyForce(PVector dir) {
    for (Particle p: particles) {
      p.applyForce(dir);
    }
  }  
  void addParticle() {
    particles.add(new Particle(origin, img,mycolor));
  }
}

最后集合使用上述的类了。在此之前,我们先读入一张图片,作为目标“名画”,processing里面提供函数loadImage

PImage myImage;
 myImage = loadImage("6.jpg");

为了更好的视觉效果,将画布的尺寸调整为图片的大小(也可以再大一点)。

  surface.setSize(myImage.width, myImage.height);

用一个for循环遍历图片,初始化粒子系统,这里设置的像素间隔为8。


  particlesS = new ArrayList<ParticleSystem>();              // 初始化粒子系统
  int temp=8;
  for (int i=0;i<myImage.width;i=i+temp)
  {
    for(int j=0;j<myImage.height;j=j+temp)
    {
      particlesS.add(new ParticleSystem(0,new PVector(i,j),img,myImage.get(i,j)));  
    }
  }

捕捉鼠标位置,生成相应的受力,这里将鼠标在画布上的位置映射到一定范围内,防止受力过于夸张。

  // 添加受力
  float dx = map(mouseX,0,width,-0.2,0.2);
  float dy=map(mouseY,0,height,-0.3,0.3);
  PVector wind = new PVector(dx,dy);

更新粒子系统:

for (int i=0;i<particlesS.size()-1;i++)
{
   ParticleSystem p = particlesS.get(i);
    p.applyForce(wind);
    p.run();
    for (int j = 0; j < 2; j++) {
    p.addParticle();
  }
}

下面给出使用粒子系统的文件的代码:

PImage myImage;
int halfImage;
ArrayList<ParticleSystem> particlesS;    // 粒子列表

void setup() {
  size(640,360);
  PImage img = loadImage("2.png");//如果用材质的话,可以用这个
  myImage = loadImage("6.jpg");
  surface.setSize(myImage.width, myImage.height);
  //myImage.loadPixels();
  particlesS = new ArrayList<ParticleSystem>();              // 初始化粒子系统
  int temp=8;
  for (int i=0;i<myImage.width;i=i+temp)
  {
    for(int j=0;j<myImage.height;j=j+temp)
    {
      particlesS.add(new ParticleSystem(0,new PVector(i,j),img,myImage.get(i,j)));  
    } 
  }
}

void draw() {
  background(255);
  
  // 添加受力
  float dx = map(mouseX,0,width,-0.2,0.2);
  float dy=map(mouseY,0,height,-0.3,0.3);
  PVector wind = new PVector(dx,dy);

for (int i=0;i<particlesS.size()-1;i++)
{
   ParticleSystem p = particlesS.get(i);
    p.applyForce(wind);
    p.run();
    for (int j = 0; j < 2; j++) {
    p.addParticle();
  }
}
}

到了这一步,程序还是不能运行的,因为找不到图片文件,所以我们需要在源文件pde的同级目录下新建一个data文件夹,文件夹里面装我们的图片。在这里插入图片描述

作者有话说

千万!每次写代码都要注意保存,就在我写这篇博客的过程中,下课了,我去吃饭,我明明按了ctrl+S但是!它并没有保存!后来把代码又写了一遍!我。。。好难啊。

话说回来,虽然本程序并不复杂,但是本人还是十分喜欢最后的效果,而且感觉还可以做出诸多的拓展,比如说鼠标点击画面产生波纹的效果。

  • 18
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 59
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 59
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值