Wrangleでノードをコーディングで実装する

今回の記事ではAttribute Wrangleノードを用いてVEXコードをSOPノードとして利用する方法について紹介します。

まず、ノードネットワーク上でVEXを使う方法は、主に2種類あります。

ひとつはアセットとして作成することです。
こちらはFile-> New Assetで作成を行うことができます。

DefinitionVEXにし、Network Typeを指定することでVEXのノードを作成することが可能ですが、
作成できるTypeが、Material VOP, CVEX VOP , CHOP, COP の4種類となっており、直接ジオメトリ階層には作成できません。

そこで、今回はジオメトリ階層上に、VEXコードを使ったノードを作成する方法として広く用いられている、
Attribute Wangleノードを使う方法について、ご紹介します。

 

その前に、Houdiniの4つのアトリビュートと、ノードの適用先について簡単にご紹介します。

Houdiniにはアトリビュートのクラスが4つ存在します。

Point, Vertex, Primitive, Detail の4種類です。 

Pointは各ポイントごとに、Vertexは各Vertexごとに、Primitiveは各プリミティブごとに持つことのできるアトリビュートです。
Detailは、各ジオメトリごとに持つことができるアトリビュートとなります。

このようにアトリビュートはクラスごとに分かれており、それぞれ、ジオメトリのコンポーネントに対して付与されるものです。

そしてもうひとつ重要なのが、ノードの適用先です。

これは簡単に言うと、どのコンポーネントに対して、ノードの処理を実行するか、ということです。

例えば、Poly Extrudeノードであれば、基本的には「面」を押し出すので、適用先はプリミティブになります。
Group Createノードであれば、Point, Edge, Vertex, Primitiveなどグループを作る適用先を任意に指定することが可能です。
Transformノードは適用先を指定することはできませんが、最終的にポイントの座標を動かすことになるので、適用先はポイントです。

このようにHoudiniのノードは各々、明示的か、暗黙的かはさておき、適用される領域を明確に決める必要があります。

そして当然ではありますが、ジオメトリというのは、Point, Edge, Vertex, Primitiveの各々の合計数は同じ数にはなりませんので、
適用先を選択できるノードの場合、どの領域を適用先と指定するかで、同じ種類のノードでも実際に行われる処理の数が変わります。

例えば、boxのように、ポイント数が6でプリミティブ数が8のジオメトリの場合、同じノードでも
適用先をポイントに指定していれば8回、プリミティブに指定していれば6回、ノードの処理が実行されます。

この知識は、先ほど紹介したノードではあまり意味を成さないですが、これから紹介するAttribute Wrangleノードでは非常に重要です。

 

まず、これが標準的なAttribute Wrangleノードと、そのパラメータです。

Attribute Wrangleノードとは、ジオメトリのアトリビュートをVEXpressionを使ってコーディングベースで操作するためのノードです。

このノードには、Group / Group Typeの他にRun Overというパラメータが存在します。
このRun Overというのが、ノードの適用先/実行回数を示します。

上から順番に見てきますと、このようになります。

Detail (only once) ジオメトリそのもの(つまり1回)
Primitives 各プリミティブ
Points 各ポイント
Vertices 各Vertex
Numbers 指定した回数

上4点に関しては、アトリビュートのクラスの分類と名前が一致しすることがおわかりいただけると思います。
これらは各コンポーネントに対して別個にVEXの処理が行われます。つまり、コンポーネント数と同じ回数の処理が実行されます。
Numbersは実行する回数を指定して実行するタイプで、どのコンポーネントに対して実行するかは指定しません。

今回はPointsに設定することで、Point Wrangleとして利用してみます。

 

今回のシーンファイルです。
今回は以前の記事で作成した、山のモデルに色を付けるVEXの拡張を行います。

以前、Attribute Expressionノード上に作成したVEXpressionを、Attribute Wrangleノードで実装すると、以下のようになります。

@Cd=set(abs(@P.y),1,abs(@P.y)*0.3);
//set(abs(@P.y), 1, abs(@P.y)*0.3) Attribute Expressionノードでの記述

殆ど内容は変わりませんが、若干記述が変わっていることが見て取れるかと思います。

頭に @Cd= がついているのと、末尾に ; がついています。
今回、Run OverPointsです。この場合、 以前紹介した通り、@CdPointクラスのCdアトリビュートを意味します。
例えばもし、Run OverがPrimitivesの場合は、PrimitiveクラスのCdアトリビュートを意味するようになります。

=については以前も紹介しましたが、等号の意味ではなく、左辺に右辺の値を代入するという意味になります。
今回はベクトル set(abs(@P.y),1,abs(@P.y)*0.3) をCdアトリビュートに代入しています。

そして行末にはセミコロンがあります。これはVEXにおける、行の終わり、という意味で、ここまでが1行の命令ということです。
基本的には一行ごとに記述する必要のあるもので、改行みたいなものと認識していただければと思います。

但し、iffor などのステートメントの後には ; を記述する必要がありません。if やfor では; の代わりに、 {} を記述します。
※{} の中が1行の場合は{}は省略できます。 

 

次に、この山のサイズを考慮してカラーリングを行うようなVEXを考えます。
また、今回はGreen lineというパラメータを作成して、緑の部分の高さをコントロールできるようにもしてみましょう。

これをコードで簡潔に記述すると、このようになります。

ただし、この記述だと各行の解説が難しいので、今回は変数を利用して、以下のように書き直したもので解説を行います。

