LoginSignup
4
1

More than 5 years have passed since last update.

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

Posted at

2018-12-08_16h48_02.png

Unreal Engine 4 (UE4) Advent Calendar 2018の12月10日の記事です。
1. [UE4]InstancedStaticMeshのInstanceIDを何とかしてマテリアルで取得する
2. [UE4]GarbageCollectの対象周りを調べてみた

UE4.21.1で検証

はじめに

2018-12-08_15h02_09.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

マテリアルへIDを渡す

BPからマテリアルへ、インスタンス別に渡せるパラメーターは基本的にありません。
唯一渡せるパラメーターとして、Transformがあります。
(正確には渡す、というより頂点座標の変換に内部的に使用されているだけです)
2018-12-08_16h01_44.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
2018-12-08_16h09_38.png
各インスタンスをのTransformを拾ってきて、「Calc Scale」関数でスケールを処理して再設定します
2018-12-08_16h24_24.png

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

この結果が各インスタンスのTransform値になります
2018-12-08_16h49_34.png

マテリアルで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を取ってスケールを計算します
  • 後はそれぞれ合成です

こんな感じでデバッグ表示
2018-12-08_16h46_24.png

ズレを直す

拡大・縮小の少数第二桁に値を入れているため、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

カスタムノードをワールドポジションオフセットに繋ぎます
2018-12-08_16h52_57.png

ほか

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1