2011/07/12

ビジュアルエフェクト試行錯誤

今日は、実験作 2 作のご紹介です。

重くならないように、このページにはサムネイル画像を貼りました。 各作品名のハイパーリンクを踏めば、動作を確認できるページへジャンプします。

ぜひ実際に動作するサンプルをご覧ください。



Rainbow in the Night

きらきら輝くパーティクル。シンプルなピクセル操作を組み合わせで、あたかも発光しているかのような視覚効果が得られます。

マウスボタンを押し続けると、オーロラが出現します。




White Cube

ルービックキューブの柔軟な回転動作の可視化を実現するためにあれこれ考えた結果です。

パフォーマンス的な意味では、さらなる最適化の余地もありますが、『連鎖的な行列の作用がどんな幾何学的変化をもたらすのか』というもう一つのテーマを追究するため、敢えて行列スタックを多用しています。




【ソースコード】

[1] Rainbow in the Night

final int  N_PARTICLES = 2000;
Particle[] particles;
float[][]  speedRateMap;
long       time;
 
void setup() {
  size(400, 300);
  frameRate(30);
  time = 0;
   
  particles = new Particle[N_PARTICLES];
  for(int i = 0; i < particles.length; i++) {
    particles[i] = new Particle(random(width), random(height));
  }
   
  // 通常時・ブレーキ時のスピードレート
  float normalSpeedRate = 2.0;
  float brakeSpeedRate  = 0.1;
   
  // ノイズ画像から速度レートマップ生成
  // ========================================================
  // smap を任意の画像に差し替えれば、
  // パーティクルでメッセージを作る事も可能
  speedRateMap = new float[width][height];
  PImage smap = createCroud();
  for(int y = 0; y < smap.height; y++) {
    for(int x = 0; x < smap.width; x++) {
      int c = smap.pixels[y * width + x] & 0xFF;
      // マップに基づいて速度の減衰率を求める
      float data = 1.0 - (float)c / 0xFF;
      data *= (normalSpeedRate - brakeSpeedRate);
      data += brakeSpeedRate;
       
      speedRateMap[x][y] = data;
    }
  }
}
 
void draw() {
  time++;
  background(0);
  stroke(255);
   
  for(int i = 0; i < particles.length; i++) {
    Particle p = particles[i];
    stroke(p._color);
    point(p.x, p.y);
    p.update();
  }
  blendTwincle();
}
 
private void blendTwincle() {
  PImage fg = createTwincle();
 
  loadPixels();
  for(int y = 0; y < height; y++) {
    for(int x = 0; x < width; x++) {
      int c1 = pixels[y * width + x];
      int c2 = fg.pixels[y * width + x];
      pixels[y * width + x] = addColor(c1, c2);
    }
  }
  updatePixels();
}
 
int addColor(int c1, int c2) {
  int r1 = c1 >> 16 & 0xFF;
  int g1 = c1 >>  8 & 0xFF;
  int b1 = c1       & 0xFF;
       
  int r2 = c2 >> 16 & 0xFF;
  int g2 = c2 >>  8 & 0xFF;
  int b2 = c2       & 0xFF;
       
  int r = r1 + r2 < 0xFF ? r1 + r2 : 0xFF;
  int g = g1 + g2 < 0xFF ? g1 + g2 : 0xFF;
  int b = b1 + b2 < 0xFF ? b1 + b2 : 0xFF;
   
  return 0xFF << 24 | r << 16 | g << 8 | b;
}
 
// 指定した透過率で色を黒っぽくする
int transColor(int c, float _alpha) {
  float a;
  a = min(_alpha, 1.0);
  a = max(_alpha, 0.0);
   
  int r1 = c >> 16 & 0xFF;
  int g1 = c >>  8 & 0xFF;
  int b1 = c       & 0xFF;
   
  int r = (int)(r1 * a + 0.5);
  int g = (int)(g1 * a + 0.5);
  int b = (int)(b1 * a + 0.5);
  return 0xFF << 24 | r << 16 | g << 8 | b;
}
 
