いろんなイージングを贅沢に使う!「階段状イージング」で動きに変化をつける

2022年1月23日日曜日

p5.js Processing テクニック

t f B! P L
「階段状イージング」のグラフ

アニメーションを作るとき、「イージング」と呼ばれる加速度をつけた緩急のある動きをつけてあげると、作品の魅力がぐっと増します。

上がイージングなし、下がありの例です。

このイージングを連続させて贅沢に使う「階段状イージング」と、それを使った作例を紹介します。

👉 Read this article in English.

 

いろいろなイージング

イージングって何?

「イージング」とは、一般に動きに緩急をつけることです。

プログラミングとしては、0.0 から 1.0 までをどう変化させるかになります。一例として、先程のアニメーションのイージングを、コードとグラフで示すとこのようになります。


// イージングなし
function noEasing(x) {
  return x;
}

// イージングあり
function easing(x) {
  return x * x * x;
}


イージングなし/ありのグラフ

左のグラフがイージングなし、右がイージングあり。x軸がパラメータ x の値、y軸が関数の返り値です。イージングありの方は、なんのことはない x の3乗のグラフです。

 

イージングの関数をどう作るか?

イージングの動きは様々な種類が開発されていて、それらは「イージング関数チートシート」で見ることができます。

イージング関数の元祖 Robert Penner's Easing Functions から、イージング関数チートシート
https://easings.net/ja

それぞれのグラフから説明ページに遷移でき、そこに数学関数という項目でコードが記載されています。コードは TypeScript で書かれていますが、p5.js で使うなら ': number' を削除して、こう書き換えればいいです。


// TypeScript
function easeInCubic(x: number): number {
  return x * x * x;
}

// p5.js
function easeInCubic(x) {
  return x * x * x;
}


それぞれが、パラメータ x に 0.0 から 1.0 の値を与えると、イージング後の 0.0 から 1.0 の値を返してくれる関数になっています。

 

イージング easeInCubic を使った 'p5.js' での作例



// Rotating circles.

const w = 720;
const h = w;
const cNum = 3;
const fRate = 30;
const cycle = fRate * 2;

function setup() {
  createCanvas(w, h);
  frameRate(fRate);
}

function draw() {

  const ease = easeInCubic((frameCount % cycle) / cycle)

  translate(w * 0.5, h * 0.5);
  background(240);

  noStroke();
  for (let c = 0; c < cNum; c++) {
    let r = 0.2 * (0.5 + sin(PI * ease));
    let t = TWO_PI * (ease + c / cNum);
    let x = w * r * cos(t);
    let y = h * r * sin(t);
    fill((c * 100) % 255);
    circle(x, y, w * 0.1);
  }

}

// easing function
function easeInCubic(x) {
  return x * x * x;
}

 

関数をいろいろ繋いでみよう!

いろいろ異なるイージング関数をつなげて使ったら、新しい動きが作れるのでは?と、やってみたのがこのグラフ。

様々なイージング関数を使った「階段状イージング」のグラフ

いくつか異なる種類の関数を用意して順番に実行していき、全部で 0.0 から 1.0 まで変化するようにします。例だと 5個のイージング関数を使っていて、結果が階段状になるので、階段状イージングと呼んでいます。

アニメーション適用例はこちら。easeInOutCubic 一個のイージングだとこう。

3個のイージング easeInOutCubic、 easeOutQuad、 easeInCubic で作った階段状イージングだとこうなります。

3種類のイージング関数を使った「階段状イージング」のグラフ

階段状イージングは、イージング関数を一個使うよりも変化を出せます。さらに、関数をランダムに組み合わせたり、順番を途中で変えたりすることで、実行のたびに新鮮な動きを楽しめるかもしれません。

 

 

階段状イージングを盛り込んだ Processing 作例

blendMode(DIFFERENCE) で四角形を描いたら面白い絵になりました。

blendMode(DIFFERENCE) で描いた面白い絵

動きの途中にできる DIFFERENCE 特有の模様が面白いので、四角形が単に動くアニメーションじゃなくて、途中途中をストップモーションで見せたくなりました。これは階段状イージングにうってつけの題材です!

