アニメーションを作るとき、「イージング」と呼ばれる加速度をつけた緩急のある動きをつけてあげると、作品の魅力がぐっと増します。
イージング有り/なしの動き 🙂
— deconbatch (@deconbatch) January 22, 2022
上がなしで、下が easeInCubic のイージング。#p5js #creativecoding pic.twitter.com/xGbyDXYp5i
上がイージングなし、下がありの例です。
このイージングを連続させて贅沢に使う「階段状イージング」と、それを使った作例を紹介します。
👉 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' での作例
easeInCubic の作例 😀
— deconbatch (@deconbatch) January 22, 2022
function easeInCubic(x) {
return x * x * x;
}#p5js #creativecoding pic.twitter.com/XaKBZG7N3n
// 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 一個のイージングだとこう。
easeInOutCubic イージングの繰り返し 😌#p5js #creativecoding pic.twitter.com/eBQkm1XQOX
— deconbatch (@deconbatch) January 22, 2022
3個のイージング easeInOutCubic、 easeOutQuad、 easeInCubic で作った階段状イージングだとこうなります。
easeInOutCubic, easeOutQuad, easeInCubic の一連のイージングで一回転のパターン。🙂#p5js #creativecoding pic.twitter.com/d54IRZIycX
— deconbatch (@deconbatch) January 22, 2022
階段状イージングは、イージング関数を一個使うよりも変化を出せます。さらに、関数をランダムに組み合わせたり、順番を途中で変えたりすることで、実行のたびに新鮮な動きを楽しめるかもしれません。
階段状イージングを盛り込んだ Processing 作例
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/>
*/