InstancedStaticMeshとCustomDataとTextureで大量オブジェクト (UE4 Advent Calendar 2020)

UE4 AdventCalendar2020 15日目の記事となります
Unreal Engine 4 (UE4) Advent Calendar 2020 - Qiita

サンプルプロジェクトは以下(UE4.26)
github.com

やりたいこと

  • 大量のインスタンスメッシュについて個別のパラメータをBPからマテリアルへ渡す
  • 大量のインスタンスメッシュのパラメータ更新をGPGPU(マテリアル)で高速に実行する
  • BPのみでつくる (C++つかわない)

このような要素を含んだデモとして以下のようなサンプルを作成しました。

  • 基準位置とバネで接続された物体のシミュレーション
    • バネによる速度の変化
    • 速度による位置の変化
    • プレイヤーとのインタラクション

youtu.be
youtu.be
以降の説明は基本的にこのデモをベースにしていきます。

仕組みの概要

今回紹介する方法は大まかに以下のような流れになります。

  • InstancedStaticMeshのCustomDataにInstance番号を設定する
  • テクスチャを配列データに見立ててInstanceパラメータを格納する
  • Instanceパラメータテクスチャをマテリアルで更新する
  • マテリアルでInstance番号に対応したパラメータをテクスチャから取得して使う

事前情報

大量のメッシュを描画する場合にInstancedStaticMeshを使うことが多いと思います。
ただし、UE4のマテリアルではSV_InstanceID的な情報は取れないので、そのままではInstance毎にマテリアルで色を変えるといったことはできません。
(格子状にInstanceを配置して頂点座標からInstance番号を計算するという技もありますが...)

CustomData

InstancedStaticMeshComponentのCustomDataを利用すると、Instance毎の個別情報を設定してマテリアルから参照できます。
f:id:nagakagachi:20201202234548p:plain
今回はここにInstanceの番号を設定し、テクスチャに格納したInstanceの速度情報等を取り出す際に利用します。

CanvasObject

CanvasObjectのDrawLine等によってBPからRenderTargetの任意の場所に色を書き込むことができます。
f:id:nagakagachi:20201203185207p:plain
これを利用して、マテリアルパラメータでは難しい大量の情報をテクスチャ経由でマテリアルに渡します。

TextureRenderTarget2Dの準備

バネのシミュレーションをするにあたって、Instanceの情報を格納する4つのTextureRenderTarget2Dを用意しておきます。

  • 基準位置
  • 基準位置からの変位(前フレーム)
  • 基準位置からの変位(今フレーム)
  • 速度

今回は位置と速度の情報なのでFormatには RGBA16f を使います。
f:id:nagakagachi:20201206210820p:plain

これらのテクスチャは下図のように左から右、上から下へ順番に番号付けをした配列データのように扱います。

f:id:nagakagachi:20201206210318p:plain
データ格納テクスチャ例(幅8高さ8)

ここから分かるとおり、テクスチャの幅高さは表示したいInstanceすべてを格納できる十分なものにしておく必要があります。

InstancedStaticMeshComponentの設定

InstancedStaticMeshComponentの Num Custom Data Floats に 1 を設定してInstance毎にfloat1つのCustomDataを使えるようにします。

f:id:nagakagachi:20201121204146p:plain
InstancedStaticMeshのNumCustomDataFloats

Instance番号をCustomDataに設定

InstancedStaticMeshComponentのSetCustomDataValueで各InstanceのCustomDataにInstance番号を設定します。
マテリアル側からはこのCustomDataに入ったInstance番号を利用して、対応するテクスチャ位置から情報を読み取ります。
f:id:nagakagachi:20201203214608p:plain

Instanceの基準位置をテクスチャに書き込み

先に用意した4つのテクスチャの内、基準位置テクスチャについてはBPから書き込みを行います。
まずInstance番号に対応するテクセル座標を以下のように求めます。
f:id:nagakagachi:20201203212604p:plain
 pos.y = int(Index / Width)
 pos.x = Index - pos.y * Width

実際の書き込みではまずBeginDrawCanvastoRenderTargetで開始を宣言し、
f:id:nagakagachi:20201203215135p:plain
Instanceの数だけDrawLineで対応する情報を色として書き込んでいきます。
開始位置は先に説明した計算で求め、終了位置は 0.5,0.5 だけオフセットした位置にします。
(これでうまくいってますがもっと良い方法があるかも)
今回は基準位置(XYZ座標)を書き込みたいのでColorのRGBに座標をそのまま設定します。
f:id:nagakagachi:20201203215016p:plain
全部終わったらEndDrawCanvastoRenderTargetで完了します。
f:id:nagakagachi:20201203215155p:plain

ここまででInstanceの基準位置が左上から右下へ順番に書き込まれたテクスチャが完成します。

f:id:nagakagachi:20201214235009p:plain
100個のInstance位置を書き込んだ16x16基準位置テクスチャ可視化

*1

変位テクスチャによってメッシュを動かすマテリアル

