kick the base

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

Houdini: Wrangleがわからない - 完結編

今回もHoudiniのお話です。前々回のポストHoudini: Wrangleがわからない(分かった感ある)から始まったWrangle疑問編ですが今回の記事をもっていったん完結です。

考え方自体は前回のポストであるHoudini: Wrangleとコンポーネントにまとめてありますので、それを踏まえた上で、最初のポストと同じ条件で考察を行っていきます。

Satoru Yonekura(@yone80)さんのアドバイスがなければ本記事は書けなかったと言っても過言ではありません。重ねて感謝を述べさせていただきます。


執筆時の環境

  • Windows10 Pro
  • Houdini16.0.504.20

サンプルファイル

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

シーンファイル

解決した疑問

前々回の記事でサンプルファイルを用意しましたが、疑問点のあるノードを青くカラーリングしていました。今回のサンプルファイルで疑問が解決した部分は赤くカラーリングしています。(一部未解決です)

また疑問点に関しては前々回の記事からそのまま移植しています。

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

(注) 今回解説に使用した画像ですが、ビューポートのバーテックス番号は僕の方で加工して追加しました。Houdini上で表示させることはできますが、ポイント番号にかぶってしまったり反対側のプリミティブ上のものも表示されたりと見難いためです。

pointwrangle_prim_attrib

@P.y = @primnum; // なぜ4と5が? > 答えは下記
i@test = @numprim; //6

printf(sprintf('%g', @primnum) + '\n');
//ポイント総数は8
/*
4 // @ptnum == 0のケース、primitive4,3,0、先にアクセスできた方の@primnumが返る
4 // @ptnum == 1のケース、primitive4,1,0、先にアクセスできた方の@primnumが返る
4 // @ptnum == 2のケース、primitive4,2,1、先にアクセスできた方の@primnumが返る
4 // @ptnum == 3のケース、primitive4,3,2、先にアクセスできた方の@primnumが返る
5 // @ptnum == 4のケース、primitive5,3,0、先にアクセスできた方の@primnumが返る
5 // @ptnum == 5のケース、primitive5,1,0、先にアクセスできた方の@primnumが返る
5 // @ptnum == 6のケース、primitive5,2,1、先にアクセスできた方の@primnumが返る
5 // @ptnum == 7のケース、primitive5,3,2、先にアクセスできた方の@primnumが返る
*/
Q

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

A

f:id:kickbase:20170923031339j:plain

画像をご覧ください。ポイント番号6を選択した状態です。このポイントが共有しているプリミティブは5,2,1の3つなのが分かるかと思います。

より詳しく書くと、ポイント番号6を参照している頂点は下記3つです。

  • 5:1
  • 2:2
  • 1:1

このハイフンの前の値がプリミティブ番号となります。(後ろの値は頂点番号)

この3つのプリミティブ5,2,1のうち、最初に見つかったひとつが返ってくるというのが重要なポイントです。ここではプリミティブ番号5が返ってきていますが、この仕組がわからないとモヤっとしたままとなるのではないでしょうか。

画像ではポイント番号6を例としましたが、これがRun Overの対象であるポイントに対して0~7まで繰り返されるわけです。

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

pointwrangle_Cd_primnum

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

@Cd = rand(@primnum); //多分@primnumには4,5が入ってるんだと思う > その通り。答えは下記

printf(sprintf('%g', @primnum) + '\n');
//ポイント総数は8
/*
4 // @ptnum == 0のケース、primitive4,3,0、先にアクセスできた方の@primnumが返る
4 // @ptnum == 1のケース、primitive4,1,0、先にアクセスできた方の@primnumが返る
4 // @ptnum == 2のケース、primitive4,2,1、先にアクセスできた方の@primnumが返る
4 // @ptnum == 3のケース、primitive4,3,2、先にアクセスできた方の@primnumが返る
5 // @ptnum == 4のケース、primitive5,3,0、先にアクセスできた方の@primnumが返る
5 // @ptnum == 5のケース、primitive5,1,0、先にアクセスできた方の@primnumが返る
5 // @ptnum == 6のケース、primitive5,2,1、先にアクセスできた方の@primnumが返る
5 // @ptnum == 7のケース、primitive5,3,2、先にアクセスできた方の@primnumが返る
*/
Q

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

A

f:id:kickbase:20170923031335j:plain

解説は先程と同様です。慣れればバーテックス番号を表示しなくてもわかりますね。rand関数のシードがこの出力のとおり45のため、同じシードのポイントでは同じカラーになっているということです。

primitivewrangle_Cd_ptnum

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

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

