【Houdini HDA 講座】#5 HDAのパラメーター・より複雑なHDAの構築

技術共有
2024-07-11 ツノ

今回の目標

今まで HDA の中身を作ってきましたが、今回は HDA を外側から使いやすくするためのパラメータの作成について学習していきます。

どういうことかというと、 HDA 内のノードの中で家の形状などを大きく変える重要なパラメータを HDA の外側から調整することができるようにしていきます。

実際にやってみると理解しやすいと思うので早速やってみましょう。

HDA パラメータの基本的な作り方

まずは、 house HDA を右クリックして、「Type Propoerties」をクリックします。

5_001.png

すると、このような画面が出てきますので、「Parameters」タブをクリックして開きます。

5_002.png

そして、画像のように左側からパラメータをドラッグして追加します。

今回は Float パラメータをドラッグして追加します。

5_003.gif

これを家のサイズのパラメータにするので、画像のように Name と Label を「size」に変更して Range を 5 ~ 20 に変更します。

(Houdini の長さの単位は基本的には m なので、5 mから 20 mです。)

5_004.png

また、 右側の Channels タブを開き、このパラメータのデフォルトの値を 10 に設定して Apply をクリックします。

5_005.png

すると、 house HDA のノードに size というパラメータが現れます。

5_006.png

これが基本的なパラメータの作り方です。

ただし、まだこのパラメータは HDA 内部のパラメータと連動しておらず変更しても何も起きないので、連動するようにします。

まず、 size パラメータを右クリックして「Copy Parameter」をクリックします。

5_007.png

そして、 house HDA をダブルクリックして HDA を開いた後、下の画像の手順で先頭の grid SOP の size パラメータの 2 つの値(10)を選択して右クリックし、「Paste Relative References」をクリックします。

5_008.gif

すると、パラメーター欄に ch("../size") と記述されます。

これは以前にも出て来た ch 関数で、 ch 関数とは他のパラメータを取得する関数です。

引数に記述されている "../size" という文字列は、先ほど作成した size パラメータを指しています。

../ という記述は、一つ上の階層という意味です。

プログラムで相対参照(Relative Reference)を表現する際によく使われる記述で、 ./ が今の階層を表していて ../ が 1 つ上の階層、 .../ が 2 つ上の階層を表します。

ここでいう今の階層というのが house HDA の中で、一つ上の階層が house HDA 自体を指します。

なので、 "../size" で house HDAの size パラメータを指します。

さきほどの「Copy Parameter」と「Paste Relative References」は、 ch 関数によるパラメータの参照を素早く設定するための方法です。

これで、 size パラメータが HDA の処理と連動するようになりました。

画像のようにパラメータを動かしてみると、家の大きさが変わるのが分かります。

5_009.gif

先ほどのパラメータの基本的な作り方の他に、もう一つ便利なパラメータの作り方があります。

画像のように、 lot subdivision 1 ノードの Iteration パラメータをドラッグしてみましょう。

5_010.gif

すると、このようにパラメータが一発で連動するようになります。

5_011.gif

内部のパラメータをそのまま HDA のパラメータにする場合はこの方法が便利です。

今追加したパラメータの設定を変更しましょう。

内部のパラメータの名前だと何のパラメータなのか分かりずらいので、 Name と Label を complexity (複雑さ)に変更し、 Range を 1~10 にして 1 側をロックしておきます。

5_012.png

次に、 lot subdivision 1 ノードの Random Seed パラメータをドラッグし、 Name と Label を shape_random_seed に変更しておきます。

5_013.gif

この調子でパラメータと範囲を追加していきましょう。

ちなみに、パラメータのデフォルト値はドラッグでパラメータを作った場合ドラッグした際に入っていた値になるのでちょうど良い値に変えてからドラッグするのがおすすめです。

(先ほどのようにドラッグして設定をした後は Apply ボタンをクリックすると設定が反映されます。)

  • lot subdivision 1 ノードの Irregularity パラメータを irregularity パラメータに

5_014.png

  • poly extrude 1 ノードの Distance パラメータを floor_height パラメータに

5_015.png

  • foreach_end 1 ノードの Iterations パラメータを floor_num パラメータに

5_016.png

  • lift_ridge ノードの Roof Angle パラメータを roof_angle パラメータに

5_017.png

  • lift_ridge ノードの Roof Angle パラメータを roof_angle パラメータに

5_019.png

  • poly_extrude 2 ノードの Distance パラメータを roof_thickness パラメータに

5_020.png

  • uv transform 4 ノードの texture_size パラメータを texture_size パラメータに

5_021.png