InstancedStaticMeshに設定して頂点を動かすマテリアルです。
TextureParameterとして基準位置からの変位テクスチャを設定しています。
InstancedStaticMeshのCustomDataに設定したInstance番号を元に、変位テクスチャから対応する変位量を取得してWorldPositionOffsetで頂点を動かします。
f:id:nagakagachi:20201206234848p:plain

まず PerInstanceCustomDataでInstancedStaticMeshComponentに設定したInstance毎のCustomDataの値を取得できます。
このCustomDataには先に設定したInstance番号が入っています。
f:id:nagakagachi:20201206220229p:plain

Instance番号とテクスチャサイズから対応するUV座標を計算し、このInstanceに対応する変位情報をテクスチャから取得します。

f:id:nagakagachi:20201206234926p:plain
Instance番号に対応するUVでテクスチャから情報取得

UV座標の計算は以下のようなネットワークになります。Canvasに書き込むときの座標計算の同じです(こちらはテクセル座標ではなくUVであることにだけ注意)。

f:id:nagakagachi:20201206220813p:plain
Instance番号とテクスチャサイズから対応するUV計算

ここまででメッシュのマテリアルと変位テクスチャをつなぎましたが、まだ変位テクスチャを更新していないのでメッシュは動きません。
このあとプレイヤーとのインタラクションを含めたバネのシミュレーションをマテリアルで計算して変位テクスチャを更新することでメッシュが動くようになります。
(まだ変位テクスチャの更新処理を作ってないので動きませんが)

変位テクスチャ更新マテリアル

変位テクスチャを更新するマテリアルを用意します。
このマテリアルはMeshに設定するのではなく、Draw Material To RenderTarget でRenderTargetTextureを更新するために使います。
このマテリアルはInstance番号などを気にする必要はなく、描画UVと同じUVでテクスチャから情報を取り出して計算するだけです。
(同じテクセル位置には同じInstanceの情報が格納されているので)
速度テクスチャと前回の変位テクスチャを元にバネ-ダンパモデルの計算をして新しい変位を求めます。

f:id:nagakagachi:20201206224233p:plain
速度と変位からバネ-ダンパモデルで新しい変位を計算

プレイヤーとのインタラクションは単純な球と点の当たり判定としています。
注意点として、変位テクスチャは基準位置からの変位量の情報なので、ワールド座標であるプレイヤー位置との比較のためには同じワールド座標に変換する必要があります。
そのために基準位置テクスチャの値と変位を足し合わせてからプレイヤー位置との比較をしています。

f:id:nagakagachi:20201206224521p:plain
プレイヤー球とのヒット

また、変位はマイナスを取る可能性があるのでマテリアルの設定でマイナスのEmissiveを許可するようにしておきます。

f:id:nagakagachi:20201123000813p:plain
Emissive出力の負値を許可

サンプルプロジェクトを見ていただくのが早いと思いますが、このマテリアルにはTextureParameterとして「基準位置からの変位(前フレーム)」や「速度」のテクスチャを設定し、Draw Material to RenderTargetのターゲットとして「基準位置からの変位(今フレーム)」を指定することで変位テクスチャを更新します。

速度テクスチャ更新マテリアル

変位テクスチャが更新されたので新たに速度テクスチャを計算し直します。
このマテリアルも変位テクスチャ更新のものと同様に Draw Material to Render Target で利用するため、Instance番号などを気にする必要はありません。
前回の変位と今回の変位の差分から速度を計算して出力する単純なものになります。
 vel_ = \frac {pos_{t} - pos_{t-1}} {\Delta t}

f:id:nagakagachi:20201206225008p:plain
速度テクスチャ更新マテリアル

このマテリアルも変位テクスチャ更新と同様にマイナスのEmissiveを許可するようにしておきます。

このマテリアルにはTextureParameterとして「基準位置からの変位(前フレーム)」と「基準位置からの変位(今フレーム)」のテクスチャを設定し、Draw Material to RenderTargetのターゲットとして「速度」のテクスチャを指定することで速度テクスチャを更新します。


これでようやくマテリアルによる変位と速度の更新, Instance毎の変位の取得と頂点移動表現がつながって動くようになります。
この記事では省略している部分もありますが、実際の処理の流れはサンプルプロジェクトの方を参考にしていただければ幸いです。

まとめ

以上でInstancedStaticMeshのInstance毎にテクスチャにパラメータを格納、更新して個別の振る舞いをさせることができるようになりました。
今回はInstanceの基準位置をCanvasを利用してテクスチャとしてマテリアルに渡していますが、この方法は色々な使い方ができるのではないかと思います。
また、ただのCubeではなく植物のメッシュを大量に表示しつつ個別に揺れたりさせたい場合にも使えると思います。
youtu.be

Twitterでこんなことをしていますのでフォローしていただけるとうれしいです
なが (@nagakagachi) | Twitter


明日は Dv7Pavilion さんの Influence Map に関する記事とのことです!
qiita.com






f:id:nagakagachi:20201215000807p:plain
サムネイル用

*1:基本的にこの処理はマテリアルへ渡したい情報に変更があったときに実行することになります。 今回はInstanceの位置は生成時点から変化しないので初期化時の一回のみです。