無粋な街灯が星を隠す。夜は明けず、まだ鳥も鳴かぬ暗がりの中、手探りで靴を履き歩き出す。
曉天に訪れる美の瞬間を求めて、曙光に明るむ丘へ登れば、西の空は薄青の光を纏って早々に恭順の意を示し、ただ天空だけは青黒く朝に抗っている。
星々はひとつ、またひとつ夜闇の奥へと去り、浮かぶ雲は明るさをゆっくりと増し、紫、紫がかった灰、暗い灰色、薄く青みがかったやや明るい灰色と複雑な色でその形を見せていく。
南の金色のグラデーションが来光を無邪気に祝う中、北の空は上品な薄紫のグラデーションで静かに孤高の時を過ごす。
やがて東の地平はオレンジに染まり、杏、薄紫、水色、より濃い水色で仕上げた贅沢なグラデーションを打ち開く。
色彩の妙味に、今日もまた私は目を覚まされるのだ。
いくつもの色が、複雑に気まぐれに緻密に織りなすグラデーション。このグラデーションをどう作ろう。
👉 Read this article in English.
この記事は、「Processing Advent Calendar 2022」2日目の記事です。
昨日の記事は you(@youtoy) さんの「@yuruyurau さんの #つぶやきProcessing のプログラムを短縮されてない #p5js のプログラムにした結果と過程の話」でした。摩訶不思議な描画を行うコードの計算過程はわかってきたものの、計算部分と描画結果がまだ完全には結びつかないとのこと。これは続きが気になりますね。
半透明な色を重ねる
p5.js/Processing で半透明な色を重ねると、重なった所の色が混ざって元とは違う色になります。
複雑に色が変化するグラデーションを作るため、今回はこの半透明の色を重ねる方法を試してみます。
ところで、同じ組み合わせの色を重ねたとしても、blendMode() によって色の混ざり方は変わってきます。
blendMode(SCREEN) の場合、色を混ぜていくと光を重ねて当てたようにどんどん明るくなっていきます。
blendMode(SUBTRACT) では、絵の具を混ぜるときに近く、色を混ぜると暗くなります。
半透明の色を重ねる場合、デフォルトの blendMode(BLEND) が使いやすそうです。描画の順番で結果が変わるという面白さもあります。
色を徐々に変化させるには?
色をただ重ねただけではグラデーションになりません。色を徐々に変化させていく必要があります。
今回は透明度を調整することで色の濃さに変化をつけようと思います。
線形に変化
こちらは、濃さを均等に変化させた例です。
頂点が尖ってるせいか、変化がきつく感じられます。
sin カーブ
頂点部分がゆるやかに変化する sin カーブを使ってみましょう。
ゆっくりとした変化で、グラデーションっぽさが出ています。しかし、端の部分がパツンと切れているように見えるのが気になります。
正規分布
「もしかして自然の変化って正規分布だったりしないだろうか?」ということで、いわゆるベルカーブで変化させてみました。
端の変化具合がなめらかになりました。しかも、ベルカーブを使えば、変化具合をパラメータ(平均と分散)で容易にコントロールできるという利点もあります。
グラデーションの作例
ベルカーブのグラデーションを使った一例です。
半透明の色を重ねているので後ろが透けます。ということは、背景に色を置くことで絵作りの幅を広げることができます。
今回の作例では、上下に並べた異なる色を背景に置くことで絵作りをしました。
p5.js のサンプルコード
/**
* Gradation of Blue.
*
* @author @deconbatch
* @version 0.1
* @license CC0
* p5.js 1.5.0
* created 2022.12.02
*/
function setup() {
createCanvas(640, 1000);
colorMode(HSB, 360, 100, 100, 100);
noSmooth();
noLoop();
blendMode(BLEND);
const yL = 0.7;
// background
fill(220, 90, 30, 100);
rect(0, 0, width, height * yL);
fill(220, 60, 90, 100);
rect(0, height * yL, width, height);
stroke(0, 0, 100, 100);
line(0, height * yL, width, height * yL);
// gradation
gradateY(200, yL, 0.02, 80, createVector(55, 10, 100));
gradateY(200, 0.1, 0.03, 80, createVector(260, 90, 5));
}
/**
* gradateY : draw alpha gradation with the probability distribution function on Y-axis.
* _divNum : divide number.
* _mean : mean value of the probability distribution.
* _vari : variance value of the probability distribution.
* _alpBase : alpha value on the top of the probability distribution curve.
* _color : x = hue, y = saturation, z = brightness
*/
function gradateY(_divNum, _mean, _vari, _alpBase, _color) {
const divW = height / _divNum;
const topV = 1 / sqrt(TWO_PI * _vari);
noStroke();
for (let i = 0; i < _divNum; i++) {
let divRate = map(i, 0, _divNum, 0, 1);
let density = exp(-pow(divRate - _mean, 2) / (2 * _vari)) / sqrt(TWO_PI * _vari);
fill(_color.x, _color.y, _color.z, _alpBase * density / topV);
rect(0, i * divW, width, divW);
}
}
関数 gradateY() の引数が多くて、どれがどれだか分かりづらいです。色のパラメータを PVector にすることで、少しでも引数の数を減らして判別を容易にしようとしています。
同じものを Processing のコードで
/**
* Gradation of Blue.
*
* @author @deconbatch
* @version 0.1
* @license CC0
* Processing 3.5.3
* created 2022.12.02
*/
public void setup() {
size(640, 1000);
colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
noSmooth();
noLoop();
blendMode(BLEND);
float yL = 0.7;
// background
fill(220.0, 90.0, 30.0, 100.0);
rect(0.0, 0.0, width, height * yL);
fill(220.0, 60.0, 90.0, 100.0);
rect(0.0, height * yL, width, height);
stroke(0, 0, 100, 100);
line(0, height * yL, width, height * yL);
// gradation
gradateY(200, yL, 0.02, 80.0, new PVector(55.0, 10.0, 100.0));
gradateY(200, 0.1, 0.03, 80.0, new PVector(260.0, 90.0, 5.0));
}
/**
* gradateY : draw alpha gradation with the probability distribution function on Y-axis.
* _divNum : divide number.
* _mean : mean value of the probability distribution.
* _vari : variance value of the probability distribution.
* _alpBase : alpha value on the top of the probability distribution curve.
* _color : x = hue, y = saturation, z = brightness
*/
public void gradateY(int _divNum, float _mean, float _vari, float _alpBase, PVector _color) {
float divW = height * 1.0 / _divNum;
float topV = 1.0 / sqrt(TWO_PI * _vari);
noStroke();
for (int i = 0; i < _divNum; i++) {
float divRate = map(i, 0, _divNum, 0.0, 1.0);
float density = exp(-pow(divRate - _mean, 2) / (2.0 * _vari)) / sqrt(TWO_PI * _vari);
fill(_color.x, _color.y, _color.z, _alpBase * density / topV);
rect(0.0, i * divW, width, divW);
}
}
まとめ
この記事のタイトル絵は、朝の散歩時に感じた印象を、コード例を応用して絵にしたものです。苦手だった「作りたいと思ったものを作る」が少しは出来た気がします。
今回、コード例では Y 軸方向のグラデーション変化のみを用いました。X軸方向の変化や、グラデーションの範囲限定や回転など、いろいろ工夫の余地、つまり楽しめる余地がたくさん残っています。ぜひ工夫して楽しんでいただければと思います。
「Processing Advent Calendar 2022」3日目は、やまたみ(@ymntmtmy)さんの記事「クリエイティブコーディング1年目のまとめ」でお楽しみください!
他の人がクリエイティブ・コーディングのどういうところを楽しいと感じてらっしゃるのか、どういう考え方で作品作ってらっしゃるのかを見れて楽しいですよ。