これで一通りのパラメータの設定が終わりました。

house HDA のパラメータを変更して各パラメータの動作を確認しましょう。

5_022.gif

ぐちゃぐちゃになってしまったパラメータをデフォルトの値に戻すには、パラメータを右クリックして「Revert to Defaults」をクリックするか、 Ctrl + 中クリック(マウスホイール押下)です。

5_023.gif

より複雑な HDA の構築

HDA のパラメータでは、アセット(3Dモデル)を入力したりパラメータを可変長のリストにすることもできます。

より複雑なパラメータを構築できるように、家に窓や扉などの別アセットを取り付けるためのパラメータを構築しながら学んでいきましょう。

object merge SOP

家の外周の形を利用して、壁にドアや窓などを設置することができるようにしていきます。

家の外周を利用するには「land shape」と名付けた(講座資料では黄緑色に設定した)ノード群の最後の dissolve flat edges ノードの出力を持ってくれば良いです。

普通に dissolve flat edges ノードの出力ピンから線を伸ばせば利用することができますが、今回は object merge SOP という便利な SOP を用います。

object merge SOP を配置しましょう。

5_024.png

そして、 object merge SOP の Object 1 パラメータに利用したい dissolve flat edges ノードをドラッグします。

5_025.gif

すると、画像のように object merge ノードに dissolve flat edges ノードのジオメトリを持ってくることができました。

5_026.gif

object merge ノードは直接線をつながなくてもオブジェクトの出力を持ってくることができるようになるノードです。

今回のように、遠くのノードの出力を持ってくる際に線が長くなりすぎて見づらくなったりするのを防ぐために使えます。

object merge がどのノードを参照しているかを線で見たい場合は、ネットワークビューの View > Show for Selected Nodes を有効にするとオレンジ色の線で表示させることができます。

5_027.gif

壁に沿って物体を配置する仕組みの構築

次に、 sort SOP を以下のように設定します。

5_028.png

上から見て右下に 0 番目のポイントが来るようになりました。

5_029.png

次に、 edge group to curve SOP をつなぎます。

5_030.png

すると、画像のようにポリゴンがカーブ(線)に変換されます。

そして、 poly cut SOP をつなぎ以下のように設定します。

5_031.png

すると、 0 番目のポイントのところでカーブが切断されます。

5_032.png

次に、 distance along curves SOP をつなぎます。

5_033.png

すると、 Geometry Spread Sheet の Point に dist アトリビュートが追加されます。

Point の番号がバラバラになっているので、 Sort SOP を以下のように設定してつなぎます。

5_034.png

すると、ポイントが dist アトリビュートの昇順で並びます。

つぎに、 min max average SOP をつなぎ、以下のように設定して dist の最大を max_dist アトリビュートとして追加します。

5_035.png

そして、 point wrangle SOP をついかして、下記の VEX コードを追加します。

f@dist /= f@max_dist;

5_036.png

すると、 dist パラメータが 0 から 1 の間の値になります。

名前は「convert_to_rate」としておきましょう。

5_037.png

つぎに、 extract point from curve SOP をつなぎます。

5_038.gif

Distance Attribute パラメータには「dist」を設定します。

Cut Value パラメータを 0 から 1 の範囲で変化させると、新たに生成されたポイントがカーブ上で移動します。

次に、 copy to points ノードをつなぎ、画像のように box SOP をつなぎます。

5_039.png

先ほど追加したポイントの場所に box が配置されます。

5_040.gif

.hip の外部からのモデルの取り込み

つぎに、以下のリンクからドアのモデルをダウンロードしてください。

私が Blender で作ったドアのモデルです。(プレゼントです)

ドアのモデル

今回はこのドアのモデル壁に配置します。

もちろん HDA でドアを作ったり、お好きなモデリングツールで作ったりして使用してももんだいありません。

名前は「Door.fbx」として以下の階層となるように配置します。

Project
├ house.hip
├hda
└fbx
 └Door.bbx

5_041.png

そして、 House Geometry Node の直下(house HDA が置かれている場所)に file SOP を配置し、Geometry File パラメータに $HIP/fbx/Door.fbx と記述します。

5_043.png

そして File SOP に表示フラグを立てるとドアが表示されます。

$HIP という表記は .hip ファイル(または .hipnc .hiplc)が配置されているディレクトリを指しています。

ここで、 house HDA と file SOP の表示フラグを切り替えると分かるのですが、ドアのモデルが非常に大きいです。

これは、 Blender と Houdini で長さの単位が異なるからです。

