見出し画像

Processing/p5.js の noise() で遊ぼう! 後編

使いこなせれば Processing/p5.js での作品作りがぐっと面白くなる noise()。 今回、簡単な作例を交えながらこの noise() の特徴や使い方をご紹介しています。

前編では noise() の性質を解説しながら静止画を作っていきました。この後編では noise() を使ったアニメーションに挑戦します。

「ノイズ」にはいろいろな種類がありますが、ここでは Processing/p5.js の noise() 関数(パーリンノイズの Processing/p5.js での実装)を取り上げます。

後編は前編を読んでくださっている前提で書かれています。
また、読みやすさと実践的な理解を優先して「正確にはそうとは言い切れないんだけど」などはバッサリ省きました。
サンプルコードは Processing のものを掲載し、p5.js のコードは OpenProcessing へのリンクで示してあります。掲載のコードは CC0 で公開します。よろしければご自由にご利用ください。

この記事は全文無料でお読みいただけます。もしお気に召しましたら投げ銭お願いしますね。😉✨

画像1

時間という次元

noise() の引数の次元ですが、「次元」だからと必ずしも線、面、立体に使うと考えなくてもいいです。
例えば前編で出てきたこの例。

画像8

fill(noise(i * j * 0.01) * 360.0, 80.0, 90.0, 100.0);

2次元的拡がりを持った「面」としての作例を 1次元ノイズで描いています。

逆にこちらの例では「線」という 1次元的なものを複数描くために 2次元ノイズを使いました。

画像9

float y = noise(i, j) * 100 + j * h;

ここで noise(i, j) の i が線の長さ、j が線の本数という解釈が出来ます。
この縦に並んだ線が上から下に変化していく様子、これを上から下じゃなくて過去から未来だと考えてみて下さい。

これが noise() を使ったアニメーションです!
先程の noise(i, j) の i が一本の線の長さ、j が時間という次元という解釈になりました。

この時間という次元が noise() でアニメーションを作る上での軸になります

画像2

2次元ノイズでアニメーション

先程のこの静止画をアニメーションにしてみましょう。

画像10

使っていたのはこういう 1次元ノイズでした。

noise(i * j * 0.01)

これをアニメーションにするには時間の次元を加えて 2次元ノイズにするだけ!簡単!😀

noise(i * j * 0.01, t) // t は時間を表す変数

時間の経過は frameCount の数値の変化で表すことができます。

 float t = frameCount;

Processing/p5.js のアニメーションは基本パラパラ漫画で、一秒間に何枚の絵をパラパラするかでアニメーションの滑らかさが決まります。それを指定するのが frameRate()。
ひとまず一秒間に 10枚パラパラしましょう。 noLoop() をコメントにするのも忘れずに。

 // noLoop();
 frameRate(10);

全体のコードはこちらになります。動きをわかりやすくするためにマス目を大きめにしてみました。

/**
* noise() で遊ぼう! 後編
* アニメーション:引数の絶対値で結果は同じ
* 
* @author @deconbatch
* @version 0.1
* @license CC0 1.0 https://creativecommons.org/publicdomain/zero/1.0/deed.ja
* Processing 3.5.3
* 2020.11.07
*/

void setup() {
 size(640, 480);
 colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
 //  noLoop();
 frameRate(10);
 
 background(0.0, 0.0, 90.0, 100.0);
 stroke(0.0, 0.0, 0.0, 100.0);
 strokeWeight(1.0);
}

void draw() {
 int div = 20;
 float w = width * 0.5 / div;
 float h = height * 0.5 / div;

 float t = frameCount;
 
 translate(width * 0.5, height * 0.5);
 for (int i = -div; i < div; i++) {
   for (int j = -div; j < div; j++) {
     float x = i * w;
     float y = j * h;
     fill(noise(i * j * 0.01, t) * 360.0, 80.0, 90.0, 100.0);
     rect(x, y, w, h);
   }
 }
}

実行すると…

ちょっと変化が激しすぎるようですね。😅

そんなときは? そう! 引数の変化度合いを小さくすればいいですね。
時間に沿っての変化が激しいんだから、時間の次元の引数 t の変化度合いを小さくしてみましょう。

 float t = frameCount * 0.05;

うん!これでバッチリ!👍

このノイズにちょっと手を加えるとこんな感じに。なんかちょっとカッコいいかも!


