kick the base

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

Houdini: Wrangleとコンポーネント

今回はHoudiniのお話です。前回の記事でWrangleの疑問点をまとめました。

Twitterにて広くアドバイスを募ったところ、Satoru Yonekura(@yone80)さんにアドバイスを頂き疑問が解消しました!

情報が多くなりそうなので、記事は2つに分けることとしました。まずは本記事に理解したところをまとめ、その知見を元に前回の回答編として記事を書こうと思います。

Wrangleそのものを理解する前に、コンポーネント(Primitive, Point, Vertex, Edge, Detail)の復習が不可欠であったので主にそこに焦点を当てた記事になります。

コンポーネントと参照

前回の疑問点においてEdgeの項目がなかったので今回付言はしませんが、基本的に各エレメントの参照構造が分かれば理解できるかと思います。

また、コンポーネントとアトリビュートについての考察は、以前の記事にまとめてあります。

f:id:kickbase:20170921110947g:plain

上記画像にPoints/Vertices/Primitivesを表したGIFアニメを貼りました。ビューポートとGeometry Spreadsheetを見ながら各構造をまとめていきます。

Points

ポイントはHoudiniで最も基本となるエレメントです。特に難しい点はないかと思います。

Vertices

Vertexはプリミティブ番号:プリミティブ内の頂点番号と定義され、最終的に一意のPointを参照します。

f:id:kickbase:20170921110935j:plain

上記例を見ると、ポイント番号1を参照している頂点はふたつあります。

  • プリミティブ0:プリミティブ内の頂点番号1
  • プリミティブ1:プリミティブ内の頂点番号0

ということになります。

ここでいうプリミティブ内の頂点番号は通常表示されないので分かりにくいですが、ビューポート上でDキーを押すと表示されるDisplay Options > Markers > VerticesのNumbersにチェックを入れると表示されます。

Primitives

プリミティブも分かりやすいですね。本例ではプリミティブは2つです。プリミティブはバーテックスを参照します。

Wrangleオペレータ(Run Overと各エレメントへのアクセス)

ここが僕の理解の足りなかったところで、理解が深まったことで想定通りの挙動が確認できました。

まずは先に理解した内容をまとめておきましょう。

Run Over

VEXの対象となるエレメントを決めるオプション。エレメントの個数分だけ並列処理が実行されます。そして、そのエレメントを通して共有しているコンポーネントのアトリビュートを参照します。

ここがよく分かっていなかったためRun Overの対象と取得したいコンポーネントのアトリビュートが異なっている場合の挙動が把握できなかったということになります。

またPointを共有しているプリミティブが複数ある場合、@primnumの値は最初に見つかったひとつが返ってくるというのも理解不足だった点です。

以下にサンプルファイルと合わせて挙動を確認していきましょう。

サンプルファイル

シーンファイル

Run Over:Points/参照アトリビュート:@primnum

Run Overと参照アトリビュートを固定して様々な形状のジオメトリに対して検証していきます。

pointwrangle1

ポイント番号1,2がプリミティブを共有しています。

f:id:kickbase:20170921111422j:plain

printf(sprintf('%g', @primnum) + '\n');
//ポイント総数は4
/*
0 // @ptnum == 0のケース、これは確実に0
1 // @ptnum == 1のケース、primitive0と1、どちらか先にアクセスできた方の@primnumが返ってくる
1 // @ptnum == 2のケース、primitive0と1、どちらか先にアクセスできた方の@primnumが返ってくる
1 // @ptnum == 3のケース、これは確実に1
*/

pointwrangle2

形状を変化させました。ポイント番号1がプリミティブを共有しています。

f:id:kickbase:20170921111417j:plain

printf(sprintf('%g', @primnum) + '\n');
//ポイント総数は5
/*
0 // @ptnum == 0のケース、これは確実に0
1 // @ptnum == 1のケース、primitive0と1、どちらか先にアクセスできた方の@primnumが返ってくる
1 // @ptnum == 2のケース、これは確実に1
0 // @ptnum == 3のケース、これは確実に0
1 // @ptnum == 4のケース、これは確実に1
*/

pointwrangle3

形状は先程と同じですが、Blast SOP下流にFuse SOPを接続し、ポイントの共有を外しています。

ポイントの共有がなくなったためすべての@primnumが一意に決定されます。

f:id:kickbase:20170921111410j:plain

