kick the base

Houdiniと、CG技術と、日々のこと。

Houdini: 複数入力に対しPackするツールを作った話

もうアドベントカレンダーの季節とか嘘でしょう…?

気を取り直して皆さんいかがお過ごしでしょう。僕は今年もHoudini三昧の毎日でした。

今回は可変長引数をとってストリームごとにPackするHDAの作り方についてお話をしたいと思います。ちなみに本記事を書いている本日現在、まだHoudini19を試していないのでもしそのもののノードが追加されていたらごめんなさい。ただ、ツールの作り方やその背景などは参考になるかも?と思って現実からは目をそらしながらポストします。

本記事はHoudiniアドベントカレンダー2021 6日目の記事です。

目次

データ配布

記事で使用したシーンはこちらからダウンロードできます。

  • Windows10
  • Houdini 18.5.563

まだHoudini19にしてなくてすみません…

ツール制作の背景

まずことの発端は入力ベース(ストリームベース)でPackしてくれるノードってビルトインではなさそう。というものでした。Assembleはポリゴンの接続を見てPackしてくれるのですが、それだと不都合が起こることもあるのですね。(下記画像を参考のこと)

f:id:kickbase:20211103202411p:plain

本来ならばShaderBallとRubberToyのふたつのPacked Primitiveにしたかったわけです。

ObjectMergeを用いたアプローチ

代替案としてObjectMergeを利用したアプローチがあるのでご紹介しておきましょう。ここではベターな手法ではないので採用しませんでしたが、この考え方自体はいつかどこかで役に立つときもあるでしょう。

f:id:kickbase:20211103204059p:plain

手順としては下記のとおりです。

  1. 読み込みたいメッシュのOperatorString($OS)にプレフィックスとしてpack_を付けます*1
  2. ObjectMergeの読み込み先として../pack_*としてワイルドカードで指定します
  3. 読み込むオブジェクトの干渉を避けるため、全体をSubnetでくるみます

手順3はやらなくても機能しますが、プログラムで言うところのネームスペース的な考えのもとサブネットでくるんでいます。サブネットを使用しないとどんな問題が起こるかと言うと、別の場所でも同じプレフィックスを持ったノードを作ったときにそちらも読み込まれちゃうわけですね。また読み込む場所を意識してプレフィックスを複数作るのもあまり賢い方法とは言えません。

この手法でも目的は達成できますが、ワイヤが接続されていないので処理が読みにくくなる点とプレフィックスを付ける作業でヒューマンエラーが発生しやすいという点で見送ることにしました。

Multi Packer

そんなこんなでMulti Packer(マルチパッカー)というツールを作りました。HDAはhipファイル内に埋め込み(embed)しているためシーンファイル単体でお試しいただくことができます。サンプルファイルをダウンロードの上使用感をお試しください。

それでは制作の流れを解説しながら追っていきましょう。

Subnet化

本ツールは最終的にHDA化することを目的としていたため、最初からサブネット化していきましょう。

まずはNullを作成し

f:id:kickbase:20211104210843p:plain

Shift+Cキーでサブネット化します。

f:id:kickbase:20211104210915p:plain

サブネット下層にダイブするとこんな感じです。何ら難しい操作はしていませんね。

f:id:kickbase:20211104211005p:plain

可変長引数を受け取る

本ツールはMergeSOPのように複数のノードを受け取る仕組みにしたいと考えています。そして内部処理としては入力されたノードごとにPackしたいわけですね。

ここに少し工夫が必要になります。

For-Each Numberを呼び出す

For-Each Numberを使っていきます。ここが後々効いてきます。

f:id:kickbase:20211104213251p:plain

Iterationsを動的に変更する

Iterationsオプションにopninputs("..")を指定します。

f:id:kickbase:20211104214001p:plain

opninputsは「接続された入力で最大の入力番号を返す関数です。この「最大の入力番号」というのが少々厄介なので、気になる人のために後ほど他の方法もご紹介します。

www.sidefx.com

この状態ではサブネットに何もつないでいないためIterationsはゼロですね。

f:id:kickbase:20211104214331p:plain

挙動を確認するためひとつ上の階層に上がり、Sphere、Box、RubberToyを接続してみましょう。

f:id:kickbase:20211104214454p:plain

再度サブネットに潜ると、Iterationsは3となっています。これで入力されたノードごとに処理をする準備ができました。

f:id:kickbase:20211104214506p:plain

opninputsの挙動確認

先程opninputs関数は最大の入力番号を返すと書きました。これからこの数を元にループを回していくわけですが、この関数ではちょっと困った状況も生まれてしまいます。

SubnetにつないでいたBoxノードのワイヤを切ってみましょう。

f:id:kickbase:20211104221427p:plain

これで実際にSubnetに繋がったストリームは2つですが、なんとopninputs("..")は3を指し示したままです。これが最大の入力番号ということなのです。*2

f:id:kickbase:20211104221512p:plain

本来ならば入力されたストリームの数だけループを回したいですよね。これはPythonを利用すれば取得できます。見ていきましょう。

Expression languageをPythonに変更する

