addprimでインスタンス

#はじめに

この記事は[Houdini Advent Calender 2018](https://qiita.com/advent-calendar/2018/houdini) の19日目の記事です。

直前まで何を書こうか悩んでいてギリギリになって書き始めたため、読みづらい文章になってしまいますがご容赦ください。

今回使用するhipfileは[こちら](https://drive.google.com/file/d/1rX_4O8UZ90pEDNvG8MDYcFb_R7ovm--g/view?usp=sharing)です。 ver. Houdini 17.0.352 * (まず最初に/OUT のWedgeを実行してください。)

環境
CPU Xeon E5-2683v3*2
RAM DDR4 64GB
GPU NVIDIA GeForce GTX 1080


#Houdiniでインスタンスするには
Houdiniでインスタンスコピーをするには、いくつかの方法があります。


・Instance OBJ Node を使用する。
・Instance SOP Node を使用する。
・Pack SOP Node を使用する。
・Delayed Load を使用する。
                                等々…
![instance_nodes.png](https://qiita-image-store.s3.amazonaws.com/0/212535/162a2a8e-044e-8bfb-4032-6b171599bab7.png)

これらの詳しい解説と検証は
@fujitarium さんが[こちら]
(http://blog.livedoor.jp/fujitarium/archives/52412058.html)のブログに投稿して下さっています。

本記事ではInstance SOPによるインスタンスの代替となる
Wrangleによるインスタンスを紹介できればなと思います。



#InstanceSOP
みなさんはレンダリングをするまえに、ジオメトリキャッシュをとり
PackedDiskPrimitiveとしてロードすることやDelayed Load Proceduralを使用してIFD軽量化をすることがあるかと思います。

そもそもPackDiskPrimitiveやPackとは[こちら](http://www.sidefx.com/docs/houdini/model/packed.html)をご覧ください。

InstanceSOPはそんなPackedDiskPrimitiveをポイント毎に作成してくれるノードです。
http://www.sidefx.com/docs/houdini/nodes/sop/instance.html
![instance.PNG](https://qiita-image-store.s3.amazonaws.com/0/212535/c9916d0b-33ee-61ab-a128-c03b36672253.png)


参照ジオメトリのパスをString Attributeとして持たせ、それをInstanceSOPの
Instance Attributeパラメータで指定してあげることでPacked Disk Primitiveを作成することができます。

あらかじめidを用意し
今回filepathをこのようにして持たせました。

```vex:pointWrangle
//キャッシュ種類分のid
int num = i@local_id; 
//パラメータにopname()でキャッシュ書き出しdriverの名前を取得
string output_driver_name = chs("driver"); 
//path指定
string base_path = "$HIP/geo/" + output_driver_name + "_" + itoa(num) + "/" + output_driver_name + "_" + itoa(num);
string extension = ".bgeo.sc";
string cache_frame = "." + itoa( int((@Frame - fit01(rand(i@id), ch("offset_min"), ch("offset_max") ))%chi("loop_frame")) + 1);

s@instancefile = base_path + cache_frame + extension;
```

けれども、このInstanceSOPを用いた方法は大量のポイントに対して行おうとすると、処理が重たくなります。
デジタルアセットになっているので、中を覗いてみましょう。
![inside_instance.PNG](https://qiita-image-store.s3.amazonaws.com/0/212535/7f6001a3-6cdd-9b7c-5802-dfa81bd58956.png)

どうやらinstanceSOPはCopyStampを使ってPackedDiskPrimitiveを配置していたみたいです。


Copyを使わないで、直接ポイントに情報を与えたもうちょっと早くなるのでは？
wrangleでInstanceSOPを再現してみようと思います。

#addprimでPackedDisk

参照ジオメトリのパスをアトリビュートとしてもたせた状態までは同じです。
WrangleでPackedDiskPrimitiveを作るには
[addprim関数](http://www.sidefx.com/docs/houdini/vex/functions/addprim.html)を使用します。
typeを"PackedDisk"に指定し、プリミティブに対して1ポイントが必要と書かれています。したがってポイントも指定してあげる必要があります。

```vex:pointWrangle
//packのDisplay 方法
string lod = chs("Display_as");
//PackedDisk作成
int prim = addprim(0, "PackedDisk", @ptnum); 
//instanceするファイルパス
setprimintrinsic(0, "unexpandedfilename", prim, s@instancefile, "set"); 

setprimintrinsic(0, "viewportlod", prim, lod, "set");
```

これで各ポイントにPackedDiskPrimitiveを作成することができました。

しかし、これではorientやN、pscaleなどによった回転とスケールが適用されていない状態です。

次のWrangleでトランスフォームを適用しましょう。
今回はorientとpscaleアトリビュートで回転とスケール変更します。

```vex:primitiveWrangle
//point attribを取得
float pscale = point(0, "pscale", @primnum);
vector4 rotation = point(0, "orient", @primnum);

//Matrix作成
matrix3 rot = qconvert(rotation);
vector size = set(pscale);

scale(rot, size);

//set
setprimintrinsic(0, "transform", @primnum, rot, "set");
```
これでトランスフォームが適用されたはずです。

#比較
100万ポイントに対してinstanceSOP, WrangleでのPackedDisk
それぞれ処理を行ったときのパフォーマンスを比較してみます。
**InstanceSOP** 約25秒
![instance_pfm.PNG](https://qiita-image-store.s3.amazonaws.com/0/212535/70d7ae1f-c820-138f-a4f8-eebd722a22bb.png)
**Wrangle** 約3秒
![wrangle_inst_pfm.PNG](https://qiita-image-store.s3.amazonaws.com/0/212535/7837f2c8-4d0a-c28e-5475-4a78ce7955ca.png)

およそ8倍以上Wrangleの方が処理が速いことが分かりました。

#おわりに
最初HoudiniからDeadlineにNukeのjobを投げる方法を書こうかと思いましたが、もはやHoudiniじゃない感じがしたのでやめました。

ネタに悩み悩み、結局これかよっという感じな内容でしたがお役に立てればと思います。
お手軽にinstanceしたいのであればinstanceSOPのほうが簡単ではありますが、Wrangleでaddprimするやり方もHDAにしてしまえば問題ないかと思います。

急いで書いたので、いろいろ間に合ってなかったり分かりづらいところがあるかと思います…。

間違っていたら教えてください…。