2011/06/16

Processingで光の粒を飛ばしてみた

本日のがらくたはこちら。


一見、なんの変哲もない簡易的なパーティクルシステム。だけど、パーティクル用のテクスチャをプログラム中で動的に生成するのがちょっとしたオリジナリティだ。

ネットを適当に巡回すれば、似たようなものの作り方はいくらでも解説されているが、その殆どは予め画像処理ソフト等でテクスチャファイルを作成しておく必要がある。これを手間だと感じる人は意外に多い(と思う)。

しかし、ここでご紹介するサンプルは、そんなものをわざわざ作らなくてもソースコードをコピペするだけで動いてしまう。これを見ているよい子のみんなも手軽に試す事ができるぞ!



【ソースコード】

申し訳ないことに、面倒だったのでパーティクルをクラスにまとめなかった。この程度なら差し支えあるまい。

int counter;    // カウンタ
PImage[]  tex;  // テクスチャ配列
PVector[] pos;  // パーティクル位置ベクトル
PVector[] vel;  // パーティクル速度ベクトル

final int   NUM_PARTICLES  = 360;  // パーティクル数
final float ACCELERATION_Y = 0.1;  // 加速度

void setup() {
  size(400, 300);
  tex = new PImage[NUM_PARTICLES];
  pos = new PVector[NUM_PARTICLES];
  vel = new PVector[NUM_PARTICLES];
  counter = 0;
  
  // パーティクルのテクスチャを配列に格納
  colorMode(HSB, NUM_PARTICLES, 100, 100);
  for(int i = 0; i < tex.length; i++) {
    tex[i] = getParticleTexture(color(i, 80, 50));
    pos[i] = new PVector(0, height * 2);
    vel[i] = new PVector();
  }
}

void draw() {
  noStroke();
  fill(0, 30);
  rect(0, 0, width, height);
  
  // カウンタ更新
  if (++counter >= NUM_PARTICLES) counter = 0;
  
  // ずれ量(初期位置にノイズを加える)
  float noiseAmount = tex[counter].width/4.0;
  
  // 初期位置の設定
  pos[counter].x = width/2 + random(-noiseAmount, noiseAmount);
  pos[counter].y = height * 9/10 + random(-noiseAmount, noiseAmount);
  
  // 初速度の設定
  vel[counter].x =  random(-2, 2);
  vel[counter].y = -random(3, 7);
  
  // パーティクルの更新
  for(int i = 0; i < NUM_PARTICLES; i++) {
    if(pos[i].y < height+tex[i].height && pos[i].y>-tex[i].height) {
      // パーティクル描画 加算合成にするのがポイント
      blend(tex[i], 0, 0, 
            tex[i].width, tex[i].height,
            (int)pos[i].x, (int)pos[i].y,
            tex[i].width, tex[i].height, ADD);
    }
    // 位置・速度更新
    pos[i].x += vel[i].x;
    pos[i].y += vel[i].y;
    vel[i].y += ACCELERATION_Y;
  }
}

// ==============================================
// 指定した色のパーティクル用テクスチャを生成する
// 引数 c : パーティクルの色
// ==============================================
PImage getParticleTexture(color c) {
  // 画像サイズとパーティクルの半径
  final int   IMG_SIZE        = 15;
  final float PARTICLE_RADIUS = 0.5f * IMG_SIZE - 2;
  
  // colorMode(RGB, 255, 255, 255);
  // 画像を作成
  PImage img = createImage(IMG_SIZE, IMG_SIZE, RGB);
  img.loadPixels();
  for(int i = (int)PARTICLE_RADIUS; i > 0; i--) {
    // グラデーション作成
    int a = (int)(0xFF*(float)(PARTICLE_RADIUS-i)/PARTICLE_RADIUS);
    int fg = c;
    int fR = (0x00FF0000 & fg) >>> 16;  
    int fG = (0x0000FF00 & fg) >>> 8;
    int fB =  0x000000FF & fg; 
    int rR = (fR * a) >>> 8;
    int rG = (fG * a) >>> 8;
    int rB = (fB * a) >>> 8;
    fg = 0xFF000000 | (rR << 16) | (rG << 8) | rB;
    // パーティクル用テクスチャ作成
    for(int y = 0; y < img.height; y++) {
      for(int x = 0; x < img.width; x++) {
        float yDistance = (y-img.height/2.0)*(y-img.height/2.0);
        float xDistance = (x-img.width/2.0)*(x-img.width/2.0);
        if(yDistance + xDistance <= i*i) {
          img.pixels[y * img.width + x] = fg;
        }
      }
    }
  }
  img.updatePixels();
  img.filter(BLUR);
  return img;
}

チャームポイントは getParticleTexture() メソッドで、引数に適当な色を渡すと、指定した色のパーティクルテクスチャを返してくれる。

あとは、そのテクスチャを blend()加算合成すれば、キラキラした光の粒子ができあがるのだ(テクスチャは PImage オブジェクトなので、かなり柔軟に扱う事ができる)。

6月17日追記: 高速化しましたよ!(→OpenProcessing



【今日の戯言】

Processing の日本語サイトを運営していらっしゃる P5info さんが、執筆意欲あふれるツイートをなさっていた。


そうか、P5本を作りたいと思ってたけど、それとは別に #Processing のチートシートがあるといいな。
P5は関数が多く、概念の説明も多少いれて、数ページの小冊子になると思うけど。2011年6月16日 15:15 via HootSuite


最近、日本語で読める Processing の書籍はそれなりに充実してきたけれど、いま書店に並んでいるもののほとんどがプログラミング初心者向けで、『普段目にしているビジュアルエフェクトは一体どうやって作られているのだろう』という知的好奇心を満たすような書籍が極端に少ないと感じている(気のせいかな)。

API 仕様を理解するところまでは、親切な道標が用意されている。しかし、そこから思い描いているビジョンを形にしようとすると、どう組めばよいのか分からずに躓いてしまう。何を隠そう、ぼくもその一人。

だから、ぼくが次にほしいと思う本は、アイディアを形にするために必要なものをまとめたレシピ集だ。

多少マニアックでもいい、もしも iTunes のビジュアライザのような超クールなエフェクトの作り方を解説した書籍が出たら、5,000 円までならためらわずに出す

同じように、世界のどこかでこういう本を欲しがっている人はいないかなぁ (チラッ

2 件のコメント:

ひとことどうぞφ(・ω・,,)