kick the base

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

Houdini: 今年作ったHDAの解説

今年も色々なHDAを作りました。本記事では1年の振り返りを行いつつ、案件ベースの特殊なHDAではなく、僕自身汎用的に使っているものを作り方と合わせてご紹介していきます。

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

1年の振り返り

今年もいくつかの勉強会を開催しました。with コロナ時代ということもあり、オンラインでの開催となりました。本当はオフラインで交流を深めたいところですが、オフラインのメリットも最大限活用しつつ、今後も開催できればと思っています。

Houdiniゆるゆる会

Houdiniで作成したシーンを持ち寄り、ゆるく意見交換するお茶会をスタートしました。

内容としては毎回1つのお題を決めて、それに沿ったシーンを各々事前に作成していただき、当日お披露目と意見交換をします。 こんなところ工夫したよ!とか、こういうの作りたいけどどうやるの?といった話をします。

参加者が持ち寄ったシーンファイルはこちらにて公開しています。

隔月で開催予定なので、お時間がある方はぜひご参加ください。今年最後のゆるゆる会を企画しています。12/26(土)、お時間がありましたらぜひご参加ください。

下記はアーカイブになります。

Houdiniもくもく会

各人Houdiniの作業を通じてともに学び合うもくもく会です。

Houdiniを使っている人、学びたいと思っている人が集まって、勉強できる場として立ち上げました。 身近にHoudinistがいない。作業をする習慣をつけたい。質問をしたい等々、Houdiniに関することなら何でもOKです。 参加者同士で教えあったり、議論したり、もくもくしたり、わいわいしましょう。という感じの活動となります。

こちらは不定期開催ですが、ざっくり隔月開催できればいいな、くらいなノリでやっています。

HDAのご紹介

閑話休題、こちらが本編です。作成手法ごとにカテゴリ分けし、制作意図と合わせ説明していきます。

ちなみにHDAの一連の作成方法はもっとシンプルな方法があるかもしれません。アドバイス等いただけましたら随時更新していきます。

執筆時の環境

  • Windows10 Pro
  • Houdini 18.0.532

まだ現行最新版の18.5に上げていないのですでに不要になっているHDAもあるかもしれませんが、作り方・考え方の参考ということで

データ配布

解説用シーン、作成済みHDAはこちらからダウンロードできます。

Clone

Min/Max/AverageSOPを題材にご紹介します。ビルトインのノードを改変し、新しいHDAを作る方法です。

作成の背景

実際にサンプルのネットワークを組みながら見ていきましょう。

f:id:kickbase:20201114211123p:plain

まずはattribwrangle_distノードで第1インプットの各ポイントと第2インプットのSphere(Primitive Type: primitiveのため中心にポイントがひとつとなります)との距離をdistアトリビュートに格納します。

@dist = distance(@P, point(1, "P", 0));

distの最大値と最小値を取得するためにMin/Max/AverageSOPを使用しますが、オリジナルのノードではAnalysis Methodを追加した際Prefixを自身で入力しなければなりません。これは不便。というわけでAnalysis Methodを指定するだけでそれに紐付いたPrefixを付与するノードを作成しました。使用法は下記の通り。

作成方法

オリジナルのMin/Max/AverageSOPを改造し、自身のHDAとして登録します。本ノードはかなりシンプルな作りなので初めての改造にオススメだったりします。

Min/Max/AverageSOPの作成

まずはSideFX LabsのMin/Max/AverageSOPを作成します。

ノードを変更可能状態に

右クリックからAllow Editing of Contentsを実行し、内部のノードを変更可能状態にします。

f:id:kickbase:20201114211228p:plain

内部のノードを読み解く

今回は"../prefix"というパラメータを参照しているところを見つけましょう。

attribpromote5ノード

New Nameチャンネルのエクスプレッション

`chs("../prefix" +(detail("../meta", "iteration", 0)+1)) + chs("../attribute")` 
attribpromote6ノード

Original Nameチャンネルのエクスプレッション

`chs("../prefix" +(detail("../meta", "iteration", 0)+1)) + chs("../attribute")` 

ここあたりが怪しいですね!

該当箇所の書き換え

こんな感じにしたらprefixパラメータを取り除くことができそうです。

attribpromote5ノード

New Nameチャンネルのエクスプレッション

`chs("../method" + (detail("../meta", "iteration", 0)+1)) + "_" + chs("../attribute")` 
attribpromote6ノード

Original Nameチャンネルのエクスプレッション

`chs("../method" + (detail("../meta", "iteration", 0)+1)) + "_" + chs("../attribute")` 
Subnetworkの作成

最終的にこのSubnetworkをHDA化します。

Subnetworkに移植

作成したSubnetwork内にMin/Max/AverageSOPの中身をコピペします。

f:id:kickbase:20201114211329p:plain

