280 文字以下で実現する、レイマーチングを用いた立体的な雲画像の生成方法
車を走らせていると、青空を背景に白い雲がぽっかりと浮いていた。下の方は灰色の影を有していて、上の方は太陽に照らされ白く輝いている。なんとも立体感が凄い。
こういう雲を描いてみたいんだよなぁ…などと思いつつ、そういえば、立体感のある雲を CG で生成する場合、レイマーチングを使うんだっけか、と昔勉強していた日々を思い出す。
なお、レイマーチングについては、以下のページ "(1/2) Behind The Scene: Cloud Raymarching Demo" https://davidpeicho.github.io/blog/cloud-raymarching-walkthrough-part1/ の図
https://davidpeicho.github.io/blog/cloud-raymarching-walkthrough-part1/light-volume-interactions.jpg
が分かりやすいんだけど、note だと埋め込み表示してくれないのね…。興味のある人はクリックして見てみると良いと思う。
要はレイを少しずつ前進させて、媒体の中をスキャンして、その情報を対象としている点のレンダリングに活用する ー という方法である。
話をもとにもどそう。まあ、とにかく、レイマーチングやら内部拡散を考慮してしなければならず、自然を模倣するのは大変である。などと思っていると、ふと、ノイズ関数に対しレイマーチングすれば同様の表現ができるのではないかと思った。
そもそも自分が雲を表現するときにはノイズ関数を密度関数とみなして雲を生成している。であれば、ノイズ関数を上空から太陽光線に沿って貫くようにスキャンし、それを用いて輝度を変化させればよいのである。…これは正しいのか?と思いつつ、用事を済ませ帰宅した。
早速、Processing にざっとコードを打ち込んで、雲模様を作ってみる。
size(500,700)
noStroke()
background(99,140,190)
for i in range(100*140):
x=i%100;y=i/100
fill(240,(noise(x*.02,y*.02)-.5)*99)
circle(x*5,700-y*5,30+random(20))
なかなか良い感じである。ちなみに、地となる青空の色や、雲の輝度値は circle 関数の半径指定部分を試行錯誤するついでに調整している。目標とする青空や雲の色によって(もしくは気分によって)、これらの値は随時調整している。
つぎにレイマーチング部分を組み込む。雲の構成要素として描画する円の密度(上のコードでは透明度として表現されている)は noise(x*.02,y*.02)-.5 で与えられている。であれば、これをレイマーチングして、その値の総和を用いればレイマーチングにより影が得られるのでは? ー というのが、そもそものアイデアである。
もちろん、あくまでも平面的な広がりを持つ雲模様なので、さて、実際の雲にどこまで近づくことができるのであろうか。
レイマーチング部分のコードは以下の通りである:
t=0
for j in range(140-y):
t+=max(noise(x*.02-j*.02,y*.02+j*.02)-.5,0)
レイのステップサイズは (-j*.02,+j*.02) である。Processing の座標軸を考えると y 軸の上の方にスキャンしていくのであれば (-j*.02,-j*.02) と進行方向の y 座標は負の数になりそうである。
しかし、明るい部分をより手前に描画したほうが美しいので(ハイライトは最後に描かないと埋もれてしまうのは通常の絵画と同様である)、実際の円の描画については circle(x*5,700-y*5,30+random(20)) と、上下を逆にしている。y の値が大きくなればなるほど、画面上部に描画される。
そのため、レイマーチングにおけるスキャン時には y 方向の値を増加させつつスキャンしている。
ノイズ関数の値から 0.5 を引いて、0 との max を取っているのは、雲の形を表すために、アルファチャネルを noise(…)-.5 としているからである。つまり、ノイズの値が 0.5 以下の場合は透明色となり、雲の一部として描画されることはない。これは即ち、雲の密度としては存在しない=0 であることに他ならない。それを再現するために、max(noise(…),0) としている。
レイマーチングの結果得られた累積密度は変数 t に格納されているので、あとはこれを用いて(雲のパーツとして)円を描画すれば十分…なハズである。
size(500,700);noStroke()
background(99,140,190)
for i in range(100*140):
x=i%100;y=i/100;t=0
for j in range(140-y):t+=max(noise(x*.02-j*.02,y*.02+j*.02)-.5,0)
fill(max(240-t*8,0),(noise(x*.02,y*.02)-.5)*99)
circle(x*5,700-y*5,30+random(20))
なかなか良い感じである(自画自賛)。
もう少し、影部分が暗くても良いかな?という気もするが、まあ、あまり強調しても…という気もする。ちなみに、fill 関数内の t の係数を大きくすると暗い影となる。以下の図は係数を 8 から 18 に変更したものである:
このあたりはお好みで試してもらえると良いと思う。
さて、最後に #つぶやきProcessing 化であるが…省略できる改行を無くすだけで特に問題なく 1 tweet に収まってしまった。レイマーチング処理込みで、苦労せず #つぶやきProcessing できてしまうとは。正直、拍子抜けした感があった。
この記事が参加している募集
この記事が気に入ったらサポートをしてみませんか?