Unreal Engine 4 (UE4) Advent Calendar 2018の12月10日の記事です。
- [UE4]InstancedStaticMeshのInstanceIDを何とかしてマテリアルで取得する
- [UE4]GarbageCollectの対象周りを調べてみた
UE4.21.1で検証
はじめに
同一StaticMeshを複数配置するコンポーネントにInstanced Static Mesh Componentがあります。
ただしマテリアル内で、インスタンスの番号が取得できません。
インスタンス番号で処理したい、Data埋め込んだTexture等使いたい場合とかで非常に困ります。
-
GPGPU的に、マテリアルでオブジェクトを動かす場合は、selflashさんの下記資料が参考になると思います
- [UE4]ブループリントだけでGPGPUをしよう ~ その1 インスタンスIDを割り振る ~
https://qiita.com/selflash/items/c937308299d93340f7c7 - ただ今回やりたいのは、アーティストさんが配置した状態でインスタンスIDを取得する方法です
- ですので、座標値をそのままIDとして使用できません
- [UE4]ブループリントだけでGPGPUをしよう ~ その1 インスタンスIDを割り振る ~
マテリアルへIDを渡す
BPからマテリアルへ、インスタンス別に渡せるパラメーターは基本的にありません。
唯一渡せるパラメーターとして、Transformがあります。
(正確には渡す、というより頂点座標の変換に内部的に使用されているだけです)
ここを経由して渡します。
- 変数の使い方
- そのまま数値を入れると、Transformどれかの要素を潰してしまうことになります
- そこで、影響が少ない少数第二桁目以降を使用します。0.01 = インスタンスIDが1
- 整数部、少数第一桁部は通常のTransformとして使用します
- また、XYZでそれぞれ1桁を表します。
- X=1.01, Y=1.02, Z=1.03 で、インスタンスID=123
- 最大999個までのインスタンスIDに対応できます。
- どの要素を使うか
- 位置:配置座標として細かく設定される事が多い、細かい調整をされる事が多いため、ここを弄るのはよくない
- 回転:マテリアル内では行列化されているため、復元するのが難しい。ダメ
- 拡大・縮小:多少スケールを掛けることはあれど、細かく弄ることは位置、回転に比べて少ないハズ……
- → 拡大・縮小の少数第二桁目以降を使用します
BP
各インスタンスをのTransformを拾ってきて、「Calc Scale」関数でスケールを処理して再設定します
- インスタンスIDを各桁に分解。0.01を掛けて少数第二桁に変換
- 元のスケール値を少数第一桁まで取る(Snap to grid)
- 元のスケール(少数第一桁) + インスタンスID(少数第二桁) + 誤差を回避するための0.005
マテリアルでIDを受け取る
拡大・縮小で渡って来ているので復元します。カスタムノード使います
#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を取ってスケールを計算します
- 後はそれぞれ合成です
ズレを直す
拡大・縮小の少数第二桁に値を入れているため、0.00~0.09のスケールが掛かっています
この拡大をマテリアルのワールドポジションオフセットで元に戻します
カスタムノード使います。
#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
ほか
- ライトのベイクする際にスケールでズレない?
- BPで行うインスタンスIDの埋め込みをコンストラクションスクリプト等、編集時点で行う
- マテリアルでのスケール打ち消しのWorldPositionOffsetを入れておく
- の2点を行っていればズレないハズ……
- スケール弄ってBound変わらない?
- 変わる。ゆるして
- 少数第三桁以降使用じゃダメ?
- 試してない。誤差が怖いけど多分大丈夫。