Blender では cm (センチメートル)で、Houdini では m (メートル)なので、 Blender で作ったモデルを Houdini に持ってくると 100 倍の大きさになってしまいます。

5_044.gif

transform SOP をつなぎ、画像のようにパラメータを設定すると正しい大きさになります。

ついでに、前の向きも異なるのでY軸回転をさせています。

このようなことはツールをまたいでモデルを使用しているとたまに起きるので、このように適宜対処していきましょう。

5_045.png

null SOP をつなぎ、 door という名前に変更して分かりやすいようにしておきます。

5_046.png

HDA 内への外部モデルの取り込み

このドアのモデルを HDA の内部で利用できるように仕組みを作っていきます。

house HDA の内部の box SOP を削除し代わりに object merge SOP をつなぎます。

5_047.png

次に house HDA を右クリックして Type Properties をクリックし、 Parameters タブをクリックしてパラメータ作成画面を開きます。

5_049.png

そして、 object merge の Object 1 パラメータをドラッグして HDA のパラメータに追加します。

5_048.gif

すると、 object merge SOP の Object1 パラメータに chsop("../objpath1") というパラメータが記述されています。

5_050.png

そして、 HDA のパラメータには Object1 パラメータが追加されています。

5_051.png

これにより、 HDA の外部から HDA 内にノード(オペレータ)のジオメトリを持ってくることができるようになりました。

door (と改名した null SOP)を Object1 パラメータにドラッグします。

5_052.gif

そして、 merge SOP を output ノードの前に挟みます。

5_053.png

そして、先ほど作成したノード群の最後の copy to point SOP の出力を merge SOP につなぎます。

5_054.png

すると、ドアのモデルが HDA 内に取り込まれ、 Extract Point from Curve SOP の Cut Value パラメータを 0 から 1 の範囲で動かすとドアが壁の周りを移動するのが分かります。

配置オブジェクトの回転

5_055.gif

ドアの向きを制御できていないので、壁の向きに合わせてドアが回転するようにしましょう。

まず、 object merge 1 ノードから二股に分かれるように poly extrude SOP をつなぎ、画像のようにパラメータを設定し、敷地から壁を作ります。(壁を生成した部分から object merge SOP で取得してきても良いです。)

5_056.png

そして、 Normal SOP をつなぎ、画像のようにパラメータを設定し、 Primitive のアトリビュートに N (Normal)を持たせます。

5_057.png

この面の向きを表す Normal を使って、ドアの向きを制御します。

ドアの向きを制御するには、ドアを copy to point SOP で置く際の場所を表すポイントに N アトリビュートを持たせます。

そうすることで、 copy to point SOP が N アトリビュートの方向を元に配置を行ってくれます。

point wrangle SOP を配置して、1つ目の入力(Input0)に extract point from curve SOP をつなげ、2つ目の入力(Input1)に先ほど作成した normal SOP をつなげます。

そして、以下の VEX コードを記述します。

int prim = -1;
vector uv;
xyzdist(1, v@P, prim, uv);
if(prim > -1){
    v@N = prim(1, "N", prim);
}

5_058.png

このコードは、 xyzdist 関数という関数を用いて Input0 のポイントの位置(v@P)に最も近い Input1 の primitive の番号を取得し、それの N の値を prim 関数で取得して N アトリビュートに保存しています。

xyzdist 関数はこのようにある位置から最も近い primitive の番号(と UV の位置)を取得することができる関数です。

この関数は処理した結果の値を受け取る方法が特殊で、普通は返り値として 返り値 = 関数(引数) といったように関数の処理結果を受け取ることが多いのですが、この関数は引数に渡した変数の中に処理結果を代入します。

こういった仕組みは他の言語でも見られることがある、参照渡しと呼ばれるものです。

xyzdist 関数のドキュメントを見てみましょう。

xyzdist VEX function | SideFX

矢印の行が今回の使い方を表す記述ですが、 prim と uv の引数の前に & が付いています。

5_059_2.png

これが参照渡しを表す記号で、ざっくり説明すると普通の引数は変数の値を関数内に渡す(変数そのものについては何も渡さない)のに対して、参照渡しの引数では変数への参照を渡します。

これにより、関数の中から関数の外の変数に値を代入することができます。

少しテクニカルな仕組みですが、とりあえずドキュメントで引数に & が付いている場合は変数の中身が関数によって書き換えられることを覚えておきましょう。

VEX コードで if(prim > -1) としているのは、近くの Primitive が見つからない場合は -1 が代入されるからです。

それではこのコードの処理結果を見てみましょう。