画像3

3次元ノイズでアニメーション

続いて、2次元ノイズを使った前編の静止画作例をアニメーションにしてみましょう。

画像11

やり方は先程と同様、ノイズに時間の次元を加えて 3次元ノイズにするだけです。

	let s = noise(x * 0.005, y * 0.005, t);

出来上がりはこちら。四角形を円に変えてみました。

/**
* noise() で遊ぼう! 後編
* アニメーション:いかにも 2次元ノイズな図
* 
* @author @deconbatch
* @version 0.1
* @license CC0 1.0 https://creativecommons.org/publicdomain/zero/1.0/deed.ja
* Processing 3.5.3
* 2020.11.07
*/

void setup() {
 size(640, 480);
 colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
 //  noLoop();
 frameRate(10);

 background(0.0, 0.0, 90.0, 100.0);
 fill(220.0, 90.0, 60.0, 100.0);
 stroke(40.0, 60.0, 90.0, 100.0);
 strokeWeight(5.0);
}

void draw() {
 int divX = 16;
 int divY = 12;
 float w = width  / divX;
 float h = height / divY;

 float t = frameCount * 0.05;

 translate(w * 0.5, h * 0.5);
 for (int i = 0; i < divX; i++) {
   float x = i * w;
   for (int j = 0; j < divY; j++) {
     float y = j * h;
     float s = noise(x * 0.005, y * 0.005, t);
     ellipse(x, y, s * w, s * h);
   }
 }
}


コツがわかってきたところでもう一つ、今度は新ネタのアニメーションを作ってみます。

まず 1次元ノイズを使ってこういうものを作ります。

画像12

円を横に並べていくんですが、

画像13

円を一つ一つ描く度にノイズで角度を変えていきます。ノイズの 1次元目は円の個数です。すると滑らかに曲がる曲線になります。

画像14

次にこの線を複数にします。

画像15

ノイズが 1次元のままだとどの線も同じ形になりますね。そこで、2次元目に線の本数を入れてこんな感じに。

画像16

あとは 3次元目に時間を入れればキモいアニメーションの出来上がり!
いや、きんもー!😖

/**
* noise() で遊ぼう! 後編
* アニメーション:キモいやつ!
* 
* @author @deconbatch
* @version 0.1
* @license CC0 1.0 https://creativecommons.org/publicdomain/zero/1.0/deed.ja
* Processing 3.5.3
* 2020.11.07
*/

void setup() {
 size(640, 480);
 colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
 frameRate(24);

 fill(220.0, 90.0, 60.0, 100.0);
 stroke(40.0, 60.0, 90.0, 100.0);
 strokeWeight(5.0);
}

void draw() {
 int lines = 5;
 int steps = 16;
 float w = min(width, height) * 0.5 / steps;
 float t = frameCount * 0.01;

 background(0.0, 0.0, 90.0, 100.0);
 translate(width * 0.5, height * 0.5);
 for (int l = 0; l < lines; l++) {
   rotate(TWO_PI / lines);
   pushMatrix();
   for (int s = 0; s < steps; s++) {
     float theta = map(noise(l, s * 0.1, t), 0.0, 1.0, -0.5, 0.5) * PI;
     translate(0.0, w);
     rotate(theta);
     ellipse(0.0, 0.0, w, w);
   }
   popMatrix();
 }
}

3次元ノイズの部分はここ。 l が線、s が円、t が時間です。

float theta = map(noise(l, s * 0.1, t), 0.0, 1.0, -0.5, 0.5) * PI;

s は 0.1倍して滑らかな曲線になるようにし、l はわざと粗いままで線ごとに全く異なるノイズ値が得られるようにしています。
記事の最初の方で「次元といっても必ずしも線、面、立体に使うと考えなくていい」と言いましたが、これなど正にその良い例ですね!

それぞれの引数の変化度合いを変えてみてアニメーションがどう変わるかを見るのも面白いですよ。 極端に変えるとこうなりますけど、これは引数のどこを変えたかわかりますよね?


画像4

次元を超えろ!

ノイズで変化させるアニメーションを作るには、静止画の noise() に時間という次元を足せば簡単にできるということがわかりました。

…でも、待って下さい!
静止画の時点ですでに 3次元ノイズを使ってる場合はどうしたらいいんでしょう?

