Houdini：CopytoPointでのMeshの配置をMayaで再現してVRayでレンダリングしてみる

# 概要
HoudiniでCopy to PointでのMeshの配置を、MayaのBifrostGraphで再現してVRayでのレンダリングまでをやってみました。

#### 動機
いま仕事で行ってる会社ではHoudiniからnCacheを出すツールがあり、それにAttributeを持たせてnParticleとして使用してます。で、そのnParticleにInstancerを適用してMeshをばらまいています。

しかし動作がやたら不安定なため使い所がむずかしく、他に方法ないのかなーと探してたとき、こちらのTutorialでHoudiniのデータをBifrostGraphを使ってArnoldでレンダリングできることを知りました。

[Houdini data imported into Bifrost, and then used Maya Arnold rendering](
https://www.youtube.com/watch?v=MVnaE9LnLQc&ab_channel=BruceLee)
（HoudiniからAlembicで書き出したpointやcurve、またparticleに配置した豚さんをMayaのArnoldでレンダリングするという内容）

会社で使用してるのは主にVRayなので、VRayでもレンダリング可能ならnParticleの代替になりそうです。

#### BifrostGraph（Bifrost）ってなに？
Mayaの割と最近のversionから付いたノードベースのVisiual Programing環境。以前は流体エフェクトのみの拡張機能で単にBifrostという名前だった。

どちらかというとそのイメージのほうがいまだに強く、BifrostGraphを業務でバリバリ使っているという人はあまり聞かない。自分自身、業務でもテストでも使ったことはほぼないです。

# 検証環境
- Maya 2020.4
- Bifrost 2.2.01（古いverだと中身が違いすぎてダメそう）
- VRay 5 PLE
- Houdini indie 18.5.408

# 注意事項
- BifrostGraphを使う上で以下の懸念点があります
    - 現状、全体的に開発途中感が半端ない
    - 今回pointの読み込みで使うfile_cacheノードは、将来変更される可能性がある実験的なノードと公式が言っている

# 手順
### Houdiniでの作業
#### ネットワークの全体図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/0c550105-04fa-debb-5b3c-1714858089ae.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/41df8022-26ae-1677-639b-fb191d8f3f19.png)
このようなHoudiniのシーンをMayaに持っていくことにします。回転はNとupで制御しています。

### toBifrostGraph内のノードの説明
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/adbdd6c4-dc63-f85a-9bc8-b0246261704c.png)
Mayaに持っていくために処理しているのは、青で囲ったtoBifrostGraph内のノードたちです。
主にここの説明をしていきます。

#### N_up_toOrient（Wrangle）：NとupをQuaternionに変換
BifrostGraphでの回転にはQuaternionを使うため、今回のように
回転をNとupで制御している場合は、Quaternionにする変換する必要があります。

ネットワーク右側のN_up_toOrient（Wrangle）に変換用のコードを書きます。

```
matrix3 m = maketransform(@N,@up); 
@orient = quaternion(m);
```

コードはこちらを参考に、というかそのまま使わせていただきました。
[cgwiki : Convert N and Up to Orient with maketransform](https://www.tokeru.com/cgwiki/?title=HoudiniVex#Convert_N_and_Up_to_Orient_with_maketransform)

#### variant_round（Wrangle）：intのvariantを最も近い値のfloatにする
copy to pointのinstance Idとして使っているvariantを、最も近い値のfloat値を持つpoint_instance_idというfloat型Attributeにしています。

```
f@point_instance_id = rint(@variant);
```

**なんでfloatに変換するのか**
BifrostGraphでInstanceのIdとして使わるのはlong型というあまり聞き慣れないものでした。
その正体はint-64bitとのことで、Houdiniでint-64bit型を出してみたんですがそれでも読み込んでくれませんでした。というかそもそも普通のintを読んでくれない。

そのためHoudiniからintを書き出すときは、同じ値のfloat型のAttributeを用意して書き出しています。
この仕様は将来のBifrostGraphのアップデートで変わる可能性があります。というか変わってくれ～。

<details><summary>point_instance_idという名称にしてる理由は？</summary>
point_instance_idはBifrostGraphにおけるInstance Idのデフォルトプロパティ名。ただしfloat型にしてるため互換性はないです。
ここでpoint_instance_idとしてるのは、BifrostGraphで使うAttributeですよ、とわかりやすくしておくため。</details>

#### Attribute Rename：BifrostGraphで使う名前に変更
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/f4ecd385-1074-2d72-6bbb-c16613bad8be.png)
Attribute名をBifrostGraphがデフォルトで使う名称に変更しています。
これによりBifrostGraphでAttribute名を変えなくてもそのまま使えるようにしています。

変更するAttributeは以下の２つ

* pscale -> point_size
* orient -> point_orientation

#### Attribute Delete：不要なAttributeを削除
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/3ab313a1-0374-cb1b-3ea9-9112bbfc383d.png)
variant, N ,upなど、BifrostGraphでは使わない不要なAttributeを削除しておきます。
最後に、ROP AlembicでAlembic形式を書き出したら、Houdiniでの作業は終了です。

###Mayaでの作業
**Bifrost Graphを開く**
Window → Bifrost Graph Editor → Create Graph

**ノードを繋ぐ**
set_instance_geometryノードがほぼcopy to pointのようなものなので、基本はそこにAlembicのmeshとpointを繋いでいくだけです。ポイントは、

* HoudiniでいうAttributeはBifrostGraphだとPropertyと呼ばれていること
    * そのためここからAttributeのことをPropertyと記述します
* point_instance_idをto_longでlong型に変換すること
* bake_instanced_geometryでVRayなどでもレンダリング可能にしてること
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/f2aa729a-9151-104f-19cf-3984d0b8ed5f.png)