デフォルトはHscriptになっていますが、こちらをPythonに切り替えましょう。

f:id:kickbase:20211104222123p:plain

Iterationsオプションの書き換え

opninputs("..")の部分を下記のように書き換えましょう。

len(hou.pwd().parent().inputConnections())

特に説明も必要ないかと思いますが、BlockEndノードの親、つまりSubnetの入力数を取得しています。これで歯抜けの入力があっても正しい値が返ってきました。

f:id:kickbase:20211104222632p:plain

f:id:kickbase:20211104222646p:plain

最大の入力番号には気をつけましょうという補足でした。

ただし、今回は可変長引数をとるHDAを作るという都合上Hscript Expressionを使っても問題ありません*3。そのためopninputs関数のバージョンで話を進めていきます。

入力されたジオメトリへのアクセス

For-Loopを用いて入力されたストリーム(ノード)の個数を調べられることはわかりましたが、個々のジオメトリにはどのようにアクセスすればいいでしょうか。

それにはObjectMergeを用います。

f:id:kickbase:20211105120506p:plain

ObjectMergeノードを作ったら、下記設定を行います。

  1. foreach_count1ノードの名前をmetaに変更する*4
  2. Object1パラメータに`opinputpath("..", detail("../meta/", "iteration", 0))`を設定する

f:id:kickbase:20211105120523p:plain

ラベルObject1をマウスの中クリックを押してみるとスフィアにアクセスできていることがわかります。

f:id:kickbase:20211105120619p:plain

入力の歯抜け問題について

ObjectMergeをBlock Endにつないでみましょう。Gather MethodがMerge Each Iterationになっているためループの中身がマージされて出力されていますね。

f:id:kickbase:20211105120731p:plain

Single Passの数値を変えていきましょう。

f:id:kickbase:20211105121134p:plain

Single Passが0のときはSphereにアクセスできます。

f:id:kickbase:20211105121239p:plain

Single Passが1のときは空振りになります。ここが歯抜けの部分になるわけです。

f:id:kickbase:20211105121331p:plain

Single Passが2のときはRubberToyにアクセスできます。

このように、歯抜けの入力がある際もエラーが出るわけではないためopninputs関数を使う方法で問題がないということですね。

しかしPackする際に少し気を使うところがあるのでそれは後ほど解説しましょう。

Pack機能の追加

ObjectMergeの下にPackノードを追加し、サブネットの出口にOutputノードを追加しましょう。また最初にサブネット化をするときに作ったNullノードは不要なので消してしまいましょう。

f:id:kickbase:20211105123627p:plain

ひとつ階層を上がって想定通りにパックされているか見ていきましょう。

f:id:kickbase:20211105123733p:plain

ちゃんとストリームごとにPacked Primitive化されていますが、歯抜けの部分までPackされてしまっていますね。Point/Primitives/Vertices/Packed Geosが3と表示されていますが、正しくは2になっていてほしいです。ここを修正していきましょう。

空振りしたときにPackされてしまう問題の修正

上述の通り、空振りした際にエラーは出ないのですが、なにもないものをPackしてしまうため問題が起こっています。つまり、ジオメトリが空だったらPackノードを通さないという処理を追加しましょう。

Pack処理の直後にSwitchノードを追加します。

f:id:kickbase:20211105124924p:plain

ObjectMergeが空振りした、つまり現状のポイント数がゼロだった場合はPack処理を通らないようにします。

Select Inputにnpoints(0)!=0を設定しましょう。これはSwitchノードの入力番号がゼロ、つまりObjectMergeのポイント数がゼロではなかったときにフラグが立つという処理になります。

f:id:kickbase:20211105125217p:plain

Single Passを切り替えてみるとPackの処理が分岐されているのが分かるかと思います。

Switchに入るワイヤの表示が実線・破線と切り替わるところをご覧ください

f:id:kickbase:20211105125617g:plain

階層を上がって挙動を確認してみましょう。

f:id:kickbase:20211105125759p:plain

バッチリですね。

Attributeの追加オプション

あとはあったらいいな機能の追加とUIの作り込みを行っていきましょう。

連番アトリビュート

Packed Primitiveに連番アトリビュートをつけたり外したりしましょう。これはEnumerateという便利なノードがありますのでそちらを利用しましょう。

www.sidefx.com

もちろん、連番アトリビュートをつけるときだけそのノードを通るようにしておきたいですね。Switchで分岐しましょう。

f:id:kickbase:20211115232002p:plain

UIの作成

パラメータを適切に露出し、オペレーションミスを減らし直感的に操作できるよう、インタラクションも制御します。

Edit Parameter Interface

ひとつ上の階層に上がり、Subnetを選択してEdit Parameter Interfaceを実行しましょう。これでUIを編集できるようになります。

f:id:kickbase:20211115232042p:plain

Input1~4を非表示にする

ここはマストな作業ではないのですが、僕の個人的なフローとして不要なパラメータは非表示にします。

  1. Input1~4を選択
  2. Invisibleにチェックを入れる
  3. Applyを押す