UIの整備

右クリックからEdit Parameter Interface...を実行し、UIを作成していきます。

f:id:kickbase:20201114211348p:plain

まずはinput #1~#4を全選択してInvisibleにしておきましょう。 *1

f:id:kickbase:20201114211402p:plain

続いてFrom NodesタブからMin/Max/AverageSOPのUIを移植します。ただしそのままドラドロするとエラーが出てしまいます。

f:id:kickbase:20201114211530p:plain

そんなときはサブネットの外側からパラメータを移植できるようForbid Linking Parameters from Outside this Subnetのチェックを外しましょう。

f:id:kickbase:20201114211544p:plain

今度はドラドロできましたね。

f:id:kickbase:20201114211602p:plain

最後に不要になったPrefix(prefix#)を削除してAcceptしましょう。

f:id:kickbase:20201114213549p:plain

HDA化

あとはお好みの設定でHDA化してあげればOKですが、ポイントは以下のとおりです。

f:id:kickbase:20201114212620p:plain

Operator Nameをkickbase::min_max_averageとする。名称のプリフィックスであるkickbase::の部分はネームスペースを表し、これを指定することでオペレーター名の衝突を避けることができます。

今後修正の可能性が多ければバージョン管理をしても良いでしょう。ここらへんのお話は本記事最後の参考文献にリンクを掲載しています。

古いMin/Max/AverageSOPの呼び出し停止

$HH下にOPcustomizeというファイル(拡張子なし)を作成し、下記設定を記述します。

ophide Sop labs::min_max_average

これでlabsネームスペースのMin/Max/AverageSOPはタブメニューから表示されないようになります。

Decoratorパターン

Attribute NoiseSOPを題材にご紹介します。

作成の背景

シーンとしてはPyroシミュレーションの下準備を想定してください。PigheadにPyroSourceをつなぎ、その後temperatureやdensityにノイズをかけるというのはよくやると思います。その際、僕がいつもやっていた間違いが「temperatureをタイポする」というものでした。

temperature、打ちにくくないですか!?

というわけで既存のアトリビュートをテキストボックスに候補として表示するUIをつけました。ここではデザインパターンで言うところのDecoratorパターンを採用して実現しています。

作成方法

先程の例でHDA化などの細かい話はしたので、ここでは構造にフィーチャーしてお話します。

Subnetworkの作成

これがHDAになる入れ物となります。そこにNull、Attribute Noise、Outputを作成し接続します。

f:id:kickbase:20201114213800p:plain

このSubnetworkの中にAttribute Noiseを丸々入れて、そこに機能を増やしていく方法はプログラムの世界ではコンポジションと呼ばれ、継承と同じくOOPの基本となる考え方です。

機能 意味
継承 A is a B.
コンポジション A has a B.

継承が一子相伝で進化を続ける北斗神拳だとしたら、コンポジションは一つの村にサウザーとトキを集めてそれぞれの技を使ってもらう感じというとわかりやすいかもしれません。*2

f:id:kickbase:20201114175711j:plain
継承

f:id:kickbase:20201114220722j:plain
コンポジション

Nullに機能を追加

get_point_attributes_in_menuと名前をつけたNullノードに既存のアトリビュートを取得し補完する機能を付与します。

Nullノードにはパラメータ名attribs、LabelにAttributesとしたStringを作成します。(最終的にSubnetworkのattribsを参照させます)

f:id:kickbase:20201114214013p:plain

そしてMenuタブに移り下記設定を行います。

  1. use Menuにチェック
  2. use MenuをToggle(Field + Multiple Selection Menu)に設定
  3. Menu Scriptに下記コードを記載

f:id:kickbase:20201114214050p:plain

inputs = hou.pwd().inputs()
result = []
if len(inputs):
    node = inputs[0]
    attrs = [x.name() for x in node.geometry().pointAttribs()]
    result = sum(zip(attrs, attrs), ())
return result

コードの解説は割愛しますが、ジオハンドル0に入ってきた情報に対してポイントアトリビュートを取得し、TokenとLabelのセットを設定してくれるということになります。

use MenuをToggle(Field + Multiple Selection Menu)にしたことによりテキストエリアの右側にプルダウンメニューボタンが追加されるわけですね。

Attribute NoiseSOPのパラメータ設定

ほぼすべてのパラメータをSubnetworkに露出させますが、attribsパラメータのみ以下のようにNullを参照します。

`chs("../get_point_attributes_in_menu/attribs")`

f:id:kickbase:20201114214116p:plain

HDA化

Operator Nameをkickbase::attribnoiseとします。

そして重要なポイントとして、NodeタブのDescriptive Parmにattribsを設定することです。これによりノードの下側にDescriptive Textバッジ*3が表示されるようになります。

f:id:kickbase:20201114214816p:plain

古いAttributes NoiseSOPの呼び出し停止

OPcustomizeに下記設定を記述します。

ophide Sop attribnoise

スクラッチ

Switch by FrameSOPを題材にご紹介します。

作成の背景

これはSwitchSOPで代用ができますが、タイムラインを汚さない(キーフレームに依存しない)Switchがほしいということで作ってみました。

ビルトインのSwitchSOPでは下図のようになります。タイムラインにキーフレームが打たれ、他のアニメーションと同期させて切り替えるにはつどタイムラインを調整する必要があります。

f:id:kickbase:20201114215024p:plain

そして下図が作成したSwitch by FrameSOPです。キーフレームに依存せず、他のパラメータとのリンクも簡単に行なえます。

f:id:kickbase:20201114215133p:plain

既存のSwitchSOPを利用するかと思いきやスクラッチでの実装としたのでその過程をご紹介します。

作成方法

Subnetworkを作成

これがHDAになる入れ物となります。

Attribute Wrangleを作成

calculate_frame_rangeという名前にし、Run OverをDetail(only onece)にします。

そして下記コードを実装します。

int max = chi("../frames");
i[]@frames = {};
for (int i = 0; i < max; ++i){
    int frame = chi("../frame" + itoa(i+1));
    append(@frames, frame);
}
@frames = sort(@frames);

f:id:kickbase:20201114215251p:plain

内容はシンプルで、SubnetworkのUI上で増やしたframeの総数をローカル変数maxとして取得し、forループで回してframesアトリビュート(配列)にframeパラメータを格納していきます。

最後にsortをしてあげることでユーザーがframeパラメータの順序を意識しなくて良い設計にしています。

Attribute Wrangleを作成

calculate_current_rangeという名前にし、こちらもRun OverをDetail(only onece)にします。

そして下記コードを実装します。

int frames[] = detail(0, "frames");
int max = len(frames);
i@current_id = 0;

for (int i = 0; i < max; ++i){
    if(frames[i] < @Frame){
        @current_id = i+1;
    }
}

f:id:kickbase:20201114215309p:plain

先ほど作成したframeアトリビュートをローカル変数frames配列に代入します。再度forループを回し、現在のフレームがどのフレームレンジにいるのかを判定します。これをcurrent_idアトリビュートに格納します。

current_idアトリビュートがわかれば表示したいジオメトリがわかるので、ObjectMergeSOPのオブジェクトパスに下記エクスプレッションを記載します。

opinputpath("..", detail("../OBJ_ID", "current_id",0))

f:id:kickbase:20201114215330p:plain

これでSwitchSOPの機能を使わず目的のジオメトリを取得することができました。SwitchSOPはAllow Editing Contentsを使うことができないのと、コンポジションで実装しようとしても可変長入力を切り替える仕組みを作るのは骨が折れそうなためこのような仕組みにしました。

SwitchSOPを利用する方向にこだわりすぎることなく、様々な方法を選択できるようになりたいものです。

HDAをマルチインプット入力可にする

参考URLにあるように、Edit Operator Type PropertiesウィンドウでMaximum Inputを999などの大きな値にするとマルチインプットの入力形状になってくれます。

ただし入力順を変更したり、接続を削除したりするとうまく更新されないことがあり、強制的にインプットの情報をリフレッシュさせる方法があるかどうか調査中です。ご存じの方は教えていただけると幸いです。

安全にHDA化するには

すでに解説していますが、OPcustomizeとネームスペース機能を利用することによってビルトインのノードを編集することなく同名のノードを作成・呼び出しすることが可能になります。

オリジナルのノードが必要になったらHscript Texportを使っていつでも呼び出せるので安心ですね。その方法については次の項目で解説します。

オリジナルのHDAを呼び出す方法

オリジナルのMin/Max/AverageSOPを呼び出す方法について解説をします。

操作 内容
Alt+Shift+T(メインメニュー > Window > Hscript Texportでも可) Hscript Texportウィンドウを開く
$ cd obj/WORK obj > WORKノード内にカレントディレクトリを移動
$ opadd labs::min_max_average labsネームスペースのmin_max_averageノードを作成する

ちなみに上記$マークはコマンドラインの先頭という意味を表すので、実際にHscript Texportに入力する際は無視してください

オリジナルのノードはいつでも呼び出せるため安心ですね。

参考文献

まとめ

思ったより長い記事になってしまいましたが、複数のアプローチのご紹介を行いました。みなさんの作業効率向上にお役立ていただければ幸いです。

*1:この工程はHDA化する際にInputの指定をすることで不要になるのですが、項目が出続けた状態で作業をするのは精神衛生上良くないので僕はこの段階で消しています

*2:世代じゃなかったらすみません

*3:青い文字でアトリビュートが表示されます