kick the base

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

Houdini: Wrangleがわからない(分かった感ある)

今回はHoudiniのお話です。今までWrangleもVEXも手探りながら使ってきたのですが、詳しく挙動を見ていくとどうにも想定外の結果となることがあり、実は全然分かってなかったんじゃね?という疑問が浮かんできました。

本記事ではぼくが理解している部分、理解できていない部分を列挙することで事象を整理し、備忘録として記録するとともに、諸先輩方にアドバイスを頂けるきっかけになればという希望を託して書いています。また同じ問題に悩んでいる方(がいるとしたら)への情報共有になればと思い筆を執っています。

上述の通り本記事では執筆時点で疑問点が解決されていません。諸先輩方にご教授いただけると幸いです。

[追記]

Satoru Yonekura(@yone80)さんにアドバイスを頂き疑問が解消しました!解決編はふたつの記事に分けてポストします。まずはひとつ目が完成したので下記リンクからどうぞ。


執筆時の環境

  • Windows10 Pro
  • Houdini16.0.504.20

サンプルファイル

今回作成したサンプルファイルを下記に用意しました。

シーンファイル

現状理解している範囲

Wrangleには数多くの種類がありますが、Attribute Wrangleを基底にPoint Wrangle、Primitive Wrangle、Vertex Wrangleに分類されます。

ポイントとなる(であろう)ところは下記の通り

  • Point Wrangle、Primitive Wrangle、Vertex WrangleはRun Overのみが異なる。
  • Attribute WrangleとPoint Wrangleは全く同じ(多分)
  • Run OverはVEXの並列計算の実行回数を決定するととともに、編集対象(エレメント)を指定する

※Deformation Wrangle、Heightfield WrangleはVolume Wrangleを基底とし上記とは異なる挙動をするっぽいので今回の記事からは割愛します。

またVEXの並行処理とはRun Overで指定したエレメントひとつひとつに対して命令を実行することにあたると解釈しています。

事例1 BoxのPointに対し処理を行う

Box SOPの作成

f:id:kickbase:20170916013422j:plain

パラメータは一切変更せず、原点に作成したジオメトリ。本記事ではすべてのネットワークをこのBoxから始めるものとします。

pointwrangle_pos_vec01

f:id:kickbase:20170916013511j:plain

vector pos_vec = {1, 2, 1};
@P = pos_vec;
  1. 位置ベクトルpos_vecをローカル変数として定義
  2. Pアトリビュートに位置ベクトルを代入する

Run OverがPointのためすべてのポイントの位置を位置ベクトルと同ポジションに移動させるため、Boxの形状ではなくPointが1点に密集した形になります。

pointwrangle_pos_vec02

f:id:kickbase:20170916013506j:plain

vector pos_vec = {1, 2, 1};
@P += pos_vec;
  1. 位置ベクトルpos_vecをローカル変数として定義
  2. Pアトリビュートに+=演算子で位置ベクトルを加算する

Run OverがPointのためすべてのポイントのオリジナルの位置に対して位置ベクトルを加算します。このためBoxの形状を保ちつつ位置ベクトル分平行移動されます。

ここまではOK。だと思っています。

現状疑問がある範囲

事例2 BoxのPointに対しプリミティブアトリビュートを用いた操作を行う

ここから疑問点がでてきます。

pointwrangle_pt_attrib

f:id:kickbase:20170916013328j:plain

@P.y = @ptnum;
i@test = @numpt; //8

形状は変な形ですが、データとしては問題なし。@ptnum@numptも正しく取得できています。

pointwrangle_prim_attrib

f:id:kickbase:20170916013333j:plain

@P.y = @primnum; // なぜ4と5が?
i@test = @numprim; //6

ここが疑問。

ポイントナンバー0~3の@P.yには4.0が、4~7の@P.yには5.0が代入されています。この4.0または5.0という数値がどこから出てきたのかがわかりません。対して@numprimは正しく取得できています。(Boxは立方体なので6面ある)

事例3 Run Overの対象とアトリビュートが異なっている場合

pointwrangle_Cd_ptnum

Run OverがPoints、Pointのアトリビュートを参照した場合