このように、変数を使うことでコードの可読性を高めることも可能です。
ただし、不必要なメモリの移動が発生するので、場合によっては下のコードは上のコードより処理速度が低下する可能性があります。
※コンパイラによって最適化される程度の問題ですので、VEXにおいては、こちらの影響は皆無あるいは軽微であると推測できます。

まず上3行についてです。

vector bb   = getbbox_size(0);
vector bmax = getbbox_max(0);
vector bmin = getbbox_min(0);

ここでは各ベクトル型の変数に、それぞれジオメトリのバウンディングボックスの情報を入力しています。
変数bbには、バウンディングボックスのサイズ情報を、
変数bmaxには、バウンディングボックスの各座標の最大値の情報を、
変数bminには、バウンディングボックスの各座標の最小値の情報を、関数を利用してそれぞれ代入しております。

4,5行目はパラメータGreen lineの取得と値のフィッティングです。

float green_line = chf("GreenLine");
float green_fit  = fit01(green_line, bmin.y - bb.y, bmax.y);

今回はGreen lineには0.0 – 1.0の浮動小数を入力するように設定しています。
chf() 関数で浮動小数パラメータGreenLineを呼び出します。パラメータは手動で作成しなくても、右のボタンを押すことで、
コード内に記述されているchf()関数を自動的に読み取ってパラメータを作成してくれます。これを変数green_lineに格納します。

その後、fit01()関数を用いて、この0.0 – 1.0の値を、ジオメトリのY座標の最小値から最大値の範囲にフィットさせた
変数green_fitを作成します。

今回は第2引数をbmin.y – bb.yとすることで、最小値方向にかなり余分に値をとっています。
こうすることで、パラメータGreenLineが0付近の時に緑が殆ど出現しないように調整をしています。
これにより、パラメータGreenLine0の時はジオメトリは一面茶色に、1の時は一面緑色になるようになります。

8-10行目は各色成分の値を計算して、それぞれの変数に格納しています。
赤と青はスケールが違うだけの同じ式、緑は独立の式となっています。

float red_val   = abs(fit(@P.y, green_fit, bmax.y, 0.0, 1.0)) * 2.5;
float green_val = fit(@P.y, bmin.y, bmax.y, 0.5, 1.0);
float blue_val  = abs(fit(@P.y, green_fit, bmax.y, 0.0, 1.0)) * 0.7;

の式はfit()関数を用いて@P.y、つまり各ポイントの高さの値を0.0から1.0にフィットさせています。
この時に最小値に変数green_fitを使うことで、@P.yが変数green_fitを下回る場合は、結果が0になるように設定しています。
の式は単純に、Y座標が下から上に上がるにつれて値が大きくなるようなグラデーションをfit()関数を用いて作成しています。

12-15行目は実際にCdアトリビュートに数値を代入しています。しかし、ここではfit()関数の仕様の関係でif文を利用しています。

if(green_line != 1.0)
 @Cd = set(red_val, green_val, blue_val);
else
 @Cd = set(0.0, green_val, 0.0);

VEXのfit()関数は、フィット前の値の最小値と最大値が同じ数値になっている場合、
最終的な出力が、フィット後の最小値と最大値の平均値で固定されてしまいます。

これが今回の場合だと、上記のの式において、変数green_fitbmax.yと同値になる状況で発生します。

これはパラメータGreenLine1となっている時なのですが、できればこの時は赤と青は平均値ではなく、0が出力されてほしいです。
(GreenLineが1の時は全部緑であって欲しいので、赤、青の成分は0である必要があります。)

この回避策は、の式でbmax.yに極小値を足す、など他の方法も考えられますが、今回はif文を用いて回避しております。

変数green_line1.0ではない時は、先ほどのの計算式をCdアトリビュートに適用し、
1.0の時は、else文下が実行されるので、ハードコーディングでの値だけ0.0に設定しています。

また、通常、if やelse を使う際は{} を利用しますが、上述の通り、今回はif文の中身が1行しかないため {} を省略しております。

 

以上のVEXにより、パラメータGreenLineを動かすことで、緑の位置を制御しつつ山のカラーリングを行うノードが完成しました。

今回はRun OverPointsに設定しているので、先ほど紹介したVEXコードが、全てのポイントに対して、各個に実行されております。

ですので、ループなどは一切利用していないのに、全てのポイントに対して処理が実行されています。
これが暗黙的に並列処理で実行されているので、VEXは処理速度が高速になります

もし、これを並列処理を使わずに同じことを行うのであれば、Run OverDetailにした上で以下のように記述します。

これで全く同じ結果になる処理を、並列化せず1タスクで実行させることが可能です。
アトリビュートの設定方法が異なるのと、色成分計算~代入の過程がforによってループしていることが確認頂けるかと思います。

当然、今回の場合はDetailで記述するメリットは全くありません。
但し、あえて記述することで、Run Overの設定で、暗黙的にどのようなことが行われているか、より明確にすることができました。

 

そして、下の画像のように、元のGridやMountainのサイズを大きく変更しても、色合いが崩れていないことが確認できます。

このようなジオメトリのサイズをバウンディングボックスを使って取得するようなVEXスニペッドを使うことで、
ジオメトリのサイズ変更に対応できるアトリビュートの設定がWrangleを用いることで可能になりました。

以上が簡単なAttribute Wrangleノードを使ったアトリビュートの制御の紹介になります。