Processing/p5.js の noise() の引数は 3つまでしか指定できません。

時間を 4次元目というわけにはいかないですね。どうしましょう…?

わかんないので作りながら考えましょう!
まず 3次元ノイズを使った静止画を作ってみます。 以前作った「いかにも 2次元ノイズな図」を何層にも重ねるイメージで、層をノイズの 3次元目としてみます。

一層毎に色を赤、緑、青と変えて順番に重ねると、

画像17

画像18

画像19

こんな感じになります。

画像20

コードはこちら。アニメーションにすることを見越して書いていますが、まだノイズに時間を反映させてないのでピクリともしません。

/**
* noise() で遊ぼう! 後編
* アニメーション:2次元ノイズの図を重ねる
* 
* @author @deconbatch
* @version 0.1
* @license CC0 1.0 https://creativecommons.org/publicdomain/zero/1.0/deed.ja
* Processing 3.5.3
* 2020.11.07
*/

void setup() {
 size(640, 480);
 colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
 frameRate(24);

 noStroke();
}

void draw() {
 int divX = 40;
 int divY = 30;
 int divZ = 3;
 float w = width  / divX;
 float h = height / divY;
 float t = frameCount * 0.02;

 background(0.0, 0.0, 90.0, 100.0);
 translate(w * 0.5, h * 0.5);
 for (int z = 0; z < divZ; z++) {
   fill(z * 360.0 / divZ, 60.0, 80.0, 100.0);
   for (int i = 0; i < divX; i++) {
     float x = i * w;
     for (int j = 0; j < divY; j++) {
       float y = j * h;
       float s = noise(x * 0.005, y * 0.005, z);
       if (s < 0.5) {
         ellipse(x, y, s * w * 2.0, s * h * 2.0);
       }
     }
   }
 }
}

ノイズのところはこのように静止画の時点で 3次元使い切っています。

float s = noise(x * 0.005, y * 0.005, z);

さて、これにどう時間を組み込みましょう?
例えば 1次元目に時間を加算してみましょうか?

noise(x * 0.005 + t, y * 0.005, z);

うーん、確かに時間で変化はしますが、横スクロールしてるだけに見えますね。
x + t だから時間が変化してるというよりも x が増加(移動)してるという感じになります。

y 方向も同様ですから、この方法を使えば上下左右斜め自在に移動させることができますね。これはこれで用途によっては使えそうです。

地図上を動き回ってるイメージ。

さっきのキモいアニメーションで順番に同じ動きをするようにとか。

脱線しちゃいました。
時間の次元が足りない問題、これだとどうでしょう?

noise(x, y, z + t)

あ!これいい感じ!😀
これなら z 方向に移動なので 2次元平面上の描画がノイズで変化してるように見えます。3次元空間の中で、2次元平面の板をそのまま手前に引っ張って来てるイメージです。

でもこれって平面だからいいけれど、元が立体の描画だったら先程の横スクロールと同じように単なる z 方向の移動に見えちゃうんですよね。
やはり x, y, z は描画のノイズに使って、時間は変化の次元として独立させたい…となると…
もしかして、こう?

noise(noise(x, y, z), t)

うーん、横移動的ではない変化はしてますが、なんだか雑いというか、ちょっと不自然な動きになってしまいますね。
noise(x, y, z) の部分が固定で時間に沿っての変化がないから?

それならこれでどう!?

noise(noise(x, y, t), z)

なんならこれ 2次元ノイズだからここにさらに時間の次元を加えてこれでどうだ!

noise(noise(x, y, t), z, t)

…正直なにがなんだかよくわかんなくなってきちゃいました。

実のところ、4次元目に時間を持ってきたい場合の一般化された「これ!」といった解決策を私は持てていません
今回の作例では noise(x, y, z + t) で十分だったように、目的によって解決策は違ってくるんじゃないかと思います。

今回は noise() の入れ子を試しましたが、別の何らかの関数を使うという方法も考えられると思います。

noise(func(x, y, z), t)

何か良い方法をご存知でしたらぜひ教えて下さい。

画像5

たまにはこんな使い方

【ランダム値の配列代わりに】

noise() の性質の一つに

・引数の絶対値が同じなら結果も同じ値を返す(ただし、一回の動作の中で)

というものがありました。