f:id:kickbase:20170916013324j:plain

@Cd = rand(@ptnum); 
//カラーはすべてのポイントで正しくランダムになっている
//つまりrand関数の引数@ptnumはユニークになっている

想定通り。

pointwrangle_Cd_primnum

Run OverがPoints、Primitiveのアトリビュートを参照した場合

f:id:kickbase:20170916013337j:plain

@Cd = rand(@primnum); //多分@primnumには4、5が入ってるんだと思う

Point0~3、Point4~7が同じカラーとなっています。この謎が解ければ前述の疑問と同様に解決できるはずです。

primitivewrangle_Cd_ptnum

Run OverがPrimitives、Pointのアトリビュートを参照した場合

f:id:kickbase:20170916013320j:plain

@Cd = rand(@ptnum); //プリミティブナンバー1,4が同じカラーとなっている

これもどんな原理か不明。

primitivewrangle_Cd_primnum

Run OverがPrimitives、Primitiveのアトリビュートを参照した場合

f:id:kickbase:20170916013444j:plain

@Cd = rand(@primnum);
//カラーはすべてのプリミティブで正しくランダムになっている
//つまりrand関数の引数@primnumはユニークになっている

これらのことから現状Run Overとアトリビュートエレメントが一致している場合は意図したとおりの挙動となっていると思われます。

printfの挙動を確認する

Wrangleの疑問点と直接関係ありませんが、プリントデバッグは言語仕様の理解に欠かせないのでここで見ていきます。

pointwrangle1

f:id:kickbase:20170916013439j:plain

printf('pointwrangle1\n'); //pointwrangle1
/*
//想定では8回(ポイントの数だけ)繰り返されるものだと想っていた
pointwrangle1
pointwrangle1
pointwrangle1
pointwrangle1
pointwrangle1
pointwrangle1
pointwrangle1
pointwrangle1
*/

printf(sprintf('%g', int(@ptnum)) + '\n');
/*
//想定通り0~7の整数が出力される
0
1
2
3
4
5
6
7
*/

printf('--------\n');

printf(sprintf('%g', int(@primnum)) + '\n');
/*
//想定通り8回実行されているが、出力値が4と5なのは相変わらず謎
//0,1,2,3,4,5,0,1とかだったら分かるんだけど…
4
4
4
4
5
5
5
5
*/

疑問点はコメントの通り。

primitivewrangle1

f:id:kickbase:20170916013432j:plain

printf('primitivewrangle1\n'); //primitivewrangle1
/*
//想定では6回(プリミティブの数だけ)繰り返されるものだと想っていた
primitivewrangle1
primitivewrangle1
primitivewrangle1
primitivewrangle1
primitivewrangle1
primitivewrangle1
*/

printf(sprintf('%g', int(@primnum)) + '\n');
/*
//想定通り0~5の整数が出力される
0
1
2
3
4
5
*/

printf('--------\n');

printf(sprintf('%g', int(@ptnum)) + '\n');
/*
//数値の並び順が不明
1
2
3
0
2
5
*/

疑問点はコメントの通り。

attribwrangle1_detail

f:id:kickbase:20170916013427j:plain

printf('attribwrangle1_detail\n'); //attribwrangle1_detail
/*
//detailは1回だけ実行されるので想定通り
primitivewrangle1
*/

printf(sprintf('%g', int(@primnum)) + '\n'); //-1 (なぜ-1なのか)
printf(sprintf('%g', @numpt) + '\n'); //8 (正しく取得できている)

printf('--------\n');

printf(sprintf('%g', int(@ptnum)) + '\n'); //-1 (なぜ-1なのか)
printf(sprintf('%g', @numprim) + '\n'); //6 (正しく取得できている)

疑問点はコメントの通り。

上記実験から導き出される可能性として、printf関数は、同じ出力が繰り返される場合は一回の出力として処理されるということなんじゃないだろうかと思ってるんですけど、どうなんでしょう。

まとめ

わかっていると思っていたWrangleも、色々なケースを試すことで理解があやふなことが浮き彫りになってきました。

先輩のアドバイスを頂けると幸いです。