今まで HDA の中身を作ってきましたが、今回は HDA を外側から使いやすくするためのパラメータの作成について学習していきます。
どういうことかというと、 HDA 内のノードの中で家の形状などを大きく変える重要なパラメータを HDA の外側から調整することができるようにしていきます。
実際にやってみると理解しやすいと思うので早速やってみましょう。
まずは、 house HDA を右クリックして、「Type Propoerties」をクリックします。
すると、このような画面が出てきますので、「Parameters」タブをクリックして開きます。
そして、画像のように左側からパラメータをドラッグして追加します。
今回は Float パラメータをドラッグして追加します。
これを家のサイズのパラメータにするので、画像のように Name と Label を「size」に変更して Range を 5 ~ 20 に変更します。
(Houdini の長さの単位は基本的には m なので、5 mから 20 mです。)
また、 右側の Channels タブを開き、このパラメータのデフォルトの値を 10 に設定して Apply をクリックします。
すると、 house HDA のノードに size というパラメータが現れます。
これが基本的なパラメータの作り方です。
ただし、まだこのパラメータは HDA 内部のパラメータと連動しておらず変更しても何も起きないので、連動するようにします。
まず、 size パラメータを右クリックして「Copy Parameter」をクリックします。
そして、 house HDA をダブルクリックして HDA を開いた後、下の画像の手順で先頭の grid SOP の size パラメータの 2 つの値(10)を選択して右クリックし、「Paste Relative References」をクリックします。
すると、パラメーター欄に 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 の処理と連動するようになりました。
画像のようにパラメータを動かしてみると、家の大きさが変わるのが分かります。
先ほどのパラメータの基本的な作り方の他に、もう一つ便利なパラメータの作り方があります。
画像のように、 lot subdivision 1 ノードの Iteration パラメータをドラッグしてみましょう。
すると、このようにパラメータが一発で連動するようになります。
内部のパラメータをそのまま HDA のパラメータにする場合はこの方法が便利です。
今追加したパラメータの設定を変更しましょう。
内部のパラメータの名前だと何のパラメータなのか分かりずらいので、 Name と Label を complexity (複雑さ)に変更し、 Range を 1~10 にして 1 側をロックしておきます。
次に、 lot subdivision 1 ノードの Random Seed パラメータをドラッグし、 Name と Label を shape_random_seed に変更しておきます。
この調子でパラメータと範囲を追加していきましょう。
ちなみに、パラメータのデフォルト値はドラッグでパラメータを作った場合ドラッグした際に入っていた値になるのでちょうど良い値に変えてからドラッグするのがおすすめです。
(先ほどのようにドラッグして設定をした後は Apply ボタンをクリックすると設定が反映されます。)
これで一通りのパラメータの設定が終わりました。
house HDA のパラメータを変更して各パラメータの動作を確認しましょう。
ぐちゃぐちゃになってしまったパラメータをデフォルトの値に戻すには、パラメータを右クリックして「Revert to Defaults」をクリックするか、 Ctrl + 中クリック(マウスホイール押下)です。
HDA のパラメータでは、アセット(3Dモデル)を入力したりパラメータを可変長のリストにすることもできます。
より複雑なパラメータを構築できるように、家に窓や扉などの別アセットを取り付けるためのパラメータを構築しながら学んでいきましょう。
家の外周の形を利用して、壁にドアや窓などを設置することができるようにしていきます。
家の外周を利用するには「land shape」と名付けた(講座資料では黄緑色に設定した)ノード群の最後の dissolve flat edges ノードの出力を持ってくれば良いです。
普通に dissolve flat edges ノードの出力ピンから線を伸ばせば利用することができますが、今回は object merge SOP という便利な SOP を用います。
object merge SOP を配置しましょう。
そして、 object merge SOP の Object 1 パラメータに利用したい dissolve flat edges ノードをドラッグします。
すると、画像のように object merge ノードに dissolve flat edges ノードのジオメトリを持ってくることができました。
object merge ノードは直接線をつながなくてもオブジェクトの出力を持ってくることができるようになるノードです。
今回のように、遠くのノードの出力を持ってくる際に線が長くなりすぎて見づらくなったりするのを防ぐために使えます。
object merge がどのノードを参照しているかを線で見たい場合は、ネットワークビューの View > Show for Selected Nodes を有効にするとオレンジ色の線で表示させることができます。
次に、 sort SOP を以下のように設定します。
上から見て右下に 0 番目のポイントが来るようになりました。
次に、 edge group to curve SOP をつなぎます。
すると、画像のようにポリゴンがカーブ(線)に変換されます。
そして、 poly cut SOP をつなぎ以下のように設定します。
すると、 0 番目のポイントのところでカーブが切断されます。
次に、 distance along curves SOP をつなぎます。
すると、 Geometry Spread Sheet の Point に dist アトリビュートが追加されます。
Point の番号がバラバラになっているので、 Sort SOP を以下のように設定してつなぎます。
すると、ポイントが dist アトリビュートの昇順で並びます。
つぎに、 min max average SOP をつなぎ、以下のように設定して dist の最大を max_dist アトリビュートとして追加します。
そして、 point wrangle SOP をついかして、下記の VEX コードを追加します。
f@dist /= f@max_dist;
すると、 dist パラメータが 0 から 1 の間の値になります。
名前は「convert_to_rate」としておきましょう。
つぎに、 extract point from curve SOP をつなぎます。
Distance Attribute パラメータには「dist」を設定します。
Cut Value パラメータを 0 から 1 の範囲で変化させると、新たに生成されたポイントがカーブ上で移動します。
次に、 copy to points ノードをつなぎ、画像のように box SOP をつなぎます。
先ほど追加したポイントの場所に box が配置されます。
つぎに、以下のリンクからドアのモデルをダウンロードしてください。
私が Blender で作ったドアのモデルです。(プレゼントです)
今回はこのドアのモデル壁に配置します。
もちろん HDA でドアを作ったり、お好きなモデリングツールで作ったりして使用してももんだいありません。
名前は「Door.fbx」として以下の階層となるように配置します。
Project
├ house.hip
├hda
└fbx
└Door.bbx
そして、 House Geometry Node の直下(house HDA が置かれている場所)に file SOP を配置し、Geometry File パラメータに $HIP/fbx/Door.fbx
と記述します。
そして File SOP に表示フラグを立てるとドアが表示されます。
$HIP
という表記は .hip ファイル(または .hipnc .hiplc)が配置されているディレクトリを指しています。
ここで、 house HDA と file SOP の表示フラグを切り替えると分かるのですが、ドアのモデルが非常に大きいです。
これは、 Blender と Houdini で長さの単位が異なるからです。
Blender では cm (センチメートル)で、Houdini では m (メートル)なので、 Blender で作ったモデルを Houdini に持ってくると 100 倍の大きさになってしまいます。
transform SOP をつなぎ、画像のようにパラメータを設定すると正しい大きさになります。
ついでに、前の向きも異なるのでY軸回転をさせています。
このようなことはツールをまたいでモデルを使用しているとたまに起きるので、このように適宜対処していきましょう。
null SOP をつなぎ、 door という名前に変更して分かりやすいようにしておきます。
このドアのモデルを HDA の内部で利用できるように仕組みを作っていきます。
house HDA の内部の box SOP を削除し代わりに object merge SOP をつなぎます。
次に house HDA を右クリックして Type Properties をクリックし、 Parameters タブをクリックしてパラメータ作成画面を開きます。
そして、 object merge の Object 1 パラメータをドラッグして HDA のパラメータに追加します。
すると、 object merge SOP の Object1 パラメータに chsop("../objpath1")
というパラメータが記述されています。
そして、 HDA のパラメータには Object1 パラメータが追加されています。
これにより、 HDA の外部から HDA 内にノード(オペレータ)のジオメトリを持ってくることができるようになりました。
door (と改名した null SOP)を Object1 パラメータにドラッグします。
そして、 merge SOP を output ノードの前に挟みます。
そして、先ほど作成したノード群の最後の copy to point SOP の出力を merge SOP につなぎます。
すると、ドアのモデルが HDA 内に取り込まれ、 Extract Point from Curve SOP の Cut Value パラメータを 0 から 1 の範囲で動かすとドアが壁の周りを移動するのが分かります。
ドアの向きを制御できていないので、壁の向きに合わせてドアが回転するようにしましょう。
まず、 object merge 1 ノードから二股に分かれるように poly extrude SOP をつなぎ、画像のようにパラメータを設定し、敷地から壁を作ります。(壁を生成した部分から object merge SOP で取得してきても良いです。)
そして、 Normal SOP をつなぎ、画像のようにパラメータを設定し、 Primitive のアトリビュートに N (Normal)を持たせます。
この面の向きを表す 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);
}
このコードは、 xyzdist 関数という関数を用いて Input0 のポイントの位置(v@P)に最も近い Input1 の primitive の番号を取得し、それの N の値を prim 関数で取得して N アトリビュートに保存しています。
xyzdist 関数はこのようにある位置から最も近い primitive の番号(と UV の位置)を取得することができる関数です。
この関数は処理した結果の値を受け取る方法が特殊で、普通は返り値として 返り値 = 関数(引数)
といったように関数の処理結果を受け取ることが多いのですが、この関数は引数に渡した変数の中に処理結果を代入します。
こういった仕組みは他の言語でも見られることがある、参照渡しと呼ばれるものです。
xyzdist 関数のドキュメントを見てみましょう。
矢印の行が今回の使い方を表す記述ですが、 prim と uv の引数の前に &
が付いています。
これが参照渡しを表す記号で、ざっくり説明すると普通の引数は変数の値を関数内に渡す(変数そのものについては何も渡さない)のに対して、参照渡しの引数では変数への参照を渡します。
これにより、関数の中から関数の外の変数に値を代入することができます。
少しテクニカルな仕組みですが、とりあえずドキュメントで引数に &
が付いている場合は変数の中身が関数によって書き換えられることを覚えておきましょう。
VEX コードで if(prim > -1)
としているのは、近くの Primitive が見つからない場合は -1 が代入されるからです。
それではこのコードの処理結果を見てみましょう。
下の画像の手順のように、 3D ビューポートの右側にあるポイントの Normal を表示するボタンを有効化し、 wrangle ノードに表示フラグを立てて、 extract point from curve SOP の cut value パラメータを 0 から 1 の値で動かします。
すると、画像のように壁の向きに合わせて Normal の向きが変わっていることが確認できます。( geometry spread sheet で確認してみるのも良いです)
次に、 copy to point の右の入力(Input1)に point wrangle をつなぎ、ドアの動きを確認してみましょう。
先ほど設定した Normal の方向にドアが向いていることが分かります。
ちなみに、少し難しい話なので読み飛ばしてもらってもよいのですが、 Normal のようなベクトル(3つの値のセット)では物体の任意の回転は表せないので注意してください。
ベクトルは向きしか表すことができないので今回の場合ドアが向いている方向を軸とした回転は表せません。
任意の回転を表すには4つの値(型で言うと vector4)が必要であり、そのためにクォータニオン(quaternion、四元数)という数学的な表現が用意されています。
今回はポイントの N (法線)アトリビュートに値を格納しましたが、 quaternion は orient(orientation、方向)や rot (rotation、回転)アトリビュートに vector4 型で格納すると、その向きで配置が行われます。
ただし、ドアが角の部分に配置される際に角を飛び出して配置されてしまう問題があるので、次はこの問題に対応します。
まず、ドアを持ってくる object merge SOP の下に point wrangle SOP をつなぎ、 radius と名付けて次のコードを記述します。
vector size = getbbox_size(0);
f@radius = sqrt(size.x*size.x + size.z*size.z)/2;
このコードは入力のバウンディングボックスから XZ 平面(水平面)上のドアの斜辺の大きさを三平方の定理により求めています。
バウンディングボックスというのは、ジオメトリ(3Dモデル)がすっぽり入るちょうどのサイズの直方体のことです。
次に、この値を用いてドアを xz 平面上ですっぽり覆う球体を作ります。
sphere SOP を配置して、画像のようにパラメータを設定します。
エクスプレッションは detail(-1, "radius", 0)
です。
Spare Input を使って raduis ノードと接続していることに注意してください。
すると、球体が作成できました。
この球体を用いてドアを配置するための Curve の角を切り取ります。
まずは copy to point SOP を画像のようにつなげて、 Curve の角に球体を配置します。
そして、画像のように boolean curve SOP をつなげてパラメータを設定すると、角が切り取られればいいのですがうまくいきません。(こういうこともあります)
対策として、画像のように boolean curve SOP の前に resample SOP を挟みます。
うまくいっているか確認するために copy to point SOP にゴースト表示フラグを立てて確認してみます。
まだうまくいっていない個所があるのがわかります。
対策として、 boolean SOP を画像のように設定し、重なっている球体を合体します。
すると、ようやく球体を配置している部分をうまく切り取ることができました。
boolean 系の SOP は内部でそもそも処理的に難しいことしているので理想通りに動かないことがよくあります。
このあたりは大変ですが、試行錯誤をしてをして乗り切りましょう。
次に fuse SOP を配置し、画像のように設定します。
すると、ポイントが Curve の無い位置にある場合に最も近くの場所に吸い付くようになります。
次に、画像のように wrangle に fuse を繋ぎ変えます。
output ノードの前の merge SOP に表示フラグを立てて、 extract from curve SOP の Cut Value パラメータを 0 から 1 の範囲で動かしてみると、ドアが角からはみ出すことなく壁を移動していくことが確認できます。
次に、この仕組みを複数のドアや窓を入力できるように改良します。
まず、オブジェクトの入力の個数を可変長にするためにオブジェクト入力のパラメータをリスト化します。
パラメータ編集画面を開き、画像の手順でパラメータのフォルダの機能を使いオブジェクト入力をリスト化します。
リスト(フォルダ)の中に入れたパラメータ名を object#
というように最後に #
を付けるのが重要です。
すると、 wall_object パラメータの横に + / - ボダンが現れ、パラメータを可変長にすることができるようになります。
+ ボタンを 3 回押して 3 つのオブジェクト入力を作成してみましょう。
オブジェクト入力欄が 3 つ作成され、それらの名前は object1, object2, object3 となっていることが分かります。
リストに入っているパラメータの名前に #
を入れると、そこにはリストの何番目のパラメータかという数字が埋め込まれ、それぞれのパラメータに別々の名前を付けることができます。
次に、画像のように色を変更したドアを 3 つ用意します。
それを、先ほど用意した 3 つの入力に入れてみましょう。
つぎに、 object merge SOP の Object1 パラメータを chsop("../object1")
chsop("../object2")
chsop("../object3")
と変化させると、object merge SOP でそれぞれのドアを持ってくることができることが確認できます。
次に、可変長のリストに何個であっても全てのドアを持ってくることができるように改良していきます。
まずは、foreach number ブロックを追加します。
そして、画像の手順で foreach end ノードの Iterations(繰り返しの回数)に house HDA の wall_object パラメータを反映するようにします。
すると、 wall_object パラメータが 3 なので Iterations をクリックして値を表示すると 3 が表示されます。
つぎに、 object merge SOP に Spare Input を追加して foreach_count2 ノード(Metadata)をドラッグしてつなぎ、Object 1 パラメータに以下のエクスプレッションを記述します。
chsop("../object"+(detail(-1, "iteration", 0)+1))
エクスプレッションを記述する際、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 と変化させてそれぞれのドアが入力できていることを確認してみましょう。
Single Pass パラメータはループの特定回数目だけを実行するオプションで、動作確認などに使用できます。
確認が出来たら Single Pass は無効にしておきましょう。
これで、リストのドアをすべて取り込むことができる仕組みができました。
次に、これを先ほど作成した壁に沿ってオブジェクトを配置する機能と統合し、リスト入力したオブジェクトを壁に沿って配置できるようにします。
先ほど作成したノード群の object merge ノードを削除し、その下の画像の部分を foreach ブロックの中に取り込みます。
以下の画像のように行うとスムーズです。
次に、壁に追加するオブジェクトの位置を調整するためのパラメータをリストに追加します。
以下の 3 つの画像のようにパラメータを追加します。
すると、パラメータが以下のように変化します。
extract point from curve SOP に Spare Input を追加して foreach の Metadata と接続し、 Cut Value パラメータに以下のエクスプレッションを追加します。
ch("../position"+(detail(-1, "iteration", 0)+1))
このエクスプレッションは先ほど object merge SOP に追加したものとほとんど同じで、 position1, position2, position3 … パラメータを取得するものです。
house HDA の position パラメータをそれぞれ動かしてみると、対応するドアが移動することが確認できます。
次に、 transform SOP を画像の位置に 3 つ入れます。
そして、それぞれに以下のエクスプレッションを記述します。
ch("../front_offset"+(detail(-1, "iteration", 0)+1))
ch("../floor_height")*(ch("../floor"+(detail(-1, "iteration", 0)+1))-1)
ch("../height_offset"+(detail(-1, "iteration", 0)+1))
それぞれ、ドアの前方向のオフセット(微調整用の移動値)、何階に設置するか、高さ方向のオフセットを指定するエクスプレッションです。
以下のように動作するようになります。
これで、壁の自由な位置にオブジェクトを配置することができるようになりました。
今回の内容はここまでです。
ノードの整理を行っておきましょう。
.hip と HDA を忘れずに保存しておきましょう。
お疲れ様です。