みなさんWrangleやVOP好きですよね??これらに感じる魅力のうちの少なくない部分が、書き捨てのコードスニペットなどを手軽に作れる部分にあると私は思っています。
これらは上流までのジオメトリ構造やノードからはある程度切り離された環境になっており、これから作ろうとしているロジックのみに可能な限り集中できるような仕組みとなっています。
このようなことをAPEXでもやりたいわけですが、APEXを用いたリグでは、APEXそのものの大きな利点でもある遅延評価を前提とする特徴から、Wrangleなどと違って上流までで定義したAPEXリグをそのまま最下流まで保持し続ける必要があり、APEXリグそのものが、対象となるキャラクターのジョイントの構造や既存リグのロジックなどに強く依存する形になっています。
なので、リグの機能を追加する際に既存のリグをできるだけ意識しないで済むように、という形にするのがなかなか難しそうです。
ということでなんとかそれっぽいことができないものかといろいろやってみた末の現状の着地点を共有できればと思います。
環境
Houdini 21.0.512
やりたいこと
基本的な用途としては補助骨制御のためのリグを既存のGraphに追加するためのものを想定しています。
そのため、主要な体幹骨の姿勢を入力としてそれを任意のロジックで加工して対象の補助骨の姿勢に対して出力する、というようなGraphを既存のFK TransformやBone Deformなどが入っているAPEXリグに対して追加していきたいのですが、
この段階ではすでにAPEXリグの中身は多数のTransformObjectノードと親子関係を表現するためにそれらをつなぐワイヤなどで非常に見通しの悪い状態になっていることがほとんどです。
このような状態からそこにさらに全身分の補助骨のリグを追加していくのはかなりつらいので、VOPノード的に一つのノードに部位ごとなどの任意の単位でリグのロジックを構築し、Autorig Component SOPのように複数のノードを直列につないでいくことで、関心領域を適切に分割しつつ全身分のロジックを構築できる状態を目指します。
さらに言うと、私の場合はここで組んだ補助骨用のリグをゲームエンジン向けに出力することを前提にしたかったので、Houdiniのことだけ考えるならもっとストレートなやり方がありそう、みたいな場面はところどころあるかと思います。
公式のやり方
先ほど説明したワークフローですが、実は公式?のやり方があります。
それが最新のHoudini21.0でAutorig Componentに追加されたFuse Graph Componentを使用するやり方です。
詳しいことはドキュメントを参照してもらえればと思いますが、簡単に言うとApexグラフの編集差分をマージすることができるComponentになっています。
例として以下の画像のようなシンプルなAPEXリグを考えます。

これを少しだけ編集してpoint_0に対する入力に固定値を乗算して加工するようなリグに変更します。

そして編集前のリグをPack Folder SOPでBase.rigとしてPackして、Fuse Graphを選択したAutorig Component SOPの一つ目のinputにつなぎつつ、2つ目のinputに編集後のリグをPackせずにつなぐと以下のようなAPEXグラフが出力されます。

出力後の画像の緑色になっているMultiply<Vector3>ノードがFuse Graphによって新しく追加されたノードになっていて、2番目のinput内のMultiplyノードの接続情報から1番目の入力のAPEXグラフの適切な場所にユーザーが追加したノードを差し込んだりしてくれます。
この例だけ見ると Autorig Componentいる…? っていう感想になるかと思いますが、Fuse Graphの便利なところは、リグの関係のない部分を削除した状態で編集できるという部分です。
ここでの編集においては、入力の情報とそれをどのように加工してpoint_0のTransformObjectノードに入力するか、という部分に集中したかったので関係のないノードを削除してしまいました。これを先ほどのAutorig Componentの2番目の入力に改めてつなぐと、