PImage blendImage;
private PImage createTwincle() {
  if(blendImage == null)
    blendImage = createImage(width / 2, height / 2, RGB);
   
  PImage _capture = createImage(width / 2, height / 2, RGB);
   
  float _alpha;
  if(mousePressed) _alpha = 0.98;  // マウスボタンを押すとブーストする
  else _alpha = random(0.9, 0.99);
   
  loadPixels();
  for(int y = 0; y < _capture.height; y++) {
    for(int x = 0; x < _capture.width; x++) {
      int c1 = blendImage.pixels[y * blendImage.width + x];
      int c2 = pixels[(y * 2) * width + (x * 2)];
 
      c1 = transColor(c1, _alpha);
       
      blendImage.pixels[y * blendImage.width + x]
        = _capture.pixels[y * _capture.width + x]
          = addColor(c1, c2);
    }
  }
  updatePixels();
  blendImage.filter(BLUR);
  _capture.resize(width, height); 
  return _capture;
}
 
class Particle {
  public int x, y;
  public int _color;
  private float  _x,  _y;
  private float _dx, _dy;
  private final int MAX_H = 120;  // 色相の階調数
   
  Particle(float x, float y) {
    initialize();
     
    this._x = (int)(this._x = x);
    this._y = (int)(this._y = y);
    _color = 0x00000000;
  }
   
  void update() {
    float rate = 1.0;
    // マップから速度レート読み込み
    if (x >= 0 && x < speedRateMap.length
        && y >= 0 && y < speedRateMap[0].length)
          rate = speedRateMap[x][y];
     
    x = (int)(_x += (_dx * rate));
    y = (int)(_y += (_dy * rate));
    if(y >= height) initialize();
  }
   
  private void initialize() {
    colorMode(HSB, MAX_H, 100, 100);
    _color = color(time % MAX_H, 50, 70);
         
    x = (int)(_x = random(width));
    y = (int)(_y = 0);
     
    this._dx = random(-0.1, 0.1);
    this._dy = random(2, 4);
  }
}
 
// ==========================================================
// 雲模様データの生成
// ==========================================================
private PImage createCroud() {
  PImage img = createImage(width, height, RGB);
   
  float xbase  = random(100);
  float ybase  = random(100);
   
  float xnoise = 0.0;
  float ynoise = 0.0;
  float inc    = 0.02;
   
  for(int y = 0; y < height; y++) {
    for(int x = 0; x < width; x++) {
      int _gray =
        (int)(noise(xnoise+xbase, ynoise+ybase)*0xFF+0.5);
      img.pixels[y * width + x] = _gray;
      xnoise += inc;
    }
    xnoise = 0.0;
    ynoise += inc;
  }
  return img;
}

[2] White Cube

final float C_SIZE   = 100;
final float C_MARGIN =   2;
 
void setup() {
  size(400, 300, P3D);
}
 
// 回転用パラメータ
long t;
int mode_RotAxis, mode_RotPos, mode_RotDir;
void draw() {
  background(0);
  fill(255);
 
  camera();
  lights();
  float cam_dist  = 500;
  float cam_angle = radians(t*0.1);
  camera(cam_dist*cos(cam_angle), -cam_dist, cam_dist*sin(cam_angle),
         0, 0, 0, 0, 1, 0);
  noStroke();
   
  pushMatrix();
  float offset = C_SIZE + C_MARGIN;
  translate(-offset, -offset, -offset);
   
  // ここで回転のモード切り替え
  if((t+=3) % 90 == 0) {
    mode_RotAxis = (int)(random(3));
    mode_RotPos  = (int)(random(3));
    mode_RotDir  = random(1) < 0.5 ? -1 : 1;
  }
   
  translate(offset, offset, offset);
  switch (mode_RotAxis) {
    case 0:
      rotateX(radians(90));
      break;
    case 1:
      rotateY(radians(90));
      break;
  }
  translate(-offset, -offset, -offset);
   
  for(int i = 0; i < 3; i++) {
    for(int j = 0; j < 3; j++) {
      for(int k = 0; k < 3; k++) {
        pushMatrix();
        translate(offset, offset, offset);
        if(k == mode_RotPos) rotateZ(mode_RotDir * radians(t));
        translate(-offset, -offset, -offset);
        translate(i * offset, j * offset, k * offset);
        box(C_SIZE);
        popMatrix();
      }
    }   
  }
  popMatrix();
}



【裏話】


『Rainbow in the Night』 は、もともと Rainbow Shower という題にするつもりでしたが、同名の植物(厳密には雑種)が存在するため、閲覧者に意図せぬ先入観を扶植してしまうのではないかと思い、現在の名称に変更しました。

素直に日本語で 『虹の星屑』 とでも名付けておけばよかったです。

0 件のコメント:

コメントを投稿

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