はじめに
空と雲をテーマにいくつか つぶやき Processing をしてきました。 現在のところ、ひとつの到達点として以下の作品があります。
青空と雲:
#つぶやきProcessing
— Koji Saito (@KojiSaito) March 13, 2020
def setup():size(550,280);noStroke()
C=circle
def draw():
f=frameCount*.01;background(99,150,255)
for i in range(15000):x=i%150;y=100-i/150;t=noise(x*.01+f,y*.02+f);x*=4;y*=3;t=pow(t,8)*8;fill(100,t*30);C(x,y,10);fill(255,t*255);C(x-20,y-15,10) pic.twitter.com/xEcYlMsUDf
夕焼け空と雲:
#つぶやきProcessing 夕焼け雲
— Koji Saito (@KojiSaito) May 21, 2020
def setup():size(500,400);noStroke()
def draw():
for iy in range(200):
for ix in range(250):
d=dist(ix,iy,150,-100)
t=noise(ix*.01+frameCount*.02,iy*.05)
fill((t+.7)*pow(d,5)*2e-10,(t+.5)*d*d*.002,(t+.2)*d*.6)
rect(ix*2,iy*2,2,2) pic.twitter.com/XG96n83xqu
このページでは、これらの作品にたどり着くまでの経緯や、 それぞれの作品についてのコメントをまとめています。
空と雲というモチーフについて
私の場合、空と雲というテーマは、つぶやき Processing で始まったものではありません。 それは 20 代の中頃から「何となく」あったものでした。 多分、恐ろしく広い空と美しい雲が展開する場所で 生まれ育ったことも影響しているのだと思います。 そういえば、10 才の時の自由研究は雲の観察だった気がします。
30 代になってからは、CG で何とか表現できないものかと試行錯誤していました。 A.J. Preetham らの A Practical Analytic Model for Daylight - https://www2.cs.duke.edu/courses/cps124/spring08/assign/07_papers/p91-preetham.pdf を読み込んで、なんとか実装していたりしたのもこの頃だった気がします (注:結局、この論文の情報だけでは実装できず、周辺の様々な文献を 読み込むことでなんとか実装することができました)。
なかなか成果が出ないので、計算機で簡単に空や雲を描くことは無理な話なのかな、 と思うようになっていきました。40 代の頃だったと思います。
しかし、50 代の現在、たまたま出会った つぶやき Processing を通じて、 憑きものが落ちたというか、結果として、腑に落ちる表現ができたという状態となりました。
このページは、つぶやき Processing という活動を通じて、 どのように納得できる表現にまでたどり着いて行ったのかを コメントを付けつつまとめていきたいと思います。
最初のアプローチ - 飛行機雲 contrail (2020.2/29)
最初にこのテーマで つぶやき Processing しようとしたのは、 飛行機雲 contrail という作品でした。 この作品の元ネタは、以前 ICC で見た、 浅野耕平さんの作品 Lines という作品です:
https://www.ntticc.or.jp/ja/archive/works/lines/
Lines は、鑑賞者が良く見ようと近づいたりすると、 飛行機雲が消えてしまうという、逆説的なインタラクティブアートです。 その発想も非常に面白いのですが、それに加えて、 空の青さと飛行機雲という組み合わせがなんとも素敵な作品です。
この空と飛行機雲の組み合わせを、なんとか つぶやき Processing で 再現できないだろうかーと思い、試してみました。
でも、結果としては 1 ツィートに収める事はできず、 「つぶやきProcessing できませんでした。」とツィートせざるをえませんでした:
飛行機雲(contrail)、という作品を作っていたのですが、文字数超過で、つぶやきProcessing できませんでした。とほほ。
— Koji Saito (@KojiSaito) February 29, 2020
文字数的には 2 tweet 程なので、削減はちょっと難しそうです(残念)。 pic.twitter.com/A6JQEeSo24
この作品のソースコード(圧縮版)はこんな感じでした:
f=u=L=0;W=500;R=random;N=noise
def setup():size(W,300);blendMode(BLEND)
v=R(99)+50
p=10
q=(N(0)*2-1)*3
def draw():
global f,u,v,p,q,L;f+=0.001;L+=0.1;fill(0,100,180,70);rect(0,0,W,W);x=u+p*L;y=v+q*L;stroke(255,30);line(u,v,x,y);noStroke()
for i in range(17000):t=i/170;s=i%170;fill(255,pow(N(s/70.0+5*f,t/70.0+f,f*10),6)*W);rect(s*3,t*3,3,3)
if x>W or 0>y or y>300:u=0;v=R(99)+50;p=R(5)+8;q=(N(f)*2-1)*3;L=0
これだけ文字数が多いと、つぶやき Processing は無理です。 ちなみに、清書版というか、最初のソースコードはこんな感じでした:
def setup():size(510,255);noStroke();blendMode(BLEND)
f=0
u=0
v=random(99)+99
du=5
dv=(noise(0)*2-1)*5
L=0
def draw():
global f,u,v,du,dv,L
f+=0.001
L+=0.2
fill(0,100,180,20)
rect(0,0,510,510)
x=u+du*L
y=v+dv*L
stroke(255,30)
line(u,v,x,y)
noStroke()
for iy in range(128):
for ix in range(255):
a=noise(ix/70.0+5*f,iy/70.0+f,f*10)
a=pow(a,4)*255
fill(255,255,255,a)
rect(ix*2,iy*2,2,2)
if x>510 or y<0 or y>255:
u=0
v=random(99)+50
du=random(5)+8
dv=noise(f)*2-1
dv*=5
L=0
コツを掴み始めた日 (2020.3/3)
試行錯誤を繰り返すなか、空と雲の表現のコツを掴み始めたのが、 上の作品から 4 日後の 2020 年 3 月 3 日でした。
#つぶやきProcessing
— Koji Saito (@KojiSaito) March 3, 2020
H=255
def setup():size(510,H);noStroke()
def draw():
f=frameCount*0.01
for i in range(32640):
x=i%H;y=i/H
u=(x-127.)/H;v=(127.-y)/H
g=1-v
d=max(noise(x*.01+f,1/(v+.1),f)-.5,0)*(1.2-v)
fill((g*g+d)*H,(g+d)*H,(.6+g*.4+d)*H);rect(x*2,y*2,2,2) pic.twitter.com/KTFNRwfBr5
焦点距離を f とすると、3D2D 変換の式は (X,Y,Z) → (x,y)=(fX/Z,fY/Z) となります。 焦点距離を 1 とすると、y=Y/Z より Z=Y/y を得ます。 で、雲模様が Y=1 の平面上に展開されていると仮定すると、 Z=1/y となります。
実際には y が 0 の時のエラーを回避するため、1/(y+0.1) などとしています。
この関係式を使って、雲模様を描いたところ、 なかなか良い感じに空と雲が描画できました。
空の色については、edo_m18 さんの Qiita の記事
https://qiita.com/edo_m18/items/a575606a60b21f0d2c57#空の色
を参考にしています。
文字数圧縮する前のソースコードは次の通りです:
def setup():size(510,250)
f=0
def draw():
global f;f+=0.005
noStroke()
for iy in range(128):
for ix in range(255):
x=(ix-127)/255.
y=(127-iy)/300.
r=pow(1-y,2)
g=1-y
b=0.6+(1-y)*0.4
n=noise(ix*0.01+f,1/(y+0.1),f)
d=max(n-0.5,0)*(1.2-y)
fill((r+d)*255,(g+d)*255,(b+d)*255)
rect(ix*2,iy*2,2,2)
この方法で、なんとなくコツは掴み始めたのですが、 描かれる雲が薄っぺらい。 これはこれで、秋の空のようで嫌いではないのですが、 もう少し、しっかりとした雲を描きたい。 そんな風に感じつつ、試行錯誤を継続していきます。
高度 1 万メートル (2020.3/6)
2020 年 3 月 6 日に、つぶやき Processing として発表したこの作品は、 最初に空と雲を描画する作品として完成していました。 しかし、発表するタイミングを失い、この時期に発表することとなりました。
#つぶやきprocessing 高度1万m
— Koji Saito (@KojiSaito) March 6, 2020
def setup():size(510,300);noStroke()
f=0
K=255
def draw():
global f;f+=0.01;background(0,120,K)
for y in range(156):
h=min(80-y,0)/256.;p=(1-exp(-50-h))*exp(-4*h)
for x in range(K):t=noise(x/100.+f,y/100.);a=t*p;fill(K,K,K,a*K);rect(x*2,y*2,2,2) pic.twitter.com/D4mv3ciZzT
雲の密度に作用する変数 p は、Olajos さんの論文 Real-Time Rendering of Volumetric Clouds https://lup.lub.lu.se/student-papers/search/publication/8893256 を参考にしています。
文脈的には、多分、間違った使い方だと思うのですが、 表現としては面白いものとなったので、あえて間違った使い方をしています。
タイトルの「高度 1 万メートル」ですが、 生成されたアニメーションをみて、 なんだか飛行機の窓からみた景色のようだな、と感じたため、 航空機の巡航高度である高度 1 万メートルというタイトルを付けました。
やっぱり Volumetric Clouds (2020.3/11)
ノイズ関数を使うことにより、 なんとなく秋の薄雲のような表現はできるようになってきました。 これが限界なのかな、と思っていたところ 5 日後の 2020 年 3 月 11 日に Yusuke Oda さん(@odashi_t さん)より、 大変美しい雲の写真が Twitter に投稿されました:
新海誠雲 pic.twitter.com/BoYr7djkdr
— Yusuke Oda (@odashi_t) March 11, 2020
「新海誠雲」と書かれた雲をみて、やはりこういう厚みのある 美しい雲(Volumetric Clouds)を描きたい、と再び強く思うようになりました。
画像下半分の街はともかくとして、空と雲はつぷやきProcessingできないのかな、と思ってしまう(もう立派な病気だねw)。だって、1 ツィートで新海誠な雲が生成できたら素敵じゃん🙂 つぶやき Processor の知見を集結すればできる!…のかな。 https://t.co/j9QehBudlX
— Koji Saito (@KojiSaito) March 11, 2020
目指せ新海誠雲 (2020.3/12)
新海誠雲の衝撃の翌日(2020 年 3 月 12 日)、 夕焼けの表現を取り入れて つぶやき Processing してみました。
#つぶやきProcessing
— Koji Saito (@KojiSaito) March 12, 2020
H=255
def setup():size(510,H);noStroke()
def draw():
f=frameCount*.01
for i in range(32640):x=i%H;y=i/H;v=(127.-y)/H;s=.3-v;g=1-v;d=max(bezierPoint(0,-.5,1,1,noise(x*.01+f,1/(v+.1),f))-.3,0)*(1.2-v);fill((g*g+d+s)*H,(g+d)*H,(.6+g*.4+d-s)*H);rect(x*2,y*2,2,2) pic.twitter.com/uLLSFMDZsU
解説編でも書いているとおりですが、 描画位置により赤色成分を増やすと同時に青成分を減らすなどして、 なんとか夕焼けっぽい雰囲気を出そうと努力しています。
また、ノイズ関数を使う場合、ノイズ関数の連続性の特性なのか分かりませんが、 なかなか雲の輪郭部分が明確に描画できません。
ある程度厚みのある雲を描く場合、 雲と空の境界部分はある程度明確にしなければならないと感じています。
そのため、この作品では bezierPoint 関数を使ってノイズ関数に変調をかけ、 小さな値のときと、大きな値の時の差がでるように工夫を試みています (結果からいうと、これはあまり効果がなかったように感じています)。
海外からのアップデート
なお、この作品をみた Brett Cooper さん(@hellonearthis さん)が、 より夕焼けに染まった雲を描く改良版をツィートしてくれました:
#つぶやきProcessing p5
— Brett Cooper (@hellonearthis) March 12, 2020
setup=_=>{createCanvas(510,H=255);noStroke()}
draw=_=>{f=frameCount*.01
for(i=0;i++<H<<7;){
x=i%H;y=i/H;v=(127-y)/H;s=.3-v;g=1-v
d=max(bezierPoint(0,-.5,1,1,noise(x*.01+f,1/(v+.1),f))-.3,0)*(1.2-v)
fill((g*g+d+s)*H,H*g+d,(.6+g*.4+d-s)*H)
rect(x*2,y*2,2,2)}} pic.twitter.com/4rvqH66Th6
「Koji, 英語ができなくても、我々はコードでコミュニケーションがとれる」 とは、ハリウッドの映画スタジオにインターンに行っていたときに言われた言葉ですが、 まさしくコードでコミュニケーションできています。
こんな風に自分のコードが改良されていくのは 嬉しかったです。
青空版、ほぼ完成へ (2020.3/13)
そして、上記作品を つぶやき Processing した翌日、 試行錯誤の末、なんとか厚みを感じられる雲を描画できるようになりました。 この作品が、本ページ冒頭にも掲載している青空版の到達点となります。
#つぶやきProcessing
— Koji Saito (@KojiSaito) March 13, 2020
def setup():size(550,280);noStroke()
C=circle
def draw():
f=frameCount*.01;background(99,150,255)
for i in range(15000):x=i%150;y=100-i/150;t=noise(x*.01+f,y*.02+f);x*=4;y*=3;t=pow(t,8)*8;fill(100,t*30);C(x,y,10);fill(255,t*255);C(x-20,y-15,10) pic.twitter.com/xEcYlMsUDf
この作品では、雲を円の集合として描画しています。 厚みのある雲というのは、雲の底(?)の部分には太陽の光があまり到達せず、 暗くなるのが特徴です。
それらを表現するため、この作品では、 暗い円を先に描き、その上に左上に少しずらした明るい円を描いています。
# 暗い円
fill(100,t*30)
C(x,y,10)
# 明るい円
fill(255,t*255)
C(x-20,y-15,10) # 左上に少しずらして描画
この作品でも、ノイズ関数の立ち上がりを制御するため、 ノイズの値 t を 8 乗して使っています(t=pow(t,8) という部分)。
あと、実際の絵画同様、遠方のものより描画するため、 画面下部から順次描画を実践しています。
このような細かな工夫をすることにより、 つぶやき Processing にて Volumetric Clouds の表現が可能となりました。
なお、つぶやき Processing としてツィートするため、 雲の発生確率を上げています。 そのため、このプログラムで描画される空は、 少々天気が悪い状況となっています。
本当の完成版というべき空模様はもう少し清々しい、爽やかな空です。 t=pow(t,8) を t=pow(t,9) と変更すれば、 そのような空が現れます。
夜を描く(2020.3/18)
空と雲の表現も落ち着いた感じがあったので、 この日は夜の空を描くことを試みてみました。
#つぶやきProcessing
— Koji Saito (@KojiSaito) March 18, 2020
def setup():size(510,240);noStroke()
C=circle;F=fill
def draw():
clear();f=frameCount*.01;F(-1);C(50,50,40);F(0);C(60,45,40)
for i in range(5**6):x=i%170;y=i/170;v=(120-y)/240.;F(-1,max(noise(x*.01+f,1/(v+.1),f)-.3,0)*(2-v)*255);C(x*3,y*3,3)
filter(BLUR,1) pic.twitter.com/inGCGKtUmF
絵画的な表現を目指して (2020.3/19)
2020.3/19 に発表したこちらの作品では、 絵画的な表現を目指しました。
しかし、1 ツィートに収めることができず、 残念ながら つぶやき Processing として発表できませんでした。
文字数超過で、つぶやきProcessingできず(残念)。
— Koji Saito (@KojiSaito) March 19, 2020
今回の雲は単に noise 関数をドメインワープして、それを α 値に用いて描いているだけです。
P3D で描画しようとすると、関数名が長いのですぐに文字数オーバーしてしまいます。 pic.twitter.com/NNgJklt1Kv
この作品では、雲の部分をドメインワープという手法を用いて描画しています。 ドメインワープに関する話は、 この作品の解説ページにて簡単に記しましたので、 そちらを見ていただければ、と思います。
夕焼け空をテーマとして (2020.4/9)
この作品は、2020 年 4 月 1 日から 5 月 14 日まで Twitter 上にて開催された #dailycodingchallenge とイベントに参加するために 作った作品です。
#dailycodingchellenge では、毎日日替わりでテーマ(お題)が設定されており、 参加者はその「お題」に対する作品をツィートしていきます。
この日のお題は研究所でした。
文字数超過で、つぶやきProcessing できず。残念(draw 部分はリプライに貼っておきます)。
— Koji Saito (@KojiSaito) April 9, 2020
テーマ:研究所
タイトル:雲雀ヶ丘にある建物群のイメージ
前夜祭のときだっただろうか。美しい夕焼けをバックに Enya の音楽が大音量で流れていた時のことを思い出しました。
#dailycodingchallenge https://t.co/QjOi9jf9Kv pic.twitter.com/FhSqaRDMwc
この作品では、夕焼け空は (R,G,B)=(155+y,100+300*t,120+100*t-y) としただけでした。
つぶやき Processing で美しい夕焼け空を表現したいなぁ、と思い始めたのは、 この作品からだったように思います。
文字数超過で つぶやき Processing できなかったこちらの作品のソースコードは、 以下のようなものでした:
def setup():
size(500,230)
noStroke()
def draw():
clear()
f=frameCount*.01
# 空の描画
for iy in range(100):
for ix in range(250):
t=noise(ix*.02+f,1/((101-iy)/50.)*15)-.8
fill(155+iy,100+300*t,120+100*t-iy)
rect(ix*2,iy*2,2,2)
# 建物の描画
randomSeed(3)
fill(0)
for i in [0]*20:
h=random(60)
rect(random(200)+200,200-h,random(50),h)
割と緩めな夕焼け表現 (2020.5/15)
#つぶやきProcessing
— Koji Saito (@KojiSaito) May 15, 2020
def setup():size(500,170);noStroke()
C=circle
F=fill
def draw():
f=frameCount*.5;
for i in range(10):F(i*30+200-f,70-f,255-i*25-f);rect(0,i*13,500,300)
y=120+f/9;F(-9);C(400,y,50);filter(BLUR,3);C(400,y,30)
F(9,20,80);rect(0,130,500,270);filter(BLUR,2) pic.twitter.com/DbDpg8pDCl
こちらの作品は、前日まで続いた #dailycodingchallenge も終わり、 再び空をモチーフとした作品を作ってみようと思ったものです。
#dailycodingchallenge では夕日と海というテーマで 作品発表もしていたので、より変化のある夕焼け空に挑戦を試みてみました。 実際に作ってみると、なかなか難しいことが分かりました。
この作品で空を決めている部分は以下のコードです:
fill(i*30+200-f, # red
70-f, #green
255-i*25-f # blue
)
ここで、変数 i は画面上の y 座標の位置(実際の y 座標は y=i*13 です)、 変数 f は frameCount/2 です(コードでは f=frameCount*.5 で定義)。
どのように R,G,B を変化させれば、 より魅力的な夕焼け空になるのか、 それを意識し始めたのはこの作品からだったかと思います。
夕焼けと雲 (2020.5/21)
#つぶやきProcessing 解説編
— Koji Saito (@KojiSaito) May 21, 2020
先日の https://t.co/V7PXa7Uji2 さんの素晴らしい写真を RGB 分解し、それっぽい表現をする関数を適当に作ったもの(習作)です。https://t.co/TxfGv86Jge
複雑な理論などなく、単に見た目から、似たような画像を電気絵具的に生成しています。 https://t.co/OpefsKf5JU pic.twitter.com/z143zUjA7j
この作品については、あくまでも習作ではありますが、 あるひとつの到達点になった気がします。
この作品を製作するきっかけとなったのは、 tenki.jp さんに掲載された非常に美しい写真でした:
https://storage.tenki.jp/storage/static-images/suppl/article/image/2/29/298/29830/1/large.jpg
この写真は tenki.jp さんの 2020 年 5 月 19 日の記事、 「 限られた時期しか見られない「宵の明星」と「明けの明星」。5月22日は金星と水星の競演に注目 」 ( https://tenki.jp/suppl/grapefruit_j02/2020/05/19/29830.html ) に掲載されたものです。
あまりにも美しいこの写真に感銘を受け、 なんとかこのような夕焼けを作ってみたい、と考えるようになりました。
分析
分析の基本は RGB 各チャネルに分解し、それぞれの様子をみてみることです。
#つぶやきProcessing 解説編
— Koji Saito (@KojiSaito) May 21, 2020
先日の https://t.co/V7PXa7Uji2 さんの素晴らしい写真を RGB 分解し、それっぽい表現をする関数を適当に作ったもの(習作)です。https://t.co/TxfGv86Jge
複雑な理論などなく、単に見た目から、似たような画像を電気絵具的に生成しています。 https://t.co/OpefsKf5JU pic.twitter.com/z143zUjA7j
この分解画像を見てみると、各チャネルについては
- R は下の方に非常に明るい領域が帯状に存在している
- G は R 程ではないが、ぼんやりと、なんとなく下の方に明るい部分が存在する
- B は下の方が明るくなってはいるものの、どちらかというと全体的にぼんやりと光っている
という特徴がみてとれます。
であれば、画面上方のある位置からの距離 d に応じ、 べき乗で輝度を調整すればよさそうです。
この作品では、画面上方の位置 (150,-100) …この場所は画面外となりますが、 その位置から、これから色を塗ろうと思っている点との距離を d とし、 R,G,B の輝度値 \( I_R,I_G,I_B \) を次のように決めました:
$$ \begin{array}{cl} I_R & =0.0000000002 d^5 \newline I_G & =0.002 d^2 \newline I_B & =0.6 d \end{array} $$
要は、 R,G,B の各チャネルの輝度の変化を、 それぞれ d の 5 乗、d の 2 乗、d の 1 乗(=線形に変化)と捉えているだけです。
それぞれのチャネルの計算式にある 0.0000000002 とか 0.6 等という値は、 実際に画像を生成してみて、調整したものです。 特に何か意味があるものではありません。 完全に見た目から決まっている値です。
あとは、これらの式をベースに noise 関数により生成される雲を 足し込んで行くだけです。
ソースコード
この作品のソースコードを清書したものは以下のようになります:
def setup():
size(500,400)
noStroke()
def draw():
for iy in range(200):
for ix in range(250):
d=dist(ix,iy,150,-100)
t=noise(ix*.01+frameCount*.02,iy*.05)
fill((t+.7)*pow(d,5)*2e-10,(t+.5)*d*d*.002,(t+.2)*d*.6)
rect(ix*2,iy*2,2,2)
まとめ
以上で、2020 年 2 月 29 日から始まった、 私の空と雲をモチーフとした作品制作は同年 5 月 21 日の作品をもって、 一段落となります。
このページで説明した作品は、全部で 11 作品です。 11 作も作ると、それなりに上達するものだなぁ、と改めて思います。
もちろん、完全に納得のいく作品ができた訳ではありませんが、 自分の中でなんとなく一区切りつけることができたように感じています。
また、機会があれば再開したいと思いますが、 約 2 ヶ月半に渡った試行錯誤の結果は、5 月 29 日の作品でひとまず終了とします。