このように編集内容に関連しない周りのノードを削除してしまっても全く同じ出力が得られます。
このようにFuse Graph Componentは入力した2つのAPEXグラフの同名のノードを結合しつつ、ノードの削除などは行わずに追加された差分のみを既存のグラフに追加することができます。
この例では小規模のAPEXリグを用いたためあまりありがたみを感じないかもしれませんが、これを複雑なジョイント構造を持つキャラクターのリグで同様のことを行いたい場合には、事前に不要なノードの整理をしつつ、大量のTransformObjectをはじめとするノード群をかき分けることなく快適にリグの編集作業を行うことができるようになります。
このワークフローはドキュメントにも
This method provides a modular workflow for making changes to your rig.
と記載のある通り、既存リグの編集における非プロシージャルのちょうどいい粒度のモジュラーワークフローを提供してくれます。
既存のAutorig Componentなどではカバーできないけど、わざわざ新しくComponentやHDAを作るほどでもない、みたいなまさにWrangleやVOPでカバーしていたような状況にピッタリなComponentです。
欠点
Fuse Graphは基本的にはとても便利なComponentなのですが、一つ欠点があります。
先ほど同名のノードを結合しつつと書いたときに察した人がいるかもしれませんが、Fuse Graphは2つの入力APEXグラフ内に同名のノードが存在する場合はそれらを容赦なく結合して一つのノードにします。
それをいくつかのノードを削除してから編集した下記のグラフ(赤枠のmultiply1ノードが同名で被り)をFuse Graphでマージすると

2番目の入力グラフのmultiply1ノードは1番目の入力グラフのmultiply1ノードに結合されてなくなり、resultポートの接続のみ結合されたノードに追加されています。
1番目の入力に結合されると書いた通り、ポートの値を手動で編集していた場合には1番目の入力グラフ側のノードのものが残り、そこから2番目の入力グラフの接続情報を適用するようで、よく見ると同名ノードのinput側ポートへの接続は2番目の入力グラフのものが残る、という状態になっています。

これのせいで、Fuse Graphを使用して既存グラフを拡張し、さらに別の機能を別のFuse Graphでマージする、という形をとった場合にMultiplyノードやAddノードなどのよく使うノードをそのまま使ってしまうと、そのノードが全く関係のない場所に作られ、全く関係のないノード同士で接続されていても問答無用で結合されてしまうわけです。
しかも万が一これが起こってしまった場合に、マージ後のグラフは大量の関係ないノード群であふれているので原因の箇所が非常に見つけづらい!
これが私のやりたかったフローと非常に相性が悪く、Fuse Graphの使用を前提として解決したい場合は、同名のノードを残さないよう手動で命名を管理するか、編集した側のノードに同名のノードが存在しなくなるように後処理するComponent Scriptを挟むなどの必要があり、どれも労力に見合わない気がしたので今回は別の方法を取ることにしました。
ここで言うComponent Scriptとは、APEXグラフそのものを作成・編集するためのAPEXグラフのことを指します。これが正式名称なのかはよくわからないですが、他に簡潔な呼び方も見つからないので本記事ではComponent Scriptで統一します。
方針
というわけで現状たどり着いた方法としては、プレースホルダノードを定義し、グラフのマージ自体はMerge SOPで行う、という方法です。
APEXグラフ編集の際には、TransformObjectノードの代わりに自前で作成したプレースホルダノードを使用し、そこからMatrix4などの入出力を行う形にします。下記の画像で言うと両端のノードが特に機能を持たないプレースホルダノードとなっていて、これをTransformObjectノードの代わりに使用するイメージです。

マージについては、APEXグラフそのものはただのpointとpolylineの集合である、というのは知っている方も多いと思います。なのでAPEXグラフ自体には既存のSOPノードなどが普通に使えるようになっていて、今回のようにMerge SOPを使用すると2つ以上のAPEXグラフを雑に結合することができます。
知っての通りMerge SOPは同一のnameを持つpointを結合、なんて機能は持ってないので2つのAPEXグラフをそのままマージできます。その際同名のノードが2つ以上存在する可能性もありますが、APEXは複数の同名ノードの存在を許さないため、どこかのタイミングで被らないようにリネームするようになっています。

こうして2つのAPEXグラフがマージされた状態で、プレースホルダーノードを削除し、そこにつながっていた入出力ポートを既存のTransformObjectノードなどにつないであげる単純なComponent Scriptを挟んであげれば、ノード名の被りを気にする必要なくFuse Graphと同じようなことができそうです。
プレースホルダノードをわざわざ作らなくてもTransformObjectそのままで同じことはできると思いますが、最初に書いた外部出力が必要な都合で入出力に使用する型を制限したり、座標系の違いの吸収の処理の挿入などをしたかったのでこのような形に落ち着いています。
具体的にやったこと
ここから具体的にHDAを作るにあたって行ったことなどをtips的にかいつまんで記述していきます。
HDA内でAPEXグラフを編集できるようにする
VEXやSolverノードと違って、APEXグラフそのものはGeometryになっているので、HDA内でその編集をするにはどうすればいいのかというと、早い話Stash SOPやAPEX Graph SOPがやっていることと同じことをやればいいわけです。
HDA化する際などのParameter Interfaceを編集する際に、APEX Graph SOPなどのGeometryDataをHDAのParameterに昇格することでここに編集したAPEXグラフのGeometryが保存されるようになります。