こちらがコードになります。画面表示はせず、アニメーション作成用の画像を生成するコードになっています。GPL で公開します。ライセンス条項にのっとってご自由にお使いください。


/**
 * Life Goes Round.
 * an animation using step easing.
 *
 * @author @deconbatch
 * @version 0.1
 * @license GPL Version 3 http://www.gnu.org/licenses/
 * Processing 3.5.3
 * 2022.01.23
 */


void setup() {
  size(720, 720);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
  smooth();
  noLoop();
	rectMode(CENTER);

  int   frmRate  = 30;
  int   cycleSec = 3;
  int   frmMax   = frmRate * cycleSec;
  float rectSiz  = min(width, height) * random(0.5, 0.8);
  float phaseR   = random(PI);
  float phaseT   = random(PI);
  float hueOrg   = random(360.0);

  // easing functions
  ArrayList<Ease> easing = new ArrayList<Ease>();
  easing.add(new InOutQuart());
  easing.add(new OutQuart());
  easing.add(new InBack());
  easing.add(new InQuad());
  easing.add(new OutBack());
  int cycleMax = easing.size();
  int easingStart = floor(random(cycleMax));
  
	translate(width * 0.5, height * 0.5);
  noStroke();
  float easePrev  = 0.0;
  float easeRatio = 0.0;
  for (int cycleCnt = 0; cycleCnt < cycleMax; cycleCnt++) {
    for (int frmCnt = 0; frmCnt < frmMax; frmCnt++) {
      // step easing
      float frmRatio = map(frmCnt, 0, frmMax - 1, 0.0, 1.0);
      easeRatio = easePrev + easing.get((easingStart + cycleCnt) % cycleMax).ease(frmRatio) / cycleMax;
      float radii = abs(sin(phaseR + easeRatio * PI)) * rectSiz * 0.3;
      float hueBase = hueOrg + 360.0 * easeRatio;
  
      blendMode(BLEND);
      background(hueBase % 360.0, 30.0, 60.0, 100.0);

      blendMode(DIFFERENCE);
      fill((hueBase + 60.0) % 360.0, 30.0, 80.0, 100.0);
      for (int p = 0; p < 8; p++) {
        int sign = (p % 2 == 0) ? 1 : -1;
        for (float r = 0.2; r < 0.5; r += 0.1) {
          float t = PI * 0.25 * p + sign * (phaseT + TWO_PI * easeRatio) * r * 2.5;
          float x = r * width * cos(t);
          float y = r * height * sin(t);
          rect(x, y, rectSiz, rectSiz, radii);
        }
      }
  
      saveFrame("frames/" + String.format("%02d", cycleCnt) + ".00." + String.format("%04d", frmCnt) + ".png");

    }

    // for stop motion
    for (int i = 0; i < frmRate; i++) {
      saveFrame("frames/" + String.format("%02d", cycleCnt) + ".01." + String.format("%04d", i) + ".png");
    }
    
    easePrev = easeRatio;

  }
  exit();
}


/**
 * Ease : hold easing functions.
 * reference : Robert Penner's Easing Functions (https://easings.net/)
 */
public interface Ease {
  public float ease(float _t);
}

public class InOutQuart implements Ease {
  public float ease(float _t) {
    return (_t < 0.5) ? 8.0 * _t * _t * _t * _t : 1 - pow(-2.0 * _t + 2.0, 4) / 2.0;
  }
}

public class OutQuart implements Ease {
  public float ease(float _t) {
    return 1.0 - pow(1.0 - _t, 4);
  }
}

public class InQuad implements Ease {
  public float ease(float _t) {
    return pow(_t, 2);
  }
}

public class InBack implements Ease {
  public float ease(float _t) {
    float c1 = 1.70158;
    float c3 = c1 + 1;
    return c3 * _t * _t * _t - c1 * _t * _t;
  }
}

public class OutBack implements Ease {
  public float ease(float _t) {
    float c1 = 1.70158;
    float c3 = c1 + 1;
    return 1.0 + c3 * pow(_t - 1.0, 3) + c1 * pow(_t - 1.0, 2);
  }
}

/*
Copyright (C) 2022- deconbatch

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/


 

QooQ