printf(sprintf('%g', @primnum) + '\n');
//ポイント総数は6
/*
0 // @ptnum == 0のケース、これは確実に0
0 // @ptnum == 1のケース、これは確実に0
1 // @ptnum == 2のケース、これは確実に1
0 // @ptnum == 3のケース、これは確実に0
1 // @ptnum == 4のケース、これは確実に1
1 // @ptnum == 5のケース、これは確実に1
*/

pointwrangle4

今度はGrid SOPで確認していきます。ポイント番号1,3,4,5,7がプリミティブを共有しています。

f:id:kickbase:20170921111402j:plain

printf(sprintf('%g', @primnum) + '\n');
//ポイント総数は9
/*
0 // @ptnum == 0のケース、これは確実に0
0 // @ptnum == 1のケース、primitive0と1、どちらか先にアクセスできた方の@primnumが返ってくる
1 // @ptnum == 2のケース、これは確実に1
0 // @ptnum == 3のケース、primitive0と2、どちらか先にアクセスできた方の@primnumが返ってくる
0 // @ptnum == 4のケース、primitive0,1,2,3、先にアクセスできた@primnumが返ってくる
1 // @ptnum == 5のケース、primitive1と3、どちらか先にアクセスできた方の@primnumが返ってくる
2 // @ptnum == 6のケース、これは確実に2
2 // @ptnum == 7のケース、primitive2と3、どちらか先にアクセスできた方の@primnumが返ってくる
3 // @ptnum == 8のケース、これは確実に3
*/

pointwrangle5

形状は先程と同じですが、Attribute Wrangle SOP下流にFuse SOPを接続し、ポイントの共有を外しています。

ポイントの共有がなくなったためすべての@primnumが一意に決定されます。

f:id:kickbase:20170921111356j:plain

printf(sprintf('%g', @primnum) + '\n');
//ポイント総数は15(fuseでポイントの共有を外してるのですべてのprimnumは確定される)
/*
0 // @ptnum == 0のケース、これは確実に0
0 // @ptnum == 1のケース、これは確実に0
1 // @ptnum == 2のケース、これは確実に1
0 // @ptnum == 3のケース、これは確実に0
0 // @ptnum == 4のケース、これは確実に0
1 // @ptnum == 5のケース、これは確実に1
2 // @ptnum == 6のケース、これは確実に2
2 // @ptnum == 7のケース、これは確実に2
3 // @ptnum == 8のケース、これは確実に3
3 // @ptnum == 9のケース、これは確実に3
3 // @ptnum == 10のケース、これは確実に3
1 // @ptnum == 11のケース、これは確実に1
2 // @ptnum == 12のケース、これは確実に2
3 // @ptnum == 13のケース、これは確実に3
2 // @ptnum == 14のケース、これは確実に2
1 // @ptnum == 15のケース、これは確実に1
*/

pointwrangle6

Fuse SOPでポイントの共有を解除したあと、各々のプリミティブを縮小しています。

f:id:kickbase:20170921111444j:plain

printf(sprintf('%g', @primnum) + '\n');
//ポイント総数は15(見やすくするため各々の面を小さくした)
//pointwrangle5同様すべてのprimnumは確定される
/*
0 // @ptnum == 0のケース、これは確実に0
0 // @ptnum == 1のケース、これは確実に0
0 // @ptnum == 2のケース、これは確実に0
0 // @ptnum == 3のケース、これは確実に0
1 // @ptnum == 4のケース、これは確実に1
1 // @ptnum == 5のケース、これは確実に1
1 // @ptnum == 6のケース、これは確実に1
1 // @ptnum == 7のケース、これは確実に1
2 // @ptnum == 8のケース、これは確実に2
2 // @ptnum == 9のケース、これは確実に2
2 // @ptnum == 10のケース、これは確実に2
2 // @ptnum == 11のケース、これは確実に2
3 // @ptnum == 12のケース、これは確実に3
3 // @ptnum == 13のケース、これは確実に3
3 // @ptnum == 14のケース、これは確実に3
3 // @ptnum == 15のケース、これは確実に3
*/

Run Over:Primitives/参照アトリビュート:@ptnum

今度はRun OverをPrimitivesにし、各々のプリミティブを通して@ptnumを参照してみましょう。