さらに、HDA化の際のNodeタブのDive Targetの項目に対象のAPEX Garph SOPなどのパスを設定すると、HDAを選択するだけで指定したSOPノードのAPEXグラフをAPEX Network Viewペインで表示することができます。

プレースホルダノードを含む自作ノードの作成
自作ノードについては、APEX Graphノード内に任意の処理を含むSubnetノードを作成し、その状態でAPEX Graph SOPのSubgraphタブのSave To Diskで保存することができます。
この際の保存先がhipが読みに行けるパスの/apexgraphという名前のフォルダ内に指定されていると、Houdini起動時などに自動で読み込まれてTabメニュー内に表示されるようになります。
他のhipなどでも使いたい場合は自作のPackage内に/apexgraphフォルダを作成してそこに保存しましょう。
同様にAutorig Componentの自作ComponentもSaveタブから/apexcomponentフォルダ内に保存することで自動的にComponent Sourceの欄に追加されるようになります。
port名については、inとoutで別れていれば同名のportを使用することができますが、Component Scriptから扱うときにはport名_in/outと指定しなくてはいけないので、注意しましょう。
プレースホルダの自動作成
プレースホルダの置き換えの際には、現状はプレースホルダノードの名前から置換先のTransformObjectノードを特定するようにしています。つまりこの方法だとプレースホルダノードの名前には対象のKineFx Jointの名前を正確に指定する必要があるということです。
これを毎回手入力でやるのはさすがに…という感じなので、HDAのParameterにinputのCharacter Streamに含まれるKineFX Jointの一覧から任意に選択できる仕組みを作り、その情報からプレースホルダノードを作成するComponent Scriptを実行し、その結果をAPEX Graphノードに入力するようにしています。
これについてはGroup Joint SOPなどを見ればわかりますが、pythonのkinefx.uiモジュールの内のrigtreeutils.selectPointGroupParam()を使用することで実現できます。
ただし、今回の場合はHDAへの入力はPackされたCharacter Streamになるので、selectPointGroupParam()へ入力するkwargsのnodepathのキーにHDA内のUnpack Folder SOPなどへのパスを指定することで、Character Stream内部のKineFX Jointを選択することができるようになります。
from kinefx.ui import rigtreeutils
kwargs['nodepath'] = 'path to node'
rigtreeutils.selectPointGroupParm(kwargs)
リグの動作確認をできるようにする
Autorig Component SOPのように編集したリグの動作をその場で確認できるようにするには、HDAのDefault StateにapexanimateのViewer Stateを指定することで、ビューポート上でEnterキーを押すなどしてAnimateモードに入れるようになり、その時点でのリグの動作確認を行うことができます。

グラフのレイアウト
このHDAで組んだ小さなAPEXグラフたちは、あとで外部出力の処理を行うために、既存のリグへマージする前にBase.rigとは別のフォルダーにも随時マージしていってるのですが、普通にマージするとAPEX Network Viewの中央に重なり続ける形でマージされてしまいます。
このようなAPEXグラフの視覚的な整理を自動で行いたい場合、APEX Network ViewペインでLキーを押したり、APEX Layout Graphノードや、APEXのgraph::Layoutノードを使用すると思いますが、これによるレイアウトが非常に見づらく、
代替策としてWrangleでAPEXグラフのBounding Boxを取得し、それをもとに重ならないようにTransform SOPでグラフ全体を移動してからマージするようにしています。
まとめ
ぶっちゃけ大抵の用途ではFuse Graphで事足りるような気がしている(左右のミラーリングやrestlocalの自動入力などもできる)のですが、APEX用のHDAを自作するうえでこの辺りの情報がまだまだ足りていないような気がしたので記事にしてみた次第です。
APEXはComponent Scriptの存在のおかげで、APEXでAPEXを拡張するという、いわゆるメタプログラミング的なことができるようになっていて、さまざまなキャラクターや状況に合わせて非常に柔軟な拡張や処理が可能となっていたり、場合によってはSOPでゴリ押しできる部分なども含めてとても面白い環境だと思うので、今後も発展に期待したいところですね。