ここからは各部分の説明です

#### file_cache：Alembicのpointを読み込む
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/50a0459a-1c48-aa46-5df8-96eeac86038b.png)
まずAlembicのpointを読み込むのはfile_cacheノードになります。
BifrostGraphには他にread_Alembicというまさしくそれなノードがあるのですが、なぜかそちらで読み込むとAttribute（Property）が来なかったため、今回はfile_cacheノードを使用しています。

ただし、file_cacheノードは将来変更される可能性がある実験的なノードと公式に書かれているため、業務で使う場合は注意が必要です。

:bulb:知っておくと便利な小技
いまどんなPropertyを持っているか確認したいとき

- ノードを繋ぐラインを右クリック → Add Watchpoint
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/9fc418d4-77d5-4c33-a37c-c40de6cefa41.png)


####get_geo_propertyとto_long：float型のpoint_instance_idをlong型に変換
pointを読み込んだら、そのpointにget_geo_propertyノードを繋いで、point_instance_idと入力しPropertyを取り出します。その後出力のdataからto_longノードを繋いでlong型に変換します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/c760aaea-2100-ec83-65c7-2216d0adb299.png)
ここで注意しなければいけないのは、Propertyの型を指定しないとエラーになることです。

**指定の仕方**
typeという入力を右クリック → Value Types → Propertyの型（この場合float）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/4acd2082-b486-e0bc-1ea5-c51ad164b98f.png)

####set_instance_geometry：pointとmeshを接続してinstance化
pointにmeshを配置してます。手順は以下の通り。

* set_instance_geomeryノードにpointとmeshを繋ぎます。
    * meshはAttributeいらないのでread_Alembicで読み込んでいます
* 入力にあるSelectionをクリックして開き、**instance_id_valueに**先程long型に変換した**point_instance_idを繋ぎます**。
* パラメータで**Overwrite Instance Idにチェック**を入れます。
    * これはpointが持っているInstance Id Propertyではなく、instance_id_valuesに繋いだ値を使うという設定です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/b6e4c22f-f509-9006-fbf3-62e9d06015e0.png)
Instanceができたら、それをbake_instanced_geomertyに繋いで最後にOutputに繋げばノードの作業は終了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/d440544f-3d2d-43b1-1166-d96283a54e44.png)
なんか黒豚になってるけど気にしないことにします。

#### BifrotsMeshをMayaのMeshに変換する
BifrostGraphでの作業は終わりましたが、この状態ではまだレンダリングできません。
標準のNode Editorを使ってMayaのメッシュに変換する必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/e1c51e79-83dc-c8f0-02ba-9a2c3c66dd37.png)

polySurfaceShapeはtabキーを押してmeshと入力したら出てくるやつを使えばいいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/df147f4a-c957-98e9-2e65-191b45202645.png)
これで普通にレンダリングできるようになりました。

:bulb:Historyを消去しなければ、BifrostGraphの変更は反映されます。

### MaterialがMeshごとに適用できない大問題発覚
さっき”普通にレンダリングできるようになりました”と言ってしまいましたが、すみませんウソです。
察しの良い方ならお分かりの通り、これだと複数のMeshを結合して１つのMeshになっているため、このままではMaterialが個別に適用できません。
ArnoldであればBifrostGraph上でMaterialをアサインできるようですが、VRayだといまのところムリそうでした。

####現状の解決策：BifrostGraphをInstanceのMeshの数だけ作る
ということで考えた苦肉の策がこちらです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/bbcc1b37-ec56-e4d7-6c32-063be1ebfaf4.png)
BifrostGraphを２つ作ってMeshも２つ用意します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/32dd1b41-cecf-024c-3ec6-fcc2d3124f63.png)
同時に表示したら解決！わーい！

１つのBifrostGraphに１つのMaterialしか適用できないなら、BifrostGraphを２つ作ればいいじゃない。
さんざんノードベースでやってきて、最後にこれかよというがっかり感はあるけれど、調べた限りでは他の解決方法は見つからなかったです。「もっと良い方法があるよ」という方がいらっしゃれば、私めに思し召しください。

**一応コツとかある**
こんな脳筋手法でも気をつけないといけない点はあります。
それは、**set_instance_geometryから外したmeshの場所にconstruct_pointsを繋げる**こと
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/fbc86d02-40da-773f-ce1e-5ee0d5f673a9.png)
pigのBifrostGraphではtoyを読み込んでいたread_Alembicの代わりにconstruct_pointsにしています。
こうすることで、instance Idを無視して全pointに配置されるのを防いでいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/096abcb5-83e4-80c6-4754-fb6327959150.png)
toyのBifrostGraphはpigのread_Alembicが繋がっていた０番目にconstruct_pointsを繋げています。
これで狙ったpointのidにmeshを配置することができます。

**construct_pointsノードとは**
公式によると、「なにもPropertyを持っていない最小のpointsを作成する」とのことです。
なので一番処理が軽そうでmeshの代わりになるものということで選びました。
[Mayaヘルプ : construct_points](https://help.autodesk.com/view/MAYAUL/2019/JPN/?guid=Bifrost_Common_reference_geometries_construct_points_html)

##最終結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742536/48964cb1-3783-f168-1547-d0b18de5aa33.png)

##感想
Maya標準の機能でHoudiniのpointがAttribute付きで持ってこられるのはうれしい。
あとはVRayなど他レンダラーとの親和性が高まっていけば、HoudiniとMayaの連携はかなり楽になりそう。

ただBifrostGraphはUI含めて開発中感が半端ないので、業務で使うのはまだ早い気がする。
後になってシーンの再現ができないとかはすごくでてきそう。

### 結論：VRay for Houdiniに期待したい