こんにちは。NUMです。
最近は本当に暑いですね、もう外に出たくなくてずっとProcessingで遊んでいる毎日です笑
8/9 ~ 8/12にかけてアジアのジェネラティブアートの祭典、dialog()が始まりましたね。
こんな大規模な展示はそうそう無いので行きたかったですが、東京は遠いので行けずに残念です、、
Youtubeでトークのアーカイブを聞いてますが、ますます行きたくなってしまいました笑
東京にいらっしゃる方はジェネラティブアートに興味がなくてもぜひ行ってみて下さい。
もしかしたらハマっちゃうかもしれないですよ!
今回は我らの師匠、ダニエル・シフマン先生のマーブリングの動画を参考に、勉強した内容を記事にまとめていきます。
目次
Marbling
マーブリングとは、薄く貼った水に絵の具を垂らして、水面にできた模様を紙に写しとる技法です。
保育の現場でも子供の色彩感覚、想像力、紙に慎重に色を移しとる際の力加減などを育むのに効果的なので利用されています。
また、偶然性から生まれる模様を楽しむ技法でもあるのでジェネラティブアートに共通するものがありますね。マーブリングとは、薄く貼った水に絵の具を垂らして、水面にできた模様を紙に写しとる技法です。
概要
今回のマーブリングアルゴリズムは以下の考え方を前提とします。
円形のインクを追加する。
既に垂らしていたインク全てに対して形状を再計算する。
イメージとしてはインクを垂らすと、追加インクの半径方向※1にその他インクの点全てが移動する感じです。 ※1 中心から外側へ向かう直線的な方向で、円周のどの点に向かうかによらず、常に中心から外へ伸びる直線に沿った方向
数式
概要の2では以下数式を使用してその他インクの形状を再計算します。
P:その他インクの形状点の一つ
C:点Cの座標。新しいペイントドロップの中心点。
(P - C):点Cから点Pへのベクトル。
P、C間の距離と追加インクの大きさに比例して、Pの移動距離が増加する式。
1:P、C間の影響が無い場合、Pの位置を変更させないために存在している。
|P - C|^2 :PとCの間の距離(ユークリッド距離)
r^2 :追加インクの半径の二乗
|P - C|^2が分母にあることで、P、C間の距離が大きいほど分母が大きくなり、分子のr^2の値が小さくなります。その結果、Pの移動距離は小さくなります。
数式全体としては、インクが追加された時、Pが追加インクに応じてどのように移動するかを計算するためのもので、以下解釈で良いと思います。
P、C間の距離が小、追加インクの半径が大:Pの移動距離は大きい P、C間の距離が大、追加インクの半径が小さい:Pの移動距離は小さい
実装
Drop:
/**
* Drop
* 追加時は円形のオブジェクトを表現し、その頂点の位置を管理を保持
* 他のDropオブジェクトとの相互作用による形状変化
*/
class Drop {
float r; // 円の半径
PVector center; // 円の中心座標
int detail = 360; // 円を構成する頂点の数
PVector[] vertices = new PVector[detail]; // 頂点位置座標
/**
* 指定された座標と半径で新しいDropオブジェクトを作成
*
* @param x 円の中心のx座標
* @param y 円の中心のy座標
* @param r 円の半径
*/
Drop(float x, float y, float r) {
center = new PVector(x, y);
this.r = r;
setVertices();
}
/**
* 円を構成する全ての頂点を設定
* 各頂点は円周上に均等に配置され、中心からの距離は半径に基づいて計算
*/
void setVertices() {
for (int ang = 0; ang < detail; ang++) {
PVector v = PVector.fromAngle(radians(ang));
v.mult(this.r);
v.add(this.center.x, this.center.y);
vertices[ang] = v;
}
}
/**
* 他のDropオブジェクトとの相互作用により、このDropの頂点位置を変形させる
*
* @param other 他のDropオブジェクト
*/
void marble(Drop other) {
// 他のDropオブジェクトの中心座標、半径を取得
PVector otherC = other.center;
float otherR = other.r;
// 他のDropオブジェクトの全頂点に対して移動方向、距離を計算
for (int i = 0; i < detail; i++) {
// 頂点座標を取得し、コピーする
PVector v = vertices[i];
PVector p = v.copy();
p.sub(otherC); // CからPへのベクトル
float m = p.mag(); // P、Cベクトル間の大きさ
// P、C間の距離と追加インクの大きさに比例して、Pの移動距離が増加する
float root = sqrt(1 + (otherR * otherR) / (m * m));
p.mult(root);
// Cベクトルに計算結果を足し合わせて頂点座標を更新
p.add(otherC);
v.set(p);
}
}
/**
* 描画
*
* @param c 描画する色
*/
void drawDrop(color c) {
fill(c);
noStroke();
beginShape();
for (int i = 0; i < detail; i++) {
PVector v = vertices[i];
vertex(v.x, v.y);
}
endShape(CLOSE);
}
}
Main:
int addDropNum = 200; // 追加インク数
color[] palette = {
color(67, 105, 161),
color(187, 141, 30),
color(147, 28, 12),
};
void setup() {
size(850, 850);
background(0);
pixelDensity(displayDensity());
smooth();
ArrayList<Drop> drops = new ArrayList<Drop>(); // 全インク
for (int i = 0; i < addDropNum; i++) {
// 追加インク
Drop d = new Drop(int(random(0, width)), int(random(0, width)), int(random(10, 50)));
// 追加インクをもとに、全ての他インクの頂点座標を更新
for (Drop other : drops) {
other.marble(d);
}
// インクを追加
drops.add(d);
}
// インクの描画
for (Drop other : drops) {
other.drawDrop(palette[int(random(0,3))]);
}
}
まとめ
今回はマーブリングについての記事を書いてみました。
本来、マーブリングのような流体計算は計算量が多く、パフォーマンスが悪くなりがちですが、このアルゴリズムは非常にシンプルな計算式で流体の表現をすることができます。
マーブリングの核となる数式について噛み砕いて解説したので、皆さんも原理を理解していただける内容かと思います。ジェネラティブアートは突き詰めていくと数式の可視化になりますが、難解な原理を理解できた時の快感も楽しみの一つだと思います。
ぜひ、模様の美しさだけでなく、理解する行為も楽しんでいただけると嬉しいです!
Mimicry
この作品はマーブリングを使って生物の習性である「擬態」を表現してみました。
球が地面の液体の色と同化していく様相を表しています。
人の個性は、与えられた様々な環境に染まることで作られると思います。個性を出すことに執着せず、自分の今までの経験の中で好きと思えるものを追求すれば、おのずと個性は出てくると思います。
最後まで読んでいただきありがとうございました!
参考
Comments