Blenderで花火大会

2022/08/18

レンダリング結果

夏なのに引きこもりなので花火を作ります.

花火の作成

なにはともあれ,まずは花火の表現方法を考えます.結果だけ知りたい方はジオメトリノードを構築まで飛ばしてください.

採用しなかった方法:物理演算とモーションブラー

花火の運動を再現したい場合,真っ先に思いつくのはBlenderのパーティクル鋼体シミュレーションです.ちょっと実験してみましょう.

鋼体シミュレーションでつくった花火

まぁこんな結果になるんですが,写真としては違和感があります.というのも,「花火大会」で画像検索してみると……

花火大会の画像検索結果

写真の花火は,「点」ではなく「線」で写っています.

これは,撮影時に露光時間を長く取っているためです.

開花した花火が消えるまでシャッターを開いておくと、(中略)花火の軌跡をきれいに捉えることができます。シャッター速度が短すぎると(中略)花火が点で写ってしまいます。

シャッタースピードと花火の見え方の関係

出典: 花火大会の彩りを撮る, ソニー

というわけで,「Blender シャッタースピード」とか「Blender 長時間露光」で検索してみると,「モーションブラーを使え」との記述が出てきます.実際に使ってみると,

モーションブラーの設定

モーションブラー有効で撮影した花火

……あれ?なんだかガビガビです.

私の設定が悪いのかもしれませんが,どう頑張っても綺麗な結果は得られませんでした.

また,この方法では不便な点が多いように感じます.レンダリングしないと軌跡の形がわからないし,マテリアルの調整も大変そうです.

他にも,パーティクルの数を増やしたり,子パーティクルを設定したりして擬似的に軌跡を表現する方法も試しましたが,どれもきれいな結果にはなりませんでした…….

採用した方法:運動方程式+ジオメトリノード

一番良いのは,鋼体の軌跡をメッシュとして直接得られることです.

そもそも,花火の動きは単純なので,時刻からその星(空中で光る火薬1つ)の位置を代数的に決定できるはずです.つまり,ステップバイステップの数値解析は必要ありません.

したがって,運動方程式を解いて星の位置を直接計算し,ジオメトリノードによって花火のメッシュを生成します.

運動方程式を解く

変数や関数を以下のように定義します.

  • tt:花火が開く瞬間を00とする時刻,ss
  • mm:星の質量,kgkg
  • kk:空気抵抗係数,スカラー
  • 加速度
    • a(t)\vec{a}(t):時刻ttにおける星の加速度ベクトル,(m/s2,m/s2,m/s2)(m/s^2, m/s^2, m/s^2)
      • ax(t)a_x(t):時刻ttにおける星の加速度ベクトルのxx成分,m/s2m/s^2
    • g\vec{g}:重力加速度,(m/s2,m/s2,m/s2)(m/s^2, m/s^2, m/s^2)
  • 速度
    • v(t)\vec{v}(t):時刻ttにおける星の速度ベクトル,(m/s,m/s,m/s)(m/s, m/s, m/s)
      • vx(t)v_x(t):時刻ttにおける星の速度ベクトルのxx成分,m/sm/s
    • v0\vec{v}_0:時刻00における星の速度ベクトル,(m/s,m/s,m/s)(m/s, m/s, m/s)
      • v0xv_{0x}:時刻00における星の速度ベクトルのxx成分,m/sm/s
  • 位置
    • p(t)\vec{p}(t):時刻ttにおける星の位置ベクトル,(m,m,m)(m, m, m)
      • px(t)p_x(t):時刻ttにおける星の位置ベクトルのxx成分,mm
    • p0\vec{p}_0:時刻00における星の位置ベクトル,(m,m,m)(m, m, m)
      • p0xp_{0x}:時刻00における星の位置ベクトルのxx成分,mm

星の運動方程式は重力と空気抵抗1を用いて以下のようになります.

ma(t)=mgkv(t)m \vec{a}(t) = m\vec{g} - k\vec{v}(t)

両辺をmmで割れば,

a(t)=gkmv(t)\vec{a}(t) = \vec{g} - \frac{k}{m}\vec{v}(t)

加速度a(t)\vec{a}(t)が得られます.

ここからは,ひとまずxx成分に着目して解いていくことにします.

ax(t)=gxkmvx(t)a_x(t) = g_x - \frac{k}{m} v_x(t)

簡単のために,r=kmr = \frac{k}{m}とおきます.

ax(t)=gxrvx(t)a_x(t) = g_x - r v_x(t)

これをttで積分して速度vx(t)v_x(t)を求めます.変数分離を使います.

