見出し画像

アルちゃんのprocessing部~その1~

コンピューターに勝手に絵を描いてもらうことを考えましょう。
コンピューター上の絵というものが全てパラメータによって制御しうるとするならば、コンピューターに勝手に絵を描いてもらうためには勝手に動いてくれるパラメータが必要です。
人類がボーっとしているにも関わらず、一方で自動で勝手に動いてくれる最良のパラメータは時間であります。コンピューター上ではプログラムの起動時間などとして取得することができるでしょう。processingではmillis(), second()などです。

これらは原理的にはパソコンの中に封印された水晶がぷるぷる震える回数を数えておるわけですが、基本的には増える一方、単純に増加するパラメータです。
どのくらいまで増えるかというと、最大でだいたい2の32乗から64乗といった所でしょう。数にすると40億以上です。millis()ならば400万秒、1000時間。だいたい1か月以上は延々とカウントアップし続けます。

剰余%

単純に増加するパラメータを使いやすく加工しましょう。
剰余をとることで、単純に増加するパラメータは一定の範囲内でループします。剰余とは、割り算の余りを返す演算です。それが正の値ならばいかなる数であっても、1000の剰余をとれば0から1000未満の値に収まります。
単純に増加するパラメータに対して剰余をとれば、時間に対して一定範囲の値を繰り返し出力するパラメータとなります。

ところで負値の剰余は実装次第になりますでしょう。あんまりやらんようにしましょう。

//0から999の範囲の数を返す
float millis_loop()
{
 return millis() % 1000;  
}

float millis_loop(float divisor)
{
 return millis() % divisor;  
}

0から1

0から1というのはとても使いやすいパラメータです。始点と終点が与えられたならば、その中間は全て0から1で表現できます。
作り方は範囲で割ることです。例えば0から1000までの値を1000で割れば、その出力は必ず0から1です。

float millis_frac(float resolution)
{
 return (millis()%resolution)/resolution; 
}

これはつまり、ある範囲を1とみなした時に入力値をいくつとみなすかという比の問題です。値の範囲が0から始まるならば上記のように簡単ですが、負値も含むなら下記のようになるでしょう。ちなみに試してません。

//0から1に変換
float value2ratio(float value, float min, float max)
{
    float range = max-min;
    return (value-min)/range;
}
//0から1の比を、元の値に戻す
float ratio2value(float ratio, float min, float max)
{
    float range = max-min;
    return (range*ratio)+min;
}

sin

値が時間と共にカウントアップするパラメータは作れましたが、このパラメータは最大値を超えるといきなり0ないし最小値に戻ります。これは結局、源泉掛け流し温泉、あるいは流しそうめんのようなパラメータです。
場合によっては、パラメータに対してある範囲内で反復横跳びしておいて欲しい時があります。そうした時はsin,cosを使います。

sin,cosは0から2*PIの入力に対して-1から+1の値を出力します。時間に応じて出力を得る、最も簡単な方法は以下の要領でしょう。

sin(millis());

出力を0から1に収めたければ出力を2で割って0.5を足しましょう。

(sin(millis())/2)+0.5;

時間に応じて0から1を取得し、sinを制御してみましょう。

float sin_frac(float resolution)
{
 return (sin(2*PI*millis_frac(resolution))/2)+0.5;
}

ここまでまとめ

void settings()
{
 size(500, 500);
}

float m_value = 0;
float s_value = 0;
void draw()
{
 background(255);
 fill(0);
 
 m_value = millis_frac(resolution);
 s_value = sin_frac(resolution);
 
 ellipse(200,100*m_value+150,100,100);
 ellipse(350,100*s_value+150,100,100);  
 
 text(str(m_value), 10, 10);
 text(str(s_value), 10, 20);
 delay(100);
}

float resolution = 100;
float millis_frac(float resolution)
{
return (millis()%resolution)/resolution; 
}

float sin_frac(float resolution)
{
return (sin(2*PI*millis_frac(resolution))/2)+0.5;
}

疑似乱数

乱数は究極的には、なんら前後に脈絡のない値の羅列ということになります。コンピューター上でそういうものを作るのは大変に難しいので、プログラム上でランダム性を実現したい時は疑似乱数が使われます。
疑似乱数は本物の乱数ではなく、入力を機械的に処理する単なる関数であるため、同じ入力(seed)に対しては同じ出力を返すという性質を持ちます。そのため実行毎に異なる値を取得したい場合、最も単純なのは以下のような形です。

  random(millis());

これの欠点はプログラム起動時間を測られると値が予測されるということで、ゲームなどではちゃんとした対応が必要になることがあります。

ノイズ

疑似とはいえ、疑似乱数はそれなりに脈絡のない値を返します。状況によっては、もう少しなだらかな変動が必要になる場合があります。そういう時にノイズを使います。

補間

ノイズを実現するアプローチの一つは、疑似乱数で生成した値のスキマを関数で埋めることです。同じことですが、フィルター処理の要領で凸凹しすぎた疑似乱数値をなだらかにする手法も考えられます。こうした系統のアプローチをバリューノイズなどと言ったりします。
欠点は関数の継ぎ目が目立ったり、それをなくそうとする処理が重たくなりがちなことでしょう。

パーリンノイズ1D

