【Houdini】アニメーションするツタの作成
こんにちは。仕事の合間に少しずつHoudiniで遊んでいる者です。
先日、以下のようなアニメーションするツタを作成してみました。
今回はSOPとDOPを行ったり来たりしながら作成する上で、色々と考えたことや気づきを学習メモがてら共有できたらと思います。
なおHoudini初心者の記事となっておりますので間違い等多々あるかと思われます。もしお気づきになりましたらご指摘いただけますと幸いです。
↓↓今回使用しているファイルはこちらです↓↓(※テクスチャはありません)
【構想】
制作にあたって、最初に以下のような構想を持ちました。
これを念頭に置いて、色々こねこねしていきます。
【制作のおおまかな流れ】
ざっくりな流れは以下の通りです。
➀[IVY_CURVE] popを利用してツタのカーブを作成
➁ [IVY_LEAF] 葉っぱの作成
➂[IVY_MESH] カーブから成長するツタの茎や葉を作成
➃レンダリング
[IVY_CURVE] popを利用してツタのカーブを作成
今回、成長するツタの軌道を制作するために、パーティクルのシミュレーションの結果をそのまま使用するという方法を採りました。そのためにまず、popnetで使用するvelocity fieldを作成します。
大体こんな感じで動いてほしいという形をメッシュで作成(図A)し、そこからベースカーブを作成(図B)した後、ベースカーブを基準にvelocityで指定空間を満たすという手順で行います。(図C)
↓volume wrangle内でtangentuをvに変換
int pt = nearpoint(1,@P);
v@vel = point(1,'tangentu',pt);
カーブからvolumeの中をvelocityで満たす方法は、HoudiniのTips紹介サイトの方法を活用しました。余談となりますが素晴らしいサイトですので、是非学習の参考にしてみてください。
次にpopの処理をみていきます
pop network内では主に以下のことをしています。
(1)メッシュに近づくにつれて、particleに対してノイズを加える
ターゲットメッシュへの接近はSDFを利用することで実現しました。SDFとは各ボクセルに表面までの距離が入ったボリュームデータです。ターゲットとなるメッシュをSDFに変換し、このボリュームデータをパーティクルに転送することによってターゲットメッシュまでの距離を取得します。
↓イメージ図↓ 各ボクセル位置(黄色)からの距離(赤矢印)
少しわかりづらいですが可視化すると図のようになります。表面に近いところは距離が0、色に置き換えると黒くなっていることがわかりますね。
(2)パーティクルがメッシュに衝突すると、メッシュに張り付く
こちらは単純にsolverで実現しています。popcollisionbehaviorでhitしたパーティクルを識別し、rayでターゲットメッシュに貼り付けておきます。
以上のような処理を経てパーティクルの動きが計算できたら、あとは動きの軌道をカーブに変換してツタ用のカーブは完成です。左がそのままのカーブですが、ノイズなどで簡単にカスタマイズするのもよいかもしれません(右)
[IVY_LEAF] 葉っぱの作成
アニメーションに関してはbend等で適当に揺れる葉のオブジェクトを作成しました。作成したジオメトリは一旦キャッシュをとり、[IVY_MESH]にてfile sopで読み込みます。その際、packed primitiveとしてロードします。packed primitiveの理由としましては 後述いたしますがintrinsicsを使用するためです。
[IVY_MESH] カーブから成長するツタの茎や葉を作成
カーブの成長アニメーションは お決まりの「carve」ノードを使用します。この際、成長するカーブの先端からポイント番号0が並びますので、ptnumの0~10でグラデーションというようにしておけば先のとがったツタを作成できます。
次に葉の成長に合わせて葉を生やします。仕組みとしては以下のような感じです。
ポイントとなるのはアニメーションがオフセットしていくために、トリガーがオンになってからの時間経過をそれぞれのポイントに付与してやることです。(トリガーがオンになると白くなる様子↓)
トリガーがオンになったら(ここではCdX >= 0.05)、@Timeincでフレームごとの時間を毎フレーム足していきます。これにより各ポイントでの時間経過を取得します。(毎フレームの処理はsolver Sop)
最後に時間経過を葉のオブジェクトに渡してやります。これはつまり上流の葉オブジェクトに値を渡すことになるのですが、こういった場合どのような手段をとるのが良いのでしょうか。候補としてはstamp関数が挙げられますが、こちらは激重ですので「intrinsics」を使用します。
intrinsicsとは様々な種類のジオメトリ(Alembicや通常のprimitive等)に対して事前に用意されている組み込みアトリビュートのようなものです。
packed primitiveでは”index”というアニメーションのフレームに対応したアトリビュートがあるので、setprimintrinsic()関数で値を渡します。
※ちなみに、loop処理の時に葉もはやしてしまえばよいのでは?と思うかもしれませんがforeachの中でsolverやdop networkを使用することはできませので、solverを使用する葉の処理は最後にまとめて行っています。
レンダリング
chopでいい感じにゆらしたりカーブに沿って動かして撮影したら完成です。