ax(t)=gxrvx(t)dvx(t)dt=gxrvx(t)dvx(t)dt=r{vx(t)gxr}1vx(t)gxrdvx(t)dt=r1vx(t)gxrdvx(t)=rdt1vx(t)gxrdvx(t)=rdtlog{vx(t)gxr}=rt+C1vx(t)gxr=ert+C1vx(t)gxr=erteC1vx(t)=erteC1+gxr(1.1)\begin{align*} a_x(t) &= g_x - r v_x(t) \\ \frac{d v_x(t)}{dt} &= g_x - r v_x(t) \\ \frac{d v_x(t)}{dt} &= -r \{v_x(t) - \frac{g_x}{r}\} \\ \frac{1}{v_x(t) - \frac{g_x}{r}} \frac{d v_x(t)}{dt} &= -r \\ \frac{1}{v_x(t) - \frac{g_x}{r}} d v_x(t) &= -r dt \\ \int \frac{1}{v_x(t) - \frac{g_x}{r}} d v_x(t) &= \int -r dt \\ \log \{v_x(t) - \frac{g_x}{r}\} &= -rt + C_1 \\ v_x(t) - \frac{g_x}{r} &= e^{-rt + C_1} \\ v_x(t) - \frac{g_x}{r} &= e^{-rt} e^{C_1} \\ v_x(t) &= e^{-rt} e^{C_1} + \frac{g_x}{r} \qquad (1.1) \\ \end{align*}

C1C_1は積分定数です.

ここで,vx(0)=v0xv_x(0) = v_{0x}より

vx(0)=v0xeC1+gxr=v0xeC1=v0xgxr(1.2)\begin{align*} v_x(0) &= v_{0x} \\ e^{C_1} + \frac{g_x}{r} &= v_{0x} \\ e^{C_1} &= v_{0x} - \frac{g_x}{r} \qquad (1.2) \\ \end{align*}

となるため,(1.2)(1.2)(1.1)(1.1)を書き換えると

vx(t)=ert(v0xgxr)+gxrv_x(t) = e^{-rt} (v_{0x} - \frac{g_x}{r}) + \frac{g_x}{r}

となり,速度vx(t)v_x(t)が得られました.

これを更にttで積分することで,位置px(t)p_x(t)を求めます.

vx(t)=ert(v0xgxr)+gxrdpx(t)dt=ert(v0xgxr)+gxrpx(t)={ert(v0xgxr)+gxr}dtpx(t)=ert(v0xgxr)dt+gxrdtpx(t)=(v0xgxr)ertdt+gxrdtpx(t)=1r(v0xgxr)ert+gxrt+C2(2.1)\begin{align*} v_x(t) &= e^{-rt} (v_{0x} - \frac{g_x}{r}) + \frac{g_x}{r} \\ \frac{d p_x(t)}{dt} &= e^{-rt} (v_{0x} - \frac{g_x}{r}) + \frac{g_x}{r} \\ p_x(t) &= \int \{e^{-rt} (v_{0x} - \frac{g_x}{r}) + \frac{g_x}{r}\} dt \\ p_x(t) &= \int e^{-rt} (v_{0x} - \frac{g_x}{r}) dt + \int \frac{g_x}{r} dt \\ p_x(t) &= (v_{0x} - \frac{g_x}{r}) \int e^{-rt} dt + \int \frac{g_x}{r} dt \\ p_x(t) &= -\frac{1}{r} (v_{0x} - \frac{g_x}{r}) e^{-rt} + \frac{g_x}{r} t + C_2 \qquad (2.1) \\ \end{align*}

C2C_2は積分定数です.

ここで,px(0)=p0xp_x(0) = p_{0x}より

px(0)=p0x1r(v0xgxr)+C2=p0xC2=1r(v0xgxr)+p0x(2.2)\begin{align*} p_x(0) &= p_{0x} \\ -\frac{1}{r} (v_{0x} - \frac{g_x}{r}) + C_2 &= p_{0x} \\ C_2 &= \frac{1}{r} (v_{0x} - \frac{g_x}{r}) + p_{0x} \qquad (2.2) \\ \end{align*}

となるため,(2.2)(2.2)(2.1)(2.1)を書き換えると

px(t)=1r(v0xgxr)ert+gxrt+1r(v0xgxr)+p0xpx(t)=1r(v0xgxr)(1ert)+gxrt+p0x\begin{align*} p_x(t) &= -\frac{1}{r} (v_{0x} - \frac{g_x}{r}) e^{-rt} + \frac{g_x}{r} t + \frac{1}{r} (v_{0x} - \frac{g_x}{r}) + p_{0x} \\ p_x(t) &= \frac{1}{r} (v_{0x} - \frac{g_x}{r}) (1 - e^{-rt}) + \frac{g_x}{r} t + p_{0x} \\ \end{align*}

となり,位置px(t)p_x(t)が得られました.

yy成分とzz成分についても同様なので,時刻ttにおける位置p(t)\vec{p}(t)は次のようになります.