printf(sprintf('%g', @ptnum) + '\n');
//プリミティブ総数は6
/*
1 // @primnum == 0のケース、ptnum1,5,4,0、先にアクセスできた方の@ptnumが返る
2 // @primnum == 1のケース、ptnum2,6,7,3、先にアクセスできた方の@ptnumが返る
3 // @primnum == 2のケース、ptnum3,7,4,0、先にアクセスできた方の@ptnumが返る
0 // @primnum == 3のケース、ptnum3,7,4,0、先にアクセスできた方の@ptnumが返る
2 // @primnum == 4のケース、ptnum3,0,1,2、先にアクセスできた方の@ptnumが返る
5 // @primnum == 5のケース、ptnum4,7,6,5、先にアクセスできた方の@ptnumが返る
*/
Q

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

A

f:id:kickbase:20170923031330j:plain

画像をご覧ください。色が少し分かりにくいですが、BOX天面のプリミティブ番号5を選択しています。

この四角ポリゴンは4つの頂点を持っており、それぞれの頂点がそれぞれのポイントを参照しています。Geometry Spreadsheetを見ればわかりますが参照の状態は下記の通りです。

頂点 参照されているポイント
5:3 4
5:2 7
5:1 6
5:0 5

上記表の通りポイント番号4,7,6,5のうち、最初に見つかった5が返ってきています。

printfの挙動を確認する

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

本家のドキュメントに明記されいないため想像となりますが、今のところ参照元が同一の場合は複数回出力されず一回の出力として処理されるという仕様が近い気がしています。

  1. ジオメトリにアクセスする場合は、Run Over対象のエレメント数だけ実行
  2. ジオメトリにアクセスしない場合は、1回だけ実行

上記法則も考えたのですが、それでは説明がつかない事象(pointwrangle2でご紹介します)も出てきたため今のところ上記の結論となっています。

pointwrangle1

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

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

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

直値である文字列'pointwrangle1\n'を参照しているため一回だけ出力される。

primitivewrangle1

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
*/

直値である文字列'primitivewrangle1\n'を参照しているため一回だけ出力される。

attribwrangle1_detail

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 (正しく取得できている)

このノードは依然青色のままです。

Detailコンポーネントはジオメトリ全体を指すのですべてのPoint、Primitiveを包含するのかなと思っていたのですが、参照は持たないということで-1が返ってくるのかもしれませんが、確信が持てない感じです。

今後も調査を続けていきます。

pointwrangle2

printf(sprintf('%g', 'test') + '\n'); //test
//出力の参照が同じ場合一回のみ出力される

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

int num = 5;
printf(sprintf('%g', num) + '\n'); //5
//出力の参照が同じ場合一回のみ出力される

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

printf(sprintf('%g', @P.z) + '\n');
/*
//出力はすべて-1だが@P.zは各々別の参照元なのでポイント数出力される
-1
-1
-1
*/

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

s@str1 = 'dummy';
printf(sprintf('%g', @str1) + '\n'); //dummy
//ジオメトリにアクセスしているが、参照元はstr1に代入された
//文字列で全て同一なので一回のみ出力される

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

@P = {1, 2, 3};
printf(sprintf('%g', @P.y) + '\n'); //2
//ジオメトリにアクセスしているが、参照元はPに代入された{1,2,3}から
//取得できるP.yで全て同一なので一回のみ出力される

f:id:kickbase:20170925102514j:plain

キーとなるのは下記挙動です。

最初にprintf(sprintf('%g', @P.z) + '\n');した時は-1が3回出力されています。これはジオメトリが各々持っている@P.zがたまたま同じ-1という値のため理解しやすいです。

しかし、@P = {1, 2, 3};してからprintf(sprintf('%g', @P.y) + '\n');すると2が一回だけ出力されます。これは参照しているのがPoint Wrangleで生成した{1, 2, 3}のY要素ひとつのため、一回のみ出力されるということなのかなと思います。

本連載記事はWrangleオペレータの挙動を追いかけることがメインテーマであるので、printf関数についてはまた後ほど調査していくこととします。

頂いたアドバイスの引用

ぼくの認識違いがあるかもしれませんので、YonekuraさんにTwitterで頂いたアドバイスを引用として記載しておきます。

まとめ

Houdinist各位にとっては基本的過ぎる内容かと思いますが、WrangleやVEXはHoudiniの根底にあるパワフルな機能だと思うので、これをしっかり理解することは決して無駄ではないと思います。

僕自身なんとなくで使っていた中で、先輩方のお力を借りながら理解が深まったと思っています。

いやー。スッキリ!!!