[UE4]InstancedStaticMeshのInstanceIDを何とかしてマテリアルで取得する

![2018-12-08_16h48_02.png](https://qiita-image-store.s3.amazonaws.com/0/58518/66c0213a-758c-49a9-ffdf-6db67692e35d.png)

[Unreal Engine 4 (UE4) Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ue4)の12月10日の記事です。
1. **[UE4]InstancedStaticMeshのInstanceIDを何とかしてマテリアルで取得する**
2. [[UE4]GarbageCollectの対象周りを調べてみた](https://qiita.com/com04/items/5d8d73e36f3b88a8b74e)

UE4.21.1で検証

# はじめに
![2018-12-08_15h02_09.png](https://qiita-image-store.s3.amazonaws.com/0/58518/ae76f7bc-eaf9-dfad-6c2c-9d5fb81b8cb4.png)

同一StaticMeshを複数配置するコンポーネントにInstanced Static Mesh Componentがあります。  

ただしマテリアル内で、インスタンスの番号が取得できません。
インスタンス番号で処理したい、Data埋め込んだTexture等使いたい場合とかで非常に困ります。

- GPGPU的に、マテリアルでオブジェクトを動かす場合は、selflashさんの下記資料が参考になると思います
	- [UE4]ブループリントだけでGPGPUをしよう ~ その1 インスタンスIDを割り振る ~  
https://qiita.com/selflash/items/c937308299d93340f7c7
	- ただ今回やりたいのは、アーティストさんが配置した状態でインスタンスIDを取得する方法です
		- ですので、座標値をそのままIDとして使用できません

- ※インスタンス毎のランダム値で良ければPerInstanceRandomが使えます
![2018-12-08_15h38_15.png](https://qiita-image-store.s3.amazonaws.com/0/58518/497d2332-21ac-508d-d92c-7ac30277f340.png)

# マテリアルへIDを渡す
BPからマテリアルへ、インスタンス別に渡せるパラメーターは基本的にありません。
唯一渡せるパラメーターとして、Transformがあります。
（正確には渡す、というより頂点座標の変換に内部的に使用されているだけです）
![2018-12-08_16h01_44.png](https://qiita-image-store.s3.amazonaws.com/0/58518/24fe2613-9a04-6bf2-f7b6-4597d5f75b86.png)

ここを経由して渡します。

- 変数の使い方
	- そのまま数値を入れると、Transformどれかの要素を潰してしまうことになります
	- そこで、影響が少ない少数第二桁目以降を使用します。0.01 = インスタンスIDが1
		- 整数部、少数第一桁部は通常のTransformとして使用します
	- また、XYZでそれぞれ1桁を表します。
		- X=1.01, Y=1.02, Z=1.03 で、インスタンスID=123
		- 最大999個までのインスタンスIDに対応できます。
- どの要素を使うか
	- 位置：配置座標として細かく設定される事が多い、細かい調整をされる事が多いため、ここを弄るのはよくない
	- 回転：マテリアル内では行列化されているため、復元するのが難しい。ダメ
	- 拡大・縮小：多少スケールを掛けることはあれど、細かく弄ることは位置、回転に比べて少ないハズ……
	- → 拡大・縮小の少数第二桁目以降を使用します


## BP
![2018-12-08_16h09_06.png](https://qiita-image-store.s3.amazonaws.com/0/58518/cf1fedd7-2114-5a4e-196b-9adde294eb14.png)
![2018-12-08_16h09_38.png](https://qiita-image-store.s3.amazonaws.com/0/58518/b70035f2-cce5-c239-95d4-3844e664902f.png)
各インスタンスをのTransformを拾ってきて、「Calc Scale」関数でスケールを処理して再設定します
![2018-12-08_16h24_24.png](https://qiita-image-store.s3.amazonaws.com/0/58518/3ce7319d-9b64-bc71-a332-d41552f4d9e7.png)

- インスタンスIDを各桁に分解。0.01を掛けて少数第二桁に変換
- 元のスケール値を少数第一桁まで取る(Snap to grid)
- 元のスケール（少数第一桁） + インスタンスID（少数第二桁） + 誤差を回避するための0.005

この結果が各インスタンスのTransform値になります
![2018-12-08_16h49_34.png](https://qiita-image-store.s3.amazonaws.com/0/58518/3f006652-cd8a-8002-592b-83e23d3f0ab0.png)


# マテリアルでIDを受け取る
拡大・縮小で渡って来ているので復元します。カスタムノード使います

```C++
#if USE_INSTANCING
/* 行列からスケール値を復元 */
float3 Scale = float3(
		length(Parameters.InstanceLocalToWorld[0].xyz),
		length(Parameters.InstanceLocalToWorld[1].xyz),
		length(Parameters.InstanceLocalToWorld[2].xyz));

/* 要素を少数第二桁から整数化 */
int3 InstanceIDElements = int3(Scale * 100.0) % 10;

/* 合成してインスタンスIDに */
int InstanceID = InstanceIDElements.x * 100 + InstanceIDElements.y * 10 + InstanceIDElements.z;

return InstanceID;
#else
return -1;
#endif
```

- Parameters.InstanceLocalToWorldに現在のインスタンスのTransform行列が入っています
    - 行列から各要素のlengthを取ってスケールを計算します
- 後はそれぞれ合成です

こんな感じでデバッグ表示
![2018-12-08_16h46_24.png](https://qiita-image-store.s3.amazonaws.com/0/58518/6efe9dac-0c0e-c5fc-db8a-7b4bb5654cef.png)


## ズレを直す
拡大・縮小の少数第二桁に値を入れているため、0.00~0.09のスケールが掛かっています
この拡大をマテリアルのワールドポジションオフセットで元に戻します

カスタムノード使います。

```C++
#if USE_INSTANCING
/* 行列からスケール値を復元 */
float3 Scale = float3(
		length(Parameters.InstanceLocalToWorld[0].xyz),
		length(Parameters.InstanceLocalToWorld[1].xyz),
		length(Parameters.InstanceLocalToWorld[2].xyz));
/* 少数第二桁のスケールを抽出 */
float3 AddScale = fmod(Scale, 0.1);

/* スケールを消した回転行列を取得 */
float3x3 NormalizedInstanceLocalToWorld = float3x3(
		normalize(Parameters.InstanceLocalToWorld[0].xyz),
		normalize(Parameters.InstanceLocalToWorld[1].xyz),
		normalize(Parameters.InstanceLocalToWorld[2].xyz));
/* 延びたスケール分を除去 */
return mul(Parameters.InstanceLocalPosition.xyz, NormalizedInstanceLocalToWorld) * -AddScale;

#else
return float3(0,0,0);
#endif
```
カスタムノードをワールドポジションオフセットに繋ぎます
![2018-12-08_16h52_57.png](https://qiita-image-store.s3.amazonaws.com/0/58518/71500aa9-21d2-1767-022e-d99203f1809e.png)


# ほか
- ライトのベイクする際にスケールでズレない？
    - BPで行うインスタンスIDの埋め込みをコンストラクションスクリプト等、編集時点で行う
    - マテリアルでのスケール打ち消しのWorldPositionOffsetを入れておく
        - の2点を行っていればズレないハズ……
- スケール弄ってBound変わらない？
    - 変わる。ゆるして
- 少数第三桁以降使用じゃダメ？
    - 試してない。誤差が怖いけど多分大丈夫。