p(t)=1r(v0gr)(1ert)+grt+p0\vec{p}(t) = \frac{1}{r} (\vec{v_0} - \frac{\vec{g}}{r}) (1 - e^{-rt}) + \frac{\vec{g}}{r}t + \vec{p_0} \\

ただし

r=kmr = \frac{k}{m}

です.

ジオメトリノードを構築

以下が最終的なジオメトリノードです.(画像をクリックすると拡大します)

花火ジオメトリノード

このノードは,面から法線方向に星を射出します.そのため,球以外にも様々なジオメトリを入力することで,ほとんどの種類の花火を表現できます.

使用例

例えば次のような設定の花火ジオメトリノードを球に適用します.横向きの重力加速度は横風を意図しています.

花火ジオメトリノードの設定画面

ジオメトリノードの結果,以下のようなそれっぽいメッシュが得られます.

花火ジオメトリノードによって生成されたメッシュ

それっぽいですね.

解説

各部分について細かく説明していきます.

花火ジオメトリノード詳細1

上図では,軌跡の辺を生成しています.「面にポイント配置」によって,星を配置する場所をランダムに決めています.「メッシュライン」によって,星の軌跡を表現します.なお,ポイントとメッシュラインそれぞれでIDを格納しておきます.

花火ジオメトリノード詳細2

上図では,先程求めたp(t)\vec{p}(t)を使って軌跡の位置を計算しています.ttはメッシュラインが生成した各頂点のIDを使って算出されます.v0v_0は面のノーマルとノーマル速度から与えられます.また,v0v_0を任意の係数でランダマイズする機能もあり,軌跡が単調になるのを防ぎます.

花火ジオメトリノード詳細3

上図では,メッシュをソリッド化しています.そのためにメッシュを一旦カーブに変換し,そのうえで断面カーブを指定することで再度メッシュに戻しています.最後に,trajectoryという値を格納しています.これは,軌跡の始まりが0,終わりが1になるような値です.後のマテリアル設定で使います.

花火マテリアル

以下のようになりました.

花火マテリアル

ポイントは,先程のジオメトリノードで格納したtrajectoryを使って放射強度や色を制御しているところです.

例えば,放射強度が固定値だと以下のような単調な見た目になってしまいます.

花火マテリアル放射強度カーブ詳細1

この値をカーブでいじってやることで,軌道の尾をフェードアウトさせたり,

花火マテリアル放射強度カーブ詳細2

多重の花火(八重芯,三重芯,四重芯……)を表現したりできます.

花火マテリアル放射強度カーブ詳細3

今回は,尾と先端をフェードさせるシンプルな表現にしました.

花火マテリアル放射強度カーブ詳細4

シーンレイアウト

以下のようにしました.

シーンレイアウト

海の上に山が乗っかってるのは,作図が雑だからです.

ともかく,Blenderに実装するとこうなります.

シーンのオブジェクト

山や雲は画像です.以下のフリー素材を使用させていただきました.

花火の煙は,「メッシュのボリューム化」と「ボリューム変形」モディファイアで適当に作りました.

煙の作り方1

煙の作り方2

煙の作り方3

海はオーシャンモディファイアを使用して作りました.

オーシャンモディファイア設定

環境は,例のごとく西田先生の大気テクスチャにお世話になります.フォトリアリスティック自宅でも使用したやつです.

ワールド設定

レンダリングとコンポジット

まずカメラの設定です.今回は広範囲を撮影するので,その広さが伝わるように,魚眼レンズで撮影し画面を歪ませています.

カメラ設定

次にコンポジットの設定です.以下のようになりました.

コンポジットノード

デノイズ処理をしたうえで色収差,グレイン,グローを再現しています.

被写界深度と周辺減光に関しては,撮影範囲が広いこと,露光時間が長いことを考慮して,あえて再現しませんでした.

完成

↓全体像(4K撮影)

レンダリング結果

↓上から見た図

上から見た花火

↓下(発射地点)から見た図

下から見た花火

まぶし!

まとめ

Blenderで夏の花火大会を開催しました.

花火の軌跡を表現するために,物理モデルを組み込んだジオメトリノードを構築しました.運動方程式や微分積分など,学生の頃は「なんの役に立つんだ?」と思っていた知識が,こうして納涼に役立つわけですから面白いですね.

ありがとうございました.

参考文献


  1. ひとくちに空気抵抗といっても様々なモデルがあるようですが,ここではいちばん簡単なものを採用しました.

続けて読む…

夏だからGatsbyのランタイム全部消す

2021/08/02

【配布あり・編集可能】Blenderでパチンコ文字

2022/01/01

ウソの新居ができるまで(Blender)

2021/03/27

Blenderで20世紀F○X風のOPを作る

2017/04/03

Blenderでいぶし銀

2020/08/26

Firefox(Blender)

2021/08/14

書いた人

sititou70のアイコン画像
sititou70

都内の社会人エンジニア4年生。Web技術、3DCG、映像制作が好き。