パーリンノイズは、複数の乱数値からベクトルを生成し、それを元手に値を補正します。軽量かつなめらかであり、人類を感動させるこの世界の数々の現象が、所詮ただ一瞬のノイズの揺らぎに過ぎないということを分からせてくれる恐るべき魔術であります。
欠点は元の論文が分かり辛く、そもそもパーリンノイズが主題でもなく、後に説明を試みた人が実に説得力のあるバリューノイズの説明を間違えてパーリンノイズであるとしてしまい、それが広がって、さらに本人が改良した新型パーリンノイズが混ざり込んできて、大体のリンク先がリンク切れでエラー。

ノイズの論文だけにノイズが多くなってるということです。

バリューノイズとパーリンノイズは実装する側から見ると異なりますが、使う側から見たら似たようなもんです。入力は何でもよく、出力は0から1。同じ入力に対しては同じ出力。ある入力の周辺の入力に対する出力は、それなりに似たような出力を吐く。

最も簡単な使用法は以下の要領でしょう。

noise(millis());

時間に応じて規則的にループ、あるいは反復するパラメータからノイズを得るには

//ループはするが抵抗する
noise(sin_frac(resolution));

//反復はするが抵抗する
noise(millis_frac(resolution));

//0-1を規則的にループ
float millis_frac(float resolution)
{
return (millis()%resolution)/resolution; 
}

//0-1を規則的に反復
float sin_frac(float resolution)
{
return (sin(2*PI*millis_frac(resolution))/2)+0.5;
}

ランダムだがノイジーなベクトルを生成する場合はどのようにするのが良いでしょうか。パラメータの源泉は時間1つしかなく、必要な値は2つです。
同じ入力をすると同じ値が返ってきてしまうので、入力の棲み分けをする必要があります。最も単純なのは下駄を履かせることでしょう。
係数を掛けるでも良いですが、結果はちょっとズレるということになります。

 float x = noise(millis()/resolution);
 float y = noise(millis()/resolution+offset);

1次元の範囲内でナワバリのごとく、入力の範囲を決めてやれば、それなりに明確な差異が2つの値には現れるはずです。1次元の場合、始点と終点を適切に接続しなければ、ループさせると値が飛びます。なめらかな出力を簡単に得るためには範囲内で反復させる必要があります。

 float x = noise(ratio2value(sin_frac(resolution),1000,1100));
 float y = noise(ratio2value(sin_frac(resolution),200,300));
 
//再掲
//0から1に変換
float value2ratio(float value, float min, float max)
{
   float range = max-min;
   return (value-min)/range;
}
//0から1の比を、元の値に戻す
float ratio2value(float ratio, float min, float max)
{
   float range = max-min;
   return (range*ratio)+min;
}

サンプル

void draw_noise1d_vec()
{
 float resolution = 200000;
 float offset = 5153;
 //float offset = 5153345;
 
 float center_x = 100;
 float center_y = 100;
 float x_max_length = 200;
 float y_max_length = 200;

 //float x = noise(millis()/resolution);
 //float y = noise(millis()/resolution);
 
 //float x = noise(millis()/resolution);
 //float y = noise(millis()/resolution+offset);
 
 float x = noise(ratio2value(sin_frac(resolution),1000,1100));
 float y = noise(ratio2value(sin_frac(resolution),200,300));
 
 ellipse(center_x + x_max_length*x, center_y+y_max_length*y, 10, 10);
 line(center_x, center_y, center_x + x_max_length * x, center_y + y_max_length * y);  
}

パーリンノイズ2D

2次元のパーリンノイズは2値を入力し、0から1を返します。
1Dの時は左右に対する滑らかさしかありませんでしたが、2Dでは上下、あるいは前後に対するなめらかさを持ちます。マインクラフトのマップを生成するなら、この情報は不可欠です。
入力の1つを固定したなら、それは1Dパーリンノイズと同じ様なものになります。ただし出力の形状は異なりますので、以下のようにすれば単純になめらか2値を得られます。十分な距離を取れば、必要なだけの掛け流しノイズ値が得られるでしょう。

//固定する定数はなんでもよい
 float x = noise(millis()/resolution,10);
 float y = noise(10, millis()/resolution);
 
 //これは近すぎる例
 //float x = noise(millis()/resolution,11);
 //どれくらい離せば棲み分けできるかは不明
 //float x = noise(millis()/resolution,100);

1次元パーリンノイズでは関数を調整しなければループする時に断絶が発生しますが、2次元パーリンノイズでは閉曲線の軌道上で値を取得すれば容易になめらかループを実現することができます。

(x,y) = curve(t);
ずっとなめらかvalue = noise(x,y);

閉曲線ならなんでもよいので、ベジェやスプラインの始点終点を繋いだものでもいけるでしょう。2Dノイズマップの山の頂点でぐるぐるまわったり、鋭角に曲がったりすれば違和感を与えることもできるでしょう。多分。

普段使いする分には円か楕円で十分ではないでしょうか。

//tは0-1
PVector Circle_t(PVector center, float radius, float t)
{
   float x = radius * cos(2*PI*t) + center.x;
   float y = radius * sin(2*PI*t) + center.y;
   PVector new_vec = new PVector(x, y);  
   return new_vec;
}

//tは0-1
PVector Ellipse_t(PVector center, float major_axis, float minor_axis, float t)
{
   float x = major_axis * cos(2*PI*t) + center.x;
   float y = minor_axis * sin(2*PI*t) + center.y;    
   PVector new_vec = new PVector(x, y);
 
 return new_vec;  
}

円の中心点と半径を設定しておけば、必要なだけのなめらかループノイズ値が得られます。


















この記事が気に入ったらサポートをしてみませんか?