同じ引数ならプログラム中のどこででも同じ値を返してくれるし、引数の変化度合いを大きくすればほとんどランダムな値が返ってくるので、この性質を利用してランダム値の配列代わりに使うということもできます。
CPU 時間の無駄遣いですけどね。


【毎回同じ noise() 値が欲しい】

「一回の動作の中で」という条件は、裏を返すと「別の動作では得られる noise() 値が異なる」ということになります。
同じコードでも実行の度に違う noise() 値が得られるというのは、実行の度に異なる描画結果が見られるということで面白いのですが、場合によっては不都合なことがあります。例えば作品作りの途中でパラメータの調整をしているとき、毎回同じ noise() 値が得られたほうがパラメータ変更前後の違いを比較しやすいなどです。

そんなときに使えるのが noiseSeed()。

setup() 中で

noiseSeed(0);

などとしておくことで何回実行しても同じ noise() 値を得ることができるようになります。
noiseSeed() の引数の値が違えば得られる noise() 値も変わりますから、他の noise() 値での結果も試したいとか、一回の実行の中で違う noise() 値を得たいという要望にも応えられます。


【もっと大きく変化してほしい】

noise() を使っていると、実行の全体を通して大きく変化する場合と、ちょっとしか変化しない場合があります。

画像21

画像22

0.0 から 1.0 の間を大きく変化しないと意味がないという作品の場合、あらかじめ取得される noise() 値の最小値と最大値を得ることで 0.0 から 1.0 の間に正規化するという方法もあります。
滑らかさとの両立がちょっと難しくなったりしますが。


【noise() 値を循環させたい】

開始と終了の noise() を同じにして値を循環させたいという場合もあります。

こうじゃなくて、

画像23

こうしたい。

画像24

値が循環するようなノイズ値はこちらの方法で得ることができます。


【繰返しに注意】

noise() の性質として、長期的には同じ値の繰り返しになるというものがあります。

noise() は滑らかにランダムな変化をしていくのですが、それをずーっと続けていくと同じ値の繰り返しが現れます。これは左上から右下に粗めに引数の値を増加させていったものです。

noise(x, y);

こう見ると普通。

画像25

面積を倍に広げてみると?

画像26

繰返しがはっきり現れてますね。
これは通常の利用ではそんなに気にすることはありませんが、大きな作品やきめの細かい作品を作るときには覚えといたほうがいいかもしれません。

画像6

もっと深く

今回説明しませんでしたが、 noise() 値の性質を調整する noiseDetail() というものもあります。

noise() が返す値は偏ってるというお話しや、

自前で noise() を実装してどうやってノイズを生成しているかを探ったお話しも。noise() を理解する上でこれ以上の方法はないでしょう。

画像7

noise() アニメーション作ってみよう!

前編で作った自分の作品に時間の次元を加えてアニメーションにしてみましょう。

お題が見つからない方は、面上を動き回る点のアニメーション(ランダム・ウォーク)を 1次元ノイズを使って作ってみるとかどうでしょう?
あるいは 1次元ノイズで大きさが変化する円を描いて、それができたら 2個 3個とその円を増やしていって 2次元ノイズで変化させてみるとか。毎回 background() で消去してもいいし、消去をやめたらどうなるかな?とかね。

こちらのツイートのスレッドにいくつか作例を掲載しました。よろしければ参考までに。

ぜひいろいろ作ってみてお楽しみください。
私も自分でも作ってみたくなってきちゃった…😆

画像27

こんなの作っていただきました!

ky0ju(@ky0ju_art)さんの作品です。 noise() を使っての波紋をつぶやきProcessing で表現してくださいました。🙂

DCF(@ocello3)さんの作例です。画像を元にノイズで変化を加えるというイメージプロセッシング。イメージプロセッシング私も大好きです!😀

Naoto HIÉDA(@naoto_hieda)さんの作例。これまた Processing/p5.js の noise() じゃないノイズでの作例!😆 こちらのノイズはなんと引数が 4次元まで利用可能です。😳


この先には noise() を使用したちょっと大きめの作品画像があります。この記事がお気に召しましたら投げ銭お願いしますね。😉✨

ここから先は

0字 / 2画像

¥ 100

この記事が面白かったらサポートしていただけませんか? ぜんざい好きな私に、ぜんざいをお腹いっぱい食べさせてほしい。あなたのことを想いながら食べるから、ぜんざいサポートお願いね 💕