下の画像の手順のように、 3D ビューポートの右側にあるポイントの Normal を表示するボタンを有効化し、 wrangle ノードに表示フラグを立てて、 extract point from curve SOP の cut value パラメータを 0 から 1 の値で動かします。

5_059.gif

すると、画像のように壁の向きに合わせて Normal の向きが変わっていることが確認できます。( geometry spread sheet で確認してみるのも良いです)

次に、 copy to point の右の入力(Input1)に point wrangle をつなぎ、ドアの動きを確認してみましょう。

5_060.gif

先ほど設定した Normal の方向にドアが向いていることが分かります。

ちなみに、少し難しい話なので読み飛ばしてもらってもよいのですが、 Normal のようなベクトル(3つの値のセット)では物体の任意の回転は表せないので注意してください。

ベクトルは向きしか表すことができないので今回の場合ドアが向いている方向を軸とした回転は表せません。

任意の回転を表すには4つの値(型で言うと vector4)が必要であり、そのためにクォータニオン(quaternion、四元数)という数学的な表現が用意されています。

今回はポイントの N (法線)アトリビュートに値を格納しましたが、 quaternion は orient(orientation、方向)や rot (rotation、回転)アトリビュートに vector4 型で格納すると、その向きで配置が行われます。

配置物が角から飛び出さない仕組みの構築

ただし、ドアが角の部分に配置される際に角を飛び出して配置されてしまう問題があるので、次はこの問題に対応します。

5_060_1.png

まず、ドアを持ってくる object merge SOP の下に point wrangle SOP をつなぎ、 radius と名付けて次のコードを記述します。

vector size = getbbox_size(0);
f@radius = sqrt(size.x*size.x + size.z*size.z)/2;

5_059.png

このコードは入力のバウンディングボックスから XZ 平面(水平面)上のドアの斜辺の大きさを三平方の定理により求めています。

5_060_2.png

バウンディングボックスというのは、ジオメトリ(3Dモデル)がすっぽり入るちょうどのサイズの直方体のことです。

次に、この値を用いてドアを xz 平面上ですっぽり覆う球体を作ります。

sphere SOP を配置して、画像のようにパラメータを設定します。

5_060.png

エクスプレッションは detail(-1, "radius", 0) です。

Spare Input を使って raduis ノードと接続していることに注意してください。

すると、球体が作成できました。

この球体を用いてドアを配置するための Curve の角を切り取ります。

まずは copy to point SOP を画像のようにつなげて、 Curve の角に球体を配置します。

5_061.png

そして、画像のように boolean curve SOP をつなげてパラメータを設定すると、角が切り取られればいいのですがうまくいきません。(こういうこともあります)

5_062.png

対策として、画像のように boolean curve SOP の前に resample SOP を挟みます。

5_063.png

うまくいっているか確認するために copy to point SOP にゴースト表示フラグを立てて確認してみます。

5_064.png

まだうまくいっていない個所があるのがわかります。

対策として、 boolean SOP を画像のように設定し、重なっている球体を合体します。

5_065.png

すると、ようやく球体を配置している部分をうまく切り取ることができました。

boolean 系の SOP は内部でそもそも処理的に難しいことしているので理想通りに動かないことがよくあります。

このあたりは大変ですが、試行錯誤をしてをして乗り切りましょう。

次に fuse SOP を配置し、画像のように設定します。

5_067.png

すると、ポイントが Curve の無い位置にある場合に最も近くの場所に吸い付くようになります。

次に、画像のように wrangle に fuse を繋ぎ変えます。

5_066.gif

output ノードの前の merge SOP に表示フラグを立てて、 extract from curve SOP の Cut Value パラメータを 0 から 1 の範囲で動かしてみると、ドアが角からはみ出すことなく壁を移動していくことが確認できます。

5_068.gif

パラメータのリスト化

次に、この仕組みを複数のドアや窓を入力できるように改良します。

まず、オブジェクトの入力の個数を可変長にするためにオブジェクト入力のパラメータをリスト化します。

パラメータ編集画面を開き、画像の手順でパラメータのフォルダの機能を使いオブジェクト入力をリスト化します。

5_070.gif

リスト(フォルダ)の中に入れたパラメータ名を object# というように最後に # を付けるのが重要です。

すると、 wall_object パラメータの横に + / - ボダンが現れ、パラメータを可変長にすることができるようになります。

5_071.gif

+ ボタンを 3 回押して 3 つのオブジェクト入力を作成してみましょう。

オブジェクト入力欄が 3 つ作成され、それらの名前は object1, object2, object3 となっていることが分かります。