プリミティブはバーテックスを参照し、バーテックスはポイントを参照します。ポリゴンが定義されるには最低3つのバーテックスが必要なので、ひとつのプリミティブからアクセスされるのは3つ以上のポイントが対象となります。

primitivewrangle1

f:id:kickbase:20170921112023j:plain

printf(sprintf('%g', @ptnum) + '\n');
//プリミティブ総数は2
/*
0 // @primnum == 0のケース、point0,1,2、先にアクセスできた@ptnumが返ってくる
1 // @primnum == 1のケース、point1,3,2、先にアクセスできた@ptnumが返ってくる
*/

primitivewrangle2

f:id:kickbase:20170921112018j:plain

printf(sprintf('%g', @ptnum) + '\n');
//プリミティブ総数は2
/*
0 // @primnum == 0のケース、point0,1,3、先にアクセスできた@ptnumが返ってくる
1 // @primnum == 1のケース、point1,2,4、先にアクセスできた@ptnumが返ってくる
*/

primitivewrangle3

f:id:kickbase:20170921112012j:plain

printf(sprintf('%g', @ptnum) + '\n');
//プリミティブ総数は2
/*
0 // @primnum == 0のケース、point0,1,3、先にアクセスできた@ptnumが返ってくる
5 // @primnum == 1のケース、point5,2,4、先にアクセスできた@ptnumが返ってくる
*/

primitivewrangle4

f:id:kickbase:20170921112004j:plain

printf(sprintf('%g', @ptnum) + '\n');
//プリミティブ総数は4
/*
0 // @primnum == 0のケース、point0,1,3,4、先にアクセスできた@ptnumが返ってくる
1 // @primnum == 1のケース、point1,2,4,5、先にアクセスできた@ptnumが返ってくる
3 // @primnum == 2のケース、point3,4,6,7、先にアクセスできた@ptnumが返ってくる
4 // @primnum == 3のケース、point4,5,7,8、先にアクセスできた@ptnumが返ってくる
*/

primitivewrangle5

f:id:kickbase:20170921112000j:plain

printf(sprintf('%g', @ptnum) + '\n');
//プリミティブ総数は4
/*
0  // @primnum == 0のケース、point0,1,4,3、先にアクセスできた@ptnumが返ってくる
15 // @primnum == 1のケース、point15,2,5,11、先にアクセスできた@ptnumが返ってくる
14 // @primnum == 2のケース、point14,12,7,6、先にアクセスできた@ptnumが返ってくる
13 // @primnum == 3のケース、point13,10,8,9、先にアクセスできた@ptnumが返ってくる
*/

primitivewrangle6

f:id:kickbase:20170921112041j:plain

printf(sprintf('%g', @ptnum) + '\n');
//プリミティブ総数は4
/*
0  // @primnum == 0のケース、point0,1,3,2、先にアクセスできた@ptnumが返ってくる
7  // @primnum == 1のケース、point7,4,5,6、先にアクセスできた@ptnumが返ってくる
11 // @primnum == 2のケース、point11,10,9,8、先にアクセスできた@ptnumが返ってくる
15 // @primnum == 3のケース、point15,14,12,13、先にアクセスできた@ptnumが返ってくる
*/

Run Over:Detail/参照アトリビュート:@ptnum,@primnum

いままでの結果はすべて想定通りでしたが、これだけ不思議な挙動となりました。Detailコンポーネントはジオメトリ全体を指すのですべてのPoint、Primitiveを包含するのかなと思っていたのですが、参照は持たないということで-1が返ってくるのかもしれません。

下記公式ドキュメントにもDetailはPoint listとPrimitive listを持ってるぽい記述があるのですが。

Geometry attributes

引き続き要研究です。

detailwrangle1

f:id:kickbase:20170921112539j:plain

printf(sprintf('%g', @ptnum) + '\n');
//ディテール総数は常に1
/*
-1 // ディテールはポイントへの参照を持っていないってことかな
*/

printf(sprintf('%g', @primnum) + '\n');
/*
-1 // ディテールはプリミティブへの参照を持っていないってことかな
*/

頂いたアドバイスの引用

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

まとめ

Detailコンポーネントに関しては釈然としない感じになりましたが、それ以外は納得の行く結果になりました。

Run Overで指定したエレメントを通して各種アトリビュートにアクセスするという考え方をおさえておけば良さそうです。

アドバイスいただいたYonekuraさんに重ねてお礼を申し上げて記事を終えたいと思います。