もうアドベントカレンダーの季節とか嘘でしょう…?
気を取り直して皆さんいかがお過ごしでしょう。僕は今年もHoudini三昧の毎日でした。
今回は可変長引数をとってストリームごとにPackするHDAの作り方についてお話をしたいと思います。ちなみに本記事を書いている本日現在、まだHoudini19を試していないのでもしそのもののノードが追加されていたらごめんなさい。ただ、ツールの作り方やその背景などは参考になるかも?と思って現実からは目をそらしながらポストします。
本記事はHoudiniアドベントカレンダー2021 6日目の記事です。
目次
- 目次
- データ配布
- ツール制作の背景
- ObjectMergeを用いたアプローチ
- Multi Packer
- まとめ
データ配布
記事で使用したシーンはこちらからダウンロードできます。
- Windows10
- Houdini 18.5.563
まだHoudini19にしてなくてすみません…
ツール制作の背景
まずことの発端は入力ベース(ストリームベース)でPackしてくれるノードってビルトインではなさそう。というものでした。Assemble
はポリゴンの接続を見てPackしてくれるのですが、それだと不都合が起こることもあるのですね。(下記画像を参考のこと)
本来ならばShaderBallとRubberToyのふたつのPacked Primitiveにしたかったわけです。
ObjectMergeを用いたアプローチ
代替案としてObjectMerge
を利用したアプローチがあるのでご紹介しておきましょう。ここではベターな手法ではないので採用しませんでしたが、この考え方自体はいつかどこかで役に立つときもあるでしょう。
手順としては下記のとおりです。
- 読み込みたいメッシュのOperatorString($OS)にプレフィックスとして
pack_
を付けます*1 ObjectMerge
の読み込み先として../pack_*
としてワイルドカードで指定します- 読み込むオブジェクトの干渉を避けるため、全体をSubnetでくるみます
手順3はやらなくても機能しますが、プログラムで言うところのネームスペース的な考えのもとサブネットでくるんでいます。サブネットを使用しないとどんな問題が起こるかと言うと、別の場所でも同じプレフィックスを持ったノードを作ったときにそちらも読み込まれちゃうわけですね。また読み込む場所を意識してプレフィックスを複数作るのもあまり賢い方法とは言えません。
この手法でも目的は達成できますが、ワイヤが接続されていないので処理が読みにくくなる点とプレフィックスを付ける作業でヒューマンエラーが発生しやすいという点で見送ることにしました。
Multi Packer
そんなこんなでMulti Packer(マルチパッカー)というツールを作りました。HDAはhipファイル内に埋め込み(embed)しているためシーンファイル単体でお試しいただくことができます。サンプルファイルをダウンロードの上使用感をお試しください。
それでは制作の流れを解説しながら追っていきましょう。
Subnet化
本ツールは最終的にHDA化することを目的としていたため、最初からサブネット化していきましょう。
まずはNullを作成し
Shift+Cキーでサブネット化します。
サブネット下層にダイブするとこんな感じです。何ら難しい操作はしていませんね。
可変長引数を受け取る
本ツールはMergeSOPのように複数のノードを受け取る仕組みにしたいと考えています。そして内部処理としては入力されたノードごとにPackしたいわけですね。
ここに少し工夫が必要になります。
For-Each Numberを呼び出す
For-Each Numberを使っていきます。ここが後々効いてきます。
Iterationsを動的に変更する
Iterationsオプションにopninputs("..")
を指定します。
opninputsは「接続された入力で最大の入力番号を返す関数です。この「最大の入力番号」というのが少々厄介なので、気になる人のために後ほど他の方法もご紹介します。
この状態ではサブネットに何もつないでいないためIterationsはゼロですね。
挙動を確認するためひとつ上の階層に上がり、Sphere、Box、RubberToyを接続してみましょう。
再度サブネットに潜ると、Iterationsは3となっています。これで入力されたノードごとに処理をする準備ができました。
opninputsの挙動確認
先程opninputs関数は最大の入力番号を返すと書きました。これからこの数を元にループを回していくわけですが、この関数ではちょっと困った状況も生まれてしまいます。
SubnetにつないでいたBoxノードのワイヤを切ってみましょう。
これで実際にSubnetに繋がったストリームは2つですが、なんとopninputs("..")
は3を指し示したままです。これが最大の入力番号ということなのです。*2
本来ならば入力されたストリームの数だけループを回したいですよね。これはPythonを利用すれば取得できます。見ていきましょう。
Expression languageをPythonに変更する
デフォルトはHscriptになっていますが、こちらをPythonに切り替えましょう。
Iterationsオプションの書き換え
opninputs("..")
の部分を下記のように書き換えましょう。
len(hou.pwd().parent().inputConnections())
特に説明も必要ないかと思いますが、BlockEndノードの親、つまりSubnetの入力数を取得しています。これで歯抜けの入力があっても正しい値が返ってきました。
最大の入力番号には気をつけましょうという補足でした。
ただし、今回は可変長引数をとるHDAを作るという都合上Hscript Expressionを使っても問題ありません*3。そのためopninputs
関数のバージョンで話を進めていきます。
入力されたジオメトリへのアクセス
For-Loopを用いて入力されたストリーム(ノード)の個数を調べられることはわかりましたが、個々のジオメトリにはどのようにアクセスすればいいでしょうか。
それにはObjectMergeを用います。
ObjectMergeノードを作ったら、下記設定を行います。
- foreach_count1ノードの名前を
meta
に変更する*4 - Object1パラメータに
`opinputpath("..", detail("../meta/", "iteration", 0))`
を設定する
ラベルObject1をマウスの中クリックを押してみるとスフィアにアクセスできていることがわかります。
入力の歯抜け問題について
ObjectMergeをBlock Endにつないでみましょう。Gather MethodがMerge Each Iterationになっているためループの中身がマージされて出力されていますね。
Single Passの数値を変えていきましょう。
Single Passが0のときはSphereにアクセスできます。
Single Passが1のときは空振りになります。ここが歯抜けの部分になるわけです。
Single Passが2のときはRubberToyにアクセスできます。
このように、歯抜けの入力がある際もエラーが出るわけではないためopninputs
関数を使う方法で問題がないということですね。
しかしPackする際に少し気を使うところがあるのでそれは後ほど解説しましょう。
Pack機能の追加
ObjectMergeの下にPackノードを追加し、サブネットの出口にOutputノードを追加しましょう。また最初にサブネット化をするときに作ったNullノードは不要なので消してしまいましょう。
ひとつ階層を上がって想定通りにパックされているか見ていきましょう。
ちゃんとストリームごとにPacked Primitive化されていますが、歯抜けの部分までPackされてしまっていますね。Point/Primitives/Vertices/Packed Geosが3と表示されていますが、正しくは2になっていてほしいです。ここを修正していきましょう。
空振りしたときにPackされてしまう問題の修正
上述の通り、空振りした際にエラーは出ないのですが、なにもないものをPackしてしまうため問題が起こっています。つまり、ジオメトリが空だったらPackノードを通さないという処理を追加しましょう。
Pack処理の直後にSwitchノードを追加します。
ObjectMergeが空振りした、つまり現状のポイント数がゼロだった場合はPack処理を通らないようにします。
Select Inputにnpoints(0)!=0
を設定しましょう。これはSwitchノードの入力番号がゼロ、つまりObjectMergeのポイント数がゼロではなかったときにフラグが立つという処理になります。
Single Passを切り替えてみるとPackの処理が分岐されているのが分かるかと思います。
Switchに入るワイヤの表示が実線・破線と切り替わるところをご覧ください
階層を上がって挙動を確認してみましょう。
バッチリですね。
Attributeの追加オプション
あとはあったらいいな機能の追加とUIの作り込みを行っていきましょう。
連番アトリビュート
Packed Primitiveに連番アトリビュートをつけたり外したりしましょう。これはEnumerateという便利なノードがありますのでそちらを利用しましょう。
もちろん、連番アトリビュートをつけるときだけそのノードを通るようにしておきたいですね。Switchで分岐しましょう。
UIの作成
パラメータを適切に露出し、オペレーションミスを減らし直感的に操作できるよう、インタラクションも制御します。
Edit Parameter Interface
ひとつ上の階層に上がり、Subnetを選択してEdit Parameter Interfaceを実行しましょう。これでUIを編集できるようになります。
Input1~4を非表示にする
ここはマストな作業ではないのですが、僕の個人的なフローとして不要なパラメータは非表示にします。
- Input1~4を選択
- Invisibleにチェックを入れる
- Applyを押す
連番アトリビュートコントロール用フラグを作る
- 左側のCreate ParametersペインからToggleを選択
- 右側のExisting Parametersペインにドラッグ・アンド・ドロップ
- nameパラメータを
create_attribute
と設定 - Labelを
Create Attribute
と設定
Enumerateのパラメータをすべて露出する
連番アトリビュートを作るときはEnumerateノードを利用するのですが、このパラメータはすべてHDAに露出させましょう。ひとつひとつパラメータを作り、合わせていくのは効率が悪いので一気に移植します。
Subnet内のenumerate1ノードを選択
Existing Parametersペインにドラッグ・アンド・ドロップ
ノードをドラッグ・アンド・ドロップするだけですべてのパラメータが参照状態を持った状態で作れるので便利ですね。みなさんもドラッグ・アンド・ドロップを使ってみてください。
整理のためフォルダに入れる
Shift+Cを押すと、先程移植したパラメータがそのままフォルダに入ります。
フォルダの設定を行う
- Labelに
Enumerate
と設定 - Tab Disable Whenに
{ create_attribute != 1 }
を設定 - Folder TypeをCollapsibleにする*5
重要なのは2です。先に作ったToggleスイッチのnameパラメータにcreate_attribute
を設定しましたが、ここが1ではない、つまりチェックが入っていなければEnumrateのパラメータがグレーアウトされて触れないようになります。
このように、UIをインタラクティブに制御することでユーザーが使いやすいツールを作ることができますね。
ちなみに以前HDAのUIについての解説をYoutubeの無料動画で解説したことがあるので下記も参考にしてください。4回の連載動画です。
Attributeに関するパラメータをフォルダにまとめる
同様の手順でパラメータを整理していきます。
- Shift+Cでフォルダを作る
- Labelを
Attributes
に設定する - Folder TypeをSimpleにする*6
Packに関するパラメータもすべて移植する
同様の手段です。
pack1ノードをドラッグ・アンド・ドロップ
フォルダを作ってパラメータを整理
- Labelを
Pack
に設定する - Folder TypeをSimpleにする*7
Switchの分岐コントロールをつなげる
switch2ノードがEnumerateの処理を分岐するところなので、ここのSelect Inputにch("../create_attribute")
を設定します。
これでHDAのCreate Attribute Toggleスイッチのオン・オフがSwitchの分岐と連動するようになりました。
UIの動作確認
こんな感じになりました。
HDA化する
残るはHDA化のみです。今回はバージョン管理などはせずシンプルなツールにしています。また配布しやすいようhipファイル内に埋め込みを行っていますので、シーンファイルを開くだけでツールのインストール等は不要です。
(注)業務でHDAを作成する場合はネームスペースやバージョン管理をしっかりと行うことをオススメします。詳しくは下記を参考ください。
Create Digital Assetsを実行
ツールをファイルに埋め込む
Save to LibraryをEmbedded
に設定します。これによってツールをファイルに埋め込むことができるので、簡単に配布するには最適な方法と言えます。
詳しくは下記URLを参考にしてください。
ツールの設定
- Iconを
SOP_pack
に設定*8 - Maximum Inputを
999
などの大きな値に設定
完成
こんな感じになりました!
まとめ
最後まで読んでくださった方、ありがとうございます。小さなネットワークかつ単純な機能のHDAですが、丁寧に説明しようと思うと無茶苦茶長文になってしまいました。
来年もいろいろなHDAを作っていくことでしょう。便利そうなのがあればまた来年ご紹介しますね!
昨日はsasaki_0222さんの記事、Houdini19 Karma 初見所感・備忘録・Tips - Qiitaでした。
明日はkurosawaさんの記事、Houdini Engine for Unreal Engine ことはじめ|くろさわ|noteです。
ではでは!