リストに入っているパラメータの名前に # を入れると、そこにはリストの何番目のパラメータかという数字が埋め込まれ、それぞれのパラメータに別々の名前を付けることができます。

次に、画像のように色を変更したドアを 3 つ用意します。

5_072.gif

それを、先ほど用意した 3 つの入力に入れてみましょう。

5_073.gif

つぎに、 object merge SOP の Object1 パラメータを chsop("../object1") chsop("../object2") chsop("../object3") と変化させると、object merge SOP でそれぞれのドアを持ってくることができることが確認できます。

5_074.gif

次に、可変長のリストに何個であっても全てのドアを持ってくることができるように改良していきます。

まずは、foreach number ブロックを追加します。

5_075.png

そして、画像の手順で foreach end ノードの Iterations(繰り返しの回数)に house HDA の wall_object パラメータを反映するようにします。

5_075_2.gif

すると、 wall_object パラメータが 3 なので Iterations をクリックして値を表示すると 3 が表示されます。

つぎに、 object merge SOP に Spare Input を追加して foreach_count2 ノード(Metadata)をドラッグしてつなぎ、Object 1 パラメータに以下のエクスプレッションを記述します。

chsop("../object"+(detail(-1, "iteration", 0)+1))

5_076.png

エクスプレッションを記述する際、Object 1 パラメータを右クリックして Expression > Edit Expression で出てくるウィンドウにエクスプレッションを記述します。

(このパラメータは元々文字列を入力するものなので、エクスプレッションを普通に記述してもそれがエクスプレッションであることを認識してくれないためです。)

記述したエクスプレッションの説明をします。

Medadata ノードには detail のアトリビュートとして iteration などのアトリビュートが入っており、 foreach が何回目のループなのかなどの情報が入っています。

detail(-1, "iteration", 0) で Spare Input から何回目のループかの値を取得しています。

iteration は 0 スタートの数字なので、 +1 をしてパラメータリストの 1 スタートに揃えています。

それを "../object" という文字列と連結させるために "../object"+(detail(-1, "iteration", 0)+1) としています。

それを chsop 関数に入れるとループの回数により、 chsop("../object1") chsop("../object2") chsop("../object3") … というようにそれぞれのリストのパラメータへの参照を作ることができます。

以下のように foreach end ノードの Single Pass パラメータを有効にして 0, 1, 2 と変化させてそれぞれのドアが入力できていることを確認してみましょう。

5_076.gif

Single Pass パラメータはループの特定回数目だけを実行するオプションで、動作確認などに使用できます。

確認が出来たら Single Pass は無効にしておきましょう。

5_077.png

これで、リストのドアをすべて取り込むことができる仕組みができました。

次に、これを先ほど作成した壁に沿ってオブジェクトを配置する機能と統合し、リスト入力したオブジェクトを壁に沿って配置できるようにします。

先ほど作成したノード群の object merge ノードを削除し、その下の画像の部分を foreach ブロックの中に取り込みます。

5_078.png

以下の画像のように行うとスムーズです。

5_080.gif

次に、壁に追加するオブジェクトの位置を調整するためのパラメータをリストに追加します。

以下の 3 つの画像のようにパラメータを追加します。

5_081.png

5_082.png

5_083.png

5_084.png

すると、パラメータが以下のように変化します。

5_085.png

extract point from curve SOP に Spare Input を追加して foreach の Metadata と接続し、 Cut Value パラメータに以下のエクスプレッションを追加します。

ch("../position"+(detail(-1, "iteration", 0)+1))

5_086.png

このエクスプレッションは先ほど object merge SOP に追加したものとほとんど同じで、 position1, position2, position3 … パラメータを取得するものです。

house HDA の position パラメータをそれぞれ動かしてみると、対応するドアが移動することが確認できます。

5_087.gif

次に、 transform SOP を画像の位置に 3 つ入れます。

5_088.png

そして、それぞれに以下のエクスプレッションを記述します。

ch("../front_offset"+(detail(-1, "iteration", 0)+1))

5_089.png

ch("../floor_height")*(ch("../floor"+(detail(-1, "iteration", 0)+1))-1)

5_090.png

ch("../height_offset"+(detail(-1, "iteration", 0)+1))

5_091.png

それぞれ、ドアの前方向のオフセット(微調整用の移動値)、何階に設置するか、高さ方向のオフセットを指定するエクスプレッションです。

以下のように動作するようになります。

5_092.gif

これで、壁の自由な位置にオブジェクトを配置することができるようになりました。

今回の内容はここまでです。

ノードの整理を行っておきましょう。

5_093.png

.hip と HDA を忘れずに保存しておきましょう。

お疲れ様です。