今回も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
画像をご覧ください。ポイント番号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
解説は先程と同様です。慣れればバーテックス番号を表示しなくてもわかりますね。rand関数
のシードがこの出力のとおり4
と5
のため、同じシードのポイントでは同じカラーになっているということです。
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
画像をご覧ください。色が少し分かりにくいですが、BOX天面のプリミティブ番号5を選択しています。
この四角ポリゴンは4つの頂点を持っており、それぞれの頂点がそれぞれのポイントを参照しています。Geometry Spreadsheetを見ればわかりますが参照の状態は下記の通りです。
頂点 | 参照されているポイント |
---|---|
5:3 | 4 |
5:2 | 7 |
5:1 | 6 |
5:0 | 5 |
上記表の通りポイント番号4,7,6,5のうち、最初に見つかった5が返ってきています。
printfの挙動を確認する
Wrangleの疑問点と直接関係ありませんが、プリントデバッグは言語仕様の理解に欠かせないのでここで見ていきます。
本家のドキュメントに明記されいないため想像となりますが、今のところ参照元が同一の場合は複数回出力されず一回の出力として処理されるという仕様が近い気がしています。
- ジオメトリにアクセスする場合は、Run Over対象のエレメント数だけ実行
- ジオメトリにアクセスしない場合は、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で全て同一なので一回のみ出力される
キーとなるのは下記挙動です。
最初に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で頂いたアドバイスを引用として記載しておきます。
例の場合1つのポイントが3つのプリミティブから共有されています、ポイントに対して返ってくるプリミティブはその3つのうちどれかで順序は保証されません、おそらくpoints, vertices, primitivesの関係が理解できていないと思うのでDocで確認するとよいと思います
— Satoru Yonekura (@yone80) 2017年9月19日
printf()での疑問も一部は先のジオメトリの構造が理解できると解決すると思います、順序についてはVEXは並列で実行され処理が終わった順序で結果が返ってくるのでバラバラな順序になります
— Satoru Yonekura (@yone80) 2017年9月19日
printf()で結果が一度しか返ってこない例はドキュメントでの説明は見つけられなかったのですがジオメトリにアクセスするかどうかで判断されているように見えますね
— Satoru Yonekura (@yone80) 2017年9月19日
正確にはPrimitiveがVertexを参照して、VertexがPointを参照するという順番ですね
— Satoru Yonekura (@yone80) 2017年9月19日
あとprimnumは順次呼ばれるのではなく、そのポイントを参照しているプリミティブのうち最初に見つかった一つが返ってきます
まとめ
Houdinist各位にとっては基本的過ぎる内容かと思いますが、WrangleやVEXはHoudiniの根底にあるパワフルな機能だと思うので、これをしっかり理解することは決して無駄ではないと思います。
僕自身なんとなくで使っていた中で、先輩方のお力を借りながら理解が深まったと思っています。
いやー。スッキリ!!!