f:id:kickbase:20211115233125p:plain

連番アトリビュートコントロール用フラグを作る

  1. 左側のCreate ParametersペインからToggleを選択
  2. 右側のExisting Parametersペインにドラッグ・アンド・ドロップ
  3. nameパラメータをcreate_attributeと設定
  4. LabelをCreate Attributeと設定

f:id:kickbase:20211115233418p:plain

Enumerateのパラメータをすべて露出する

連番アトリビュートを作るときはEnumerateノードを利用するのですが、このパラメータはすべてHDAに露出させましょう。ひとつひとつパラメータを作り、合わせていくのは効率が悪いので一気に移植します。

Subnet内のenumerate1ノードを選択

f:id:kickbase:20211115234223p:plain

Existing Parametersペインにドラッグ・アンド・ドロップ

f:id:kickbase:20211115234328p:plain

ノードをドラッグ・アンド・ドロップするだけですべてのパラメータが参照状態を持った状態で作れるので便利ですね。みなさんもドラッグ・アンド・ドロップを使ってみてください。

整理のためフォルダに入れる

Shift+Cを押すと、先程移植したパラメータがそのままフォルダに入ります。

f:id:kickbase:20211115234638p:plain

フォルダの設定を行う
  1. LabelにEnumerateと設定
  2. Tab Disable Whenに{ create_attribute != 1 }を設定
  3. Folder TypeをCollapsibleにする*5

重要なのは2です。先に作ったToggleスイッチのnameパラメータにcreate_attributeを設定しましたが、ここが1ではない、つまりチェックが入っていなければEnumrateのパラメータがグレーアウトされて触れないようになります。

このように、UIをインタラクティブに制御することでユーザーが使いやすいツールを作ることができますね。

f:id:kickbase:20211115234732p:plain

ちなみに以前HDAのUIについての解説をYoutubeの無料動画で解説したことがあるので下記も参考にしてください。4回の連載動画です。

youtu.be

Attributeに関するパラメータをフォルダにまとめる

同様の手順でパラメータを整理していきます。

  1. Shift+Cでフォルダを作る
  2. LabelをAttributesに設定する
  3. Folder TypeをSimpleにする*6

f:id:kickbase:20211115235627p:plain

Packに関するパラメータもすべて移植する

同様の手段です。

pack1ノードをドラッグ・アンド・ドロップ

f:id:kickbase:20211116000835p:plain

フォルダを作ってパラメータを整理
  1. LabelをPackに設定する
  2. Folder TypeをSimpleにする*7 f:id:kickbase:20211116000926p:plain

Switchの分岐コントロールをつなげる

switch2ノードがEnumerateの処理を分岐するところなので、ここのSelect Inputにch("../create_attribute")を設定します。

これでHDAのCreate Attribute Toggleスイッチのオン・オフがSwitchの分岐と連動するようになりました。

f:id:kickbase:20211116001116p:plain

UIの動作確認

こんな感じになりました。

f:id:kickbase:20211116001411g:plain

HDA化する

残るはHDA化のみです。今回はバージョン管理などはせずシンプルなツールにしています。また配布しやすいようhipファイル内に埋め込みを行っていますので、シーンファイルを開くだけでツールのインストール等は不要です。

(注)業務でHDAを作成する場合はネームスペースやバージョン管理をしっかりと行うことをオススメします。詳しくは下記を参考ください。

www.sidefx.com

Create Digital Assetsを実行

f:id:kickbase:20211116200256p:plain

ツールをファイルに埋め込む

Save to LibraryをEmbeddedに設定します。これによってツールをファイルに埋め込むことができるので、簡単に配布するには最適な方法と言えます。

f:id:kickbase:20211116200409p:plain

詳しくは下記URLを参考にしてください。

www.sidefx.com

ツールの設定
  1. IconをSOP_packに設定*8
  2. Maximum Inputを999などの大きな値に設定

f:id:kickbase:20211116200657p:plain

完成

こんな感じになりました!

f:id:kickbase:20211116201241p:plain

まとめ

最後まで読んでくださった方、ありがとうございます。小さなネットワークかつ単純な機能のHDAですが、丁寧に説明しようと思うと無茶苦茶長文になってしまいました。

来年もいろいろなHDAを作っていくことでしょう。便利そうなのがあればまた来年ご紹介しますね!

昨日はsasaki_0222さんの記事、Houdini19 Karma 初見所感・備忘録・Tips - Qiitaでした。

明日はkurosawaさんの記事、Houdini Engine for Unreal Engine ことはじめ|くろさわ|noteです。

ではでは!

*1:当然ながらプレフィックスはなんでも良いです

*2:RubberToyの入力番号を取得しているわけです

*3:入力が歯抜けの場合については後述します

*4:ここは必須ではありませんが、detailアトリビュートへのアクセスするときに文字列が短くなるためやっています

*5:この設定は見た目に合わせてお好みで

*6:この設定は見た目に合わせてお好みで

*7:この設定は見た目に合わせてお好みで

*8:ツールのアイコンはお好みで設定してください。今回はPackノードのアイコンを拝借しています