#Wrangleの力を、画像にも。
HoudiniにはWrangle(らんぐる)が欠かせません。PointやPrimitiveに対して非常に細かな操作ができるHoudiniには欠かせないノード類です。PointやPrimitiveを一個単位で追加,削除,編集できるため、これさえあれば(理論上は)なんでもできることになります。加えて言えば、そのような操作をVEXと呼ばれるHoudiniで使えるC,C++ベースのプログラミング言語で記述するため、いくつもの操作をこのノード一つにまとめて記述することができます。つまり(あくまで理論上は)このノード一つでどんな3Dのシーンでも作れてしまうことになります。
では、画像のピクセルに対するこれが存在したらどうなるでしょうか…?
#Wrangleは、自作できる。
「Wrangle」とは「論争」あるいは「Wrangler」で「家畜の世話をする人,カウボーイ」といった意味があるそうなのですが、「ジオメトリに言葉(VEX)をぶつけて世話をする」的なニュアンスなんでしょうか…確かにノードアイコンはカウボーイの帽子になっています。
###Wrangleの中身
そんなWrangleですが、アイコンをよく見ると右下に南京錠のマークがついています。このマークがついたノードはデジタルアセット(HDA, Houdini Digital Asset)であり、既存のノードを組み合わせて作られたノードになっています。アセット作成時の設定によりますが、Houdini標準のアセットについてはダブルクリックでこの内部を見ることができます。
中には名前が「attribvop1」となっている「Attribute Wrangle Core」というノードが存在しています。名前が「attribvop1」となっているのには理由があります。Houdini 19より前では、このノードはAttribute VOPそのものだったのです。この名前はその名残といえるでしょう。Volume Wrangleも以前は内部にVolume VOPが存在していましたが、こちらもVolume Wrangle Coreへ置き換えられています。この各VOPから専用のノードへの置き換えは最適化のためと思われますが、Rig Attribute Wrangleでは、H19でも内部にRig Attribute VOPを確認できます。
ではこのVOPの中には何が存在している(していた)のでしょうか?Houdini 17.5のAttribute Wrangleの中身を覗いてみましょう。
内部には名前が「snippet1」となっている「Snippet」というノードが存在していました。このノードはVOP内でVEXを直接記述できるノードです。Snippetとは、Wikipediaによると
「断片」という意味で、再利用可能なソースコード、マシンコード、またはテキストの小さな領域を表すプログラミング用語である。
とのことです。基本的にノードベースで操作するVOPネットワークですが、その一部または全部をSnippetを利用して置き換えることが可能であるため、Wrangle系統はこれを利用して実装されています。
WrangleはVOPのSnippetを利用して実装されている。
つまり、なければ自分で作れる。
#画像を扱う場所、コードを書く場所。
Houdiniではジオメトリを編集するとき、マテリアルを作るとき、画像やジオメトリを出力するときなど、おおまかな作業の方向性によってそれらの作業をする場所がわけられています。画像を扱う場所は「COP Network (Compositing Operators Network)」と呼ばれており、どこでもよいので「COP2 Network」を呼び出すことでアクセスできるようになります。
COPが生成する画像は「Composit View」で確認します。いつものScene Viewから切り替えるか、存在していない場合は「+」ボタンから新たに作成しましょう。
画像を扱う場所は「COP Network」
確認は「Composit View」
#あなたがPixel Wrangleを作る番です。
先ほど作ったCOP Network「cop2net1」の中に入り「VOP」と検索すると「VOP COP2 Generator」がヒットするので、これを選択します。「Wrangle」と検索してもなにもヒットしません。
解像度はとりあえず1920×1080としておきます。
「vopcop2gen1」の中に入ると、2つノードが存在しています。変数こそ違いますが、Attribute VOP等でも存在しているよく使うインプット,アウトプットをまとめたノードが用意されています。この時点で「output1」の「R,G,B」に何らか値を入れてあげるとComposit Viewで確認もできますが、それでは面白くないので「Snippet」ノードが登場します。
####ちょっと注意点
ついにSnippetノードが登場しますが、ここで興奮していきなり@R = 1.0
などといつもの感覚でコードを書いても画面は赤くなりません。これを解決するためには「Bindings to Export」に*
と入力します。この項目について、公式のヘルプには次のようにあります。
Bindings to Export
アドホック(一時的)なバインドが@構文により作成される時、そのバインドは、読み取り専用のパラメータとして、生成されたVEX関数へ取り込まれます。 しかし、そのバインドがこの文字列に一致する場合、そのバインドはエクスポートのフラグが付きます。 例えばVOP SOPコンテキストでは、これにより、そのバインドが新しいアトリビュートを作成します。
要約すると「@構文はデフォルトでは読み取り専用で機能しているので、値を書き込むためにはそれを指定してね」という項目です。*
は全ての文字列にマッチするパターンとして機能しています。
ためしに@R = 1.0
としてみると、画像が赤くなることが確認できると思います。
デフォルトのピクセルフォーマットは「16bit 浮動小数点」
つまり、基本的には各色1.0が表示の上での最大値になる。
これは「COP VOP2 Generator」の「Image」→「Raster Depth」から変更できる。
Snippetの「Bindings to Export」に「*」の設定を忘れずに。
#これだけ。でもこれが全て。
ピクセル単位で処理を実行しているわけですが、何も情報が無いと塗りつぶすことくらいしかできないので、以下に便利な変数をまとめました。最低限のように思えますが、いまあなたの前にある画面も「ある座標にある色をセットする」という単純なことを繰り返しているにすぎません。つまりこれだけあれば十分なのです。
変数名 | 型 | 説明 |
---|---|---|
R | float | ピクセルの赤成分 |
G | float | ピクセルの緑成分 |
B | float | ピクセルの青成分 |
A | float | ピクセルのアルファ |
XRES | int | 画像の横幅(ピクセル数) |
YRES | int | 画像の縦幅(ピクセル数) |
IX | int | 画像のX座標( 0~(XRES-1), 左→右 ) |
IY | int | 画像のX座標( 0~(YRES-1), 下→上 ) |
X | float | 画像のX座標( IX/XRES ) |
Y | float | 画像のY座標( IY/YRES ) |
TIME | float | 現在の時間(秒) |
F | int | 現在の時間(フレーム) |
これらは@[変数名]
としてアクセスできます。またX, Y
に関しては、それぞれIX/XRES, IY/YRES
として対応しているため、本質的には必要ない変数でもあります。
#全てがあなた次第!
ここからは本当に自由です。ただ、あまりの自由度に逆に何をしてよいかわからないというHoudini特有のそれを感じる人も多いでしょう。そんなときは、言語こそ多少違いますが「Shader Toy」に存在するコードを参考にしたり、「The Book of Shaders」で基礎の基礎から学んでみるのも面白いかもしれません。
ゼロから全てを作らなくても、HoudiniのVEXには様々なノイズ関数が存在しているので、まずはそれらを試してみると楽しいかもしれません。
私は試しにマンデルブロ集合と呼ばれる図形を描いてみました。本題とは大きくそれてしまうので、詳しくはコード内の参考URLをご覧ください。
// 参考 https://www.iquilezles.org/www/articles/distancefractals/distancefractals.htm
float distanceToMandelbrot(vector2 c){
float c2 = dot(c, c);
// skip computation inside M1 - "http://iquilezles.org/www/articles/mset_1bulb/mset1bulb.htm"
if( 256.0 * c2 * c2 - 96.0 * c2 + 32.0 * c.x - 3.0 < 0.0 ) return 0.0;
// skip computation inside M2 - http://iquilezles.org/www/articles/mset_2bulb/mset2bulb.htm
if( 16.0 * (c2 + 2.0 * c.x + 1.0) - 1.0 < 0.0 ) return 0.0;
// iterate
float di = 1.0;
vector2 z = set(0, 0);
float m2 = 0;
vector2 dz = set(0, 0);
for( int i = 0; i < 300; i++){
if(m2 > 1024){
di = 0.0;
break;
}
vector2 temp = set(0,0);
// Z' = 2*Z*Z' + 1
temp.x = 2.0 * (z.x * dz.x - z.y * dz.y) + 1.0;
temp.y = 2.0 * (z.x * dz.y + z.y * dz.x);
dz = temp;
// Z = Z^2 + c
temp.x = z.x * z.x - z.y * z.y + c.x;
temp.y = 2.0 * z.x * z.y + c.y;
z = temp;
m2 = dot(z,z);
}
// distance
// d(c) = |Z|*log|Z|/|Z'|
float d = 0.5 * sqrt(dot(z, z) / dot(dz, dz)) * log(dot(z, z));
if(di > 0.5)
d = 0.0;
return d;
}
vector2 p = set((2 * @IX - @XRES) / float(@YRES), (2 * @IY - @YRES) / float(@YRES));
float d = distanceToMandelbrot(0.003 * p - set(0.054, -0.675));
d = clamp( pow(1000*d,0.2), 0.0, 1.0 );
@R = d;
@G = d;
@B = d;
#より良い作業環境をめざして。
1. 快適な変数操作
「Constant」または「Parameter」ノードをSnippetに接続することで、GUIによる変数操作が可能になります。ConstantではVOP内での操作が可能になります。Parameterではそのノードを内包するVOPノードそのものに自動でパラメータが追加されます。
2. 新たなイメージプレーンの追加
任意のイメージプレーンは@[任意の変数名]
として新たに作ることができます。確認するためには、Composite View左上から該当する変数を指定します。デバッグや、VOP COP2 Generatorから次のノードへ情報を引き継ぎたい場合に役に立ちます。@
の前にf
を付けるとfloat型
、v
を付けるとvector型
のイメージプレーンが作成されます。「Delete」ノードを使用すると、任意のイメージプレーンを削除できます。
3. 別パスで処理をする
「VOP COP2 Filter」を使用します。こちらも同じVOPですが、入力を必要とします。中にSnippetを置くことで、VOP COP2 Generatorと同様にコードを書くことができます。
4. 別の画像等を利用する
「cinput()」等、COPピクセルサンプリング関数が利用可能です。
5. レンダリングする
「ROP File Output」が利用可能です。Composite ROPと同一ですが、こちらはCOP内から直接利用できます。
#さいごに
最近のHoudiniは高機能で便利なノードがたくさん追加されてきています。それ自体はとても良いことなのですが、Houdiniを選ぶ人がHoudiniを選ぶ理由はそこにはありません。Houdiniの本当に強力な点は、とても原始的な操作, ポイントやプリミティブの情報をピンポイントで書き換えるような操作がオープンになっていることです。つまり、Houdiniを自分の物にするにはそれが本質的に何であるか, 感覚的には、実際何が起きているのかを意識することが大切です。
今回は各種Wrangleとは本質的には何なのかを考えることで、SOPからCOPへコンテキストの枠を超え新たにPixel Wrangleが誕生しました。
ここで質問です。Material Wrangleとか作れないの?