はじめに
以前解説した以下の記事では、Static Meshを変形させていました。
今回は、AnimationしているSkeletal Meshで疑似Soft Body Simulationを動かす解説です。
といっても、仕組み自体はStatic Meshの時と変わらないので、Skeletal Mesh用に追加で実装している部分を中心に解説していきます。
また、ここで説明する内容を実装したサンプルデータを以下で提供しているので、実際に動くデータがすぐに欲しい方は、確認してみてください。
https://heyyohanashima.gumroad.com/l/gmgmhl
環境
Unreal Engine 5.2.0
概要
この記事では、上の動画のように走っているグレイマンを変形させる例で実装の解説をしていきます。
Static Meshと違うところは、大きい所は、毎フレームSkeletal Meshの頂点をサンプリングしてくる必要があることぐらいで、後はそれに合わせた設定の調整をすればいいだけです。
また、動画の後半で出てきている赤い丸は、疑似Soft Body Simulationを計算する時の中心点(コンストレイントの起点)です。中心点が変わると、変形の仕方も変わることが確認できると思います。
そこの解説もしていきたいと思います(ちなみに、Static Meshでも中心点は自由に調整できます)。
事前準備
まず重要な事として、UVがオーバーラップしていないことを確かめてください。
グレイマンのデフォルトのUVは以下のようにオーバーラップしまくっていました。

その場合は、DCCツールでUVを貼りなおしてもいいです(UV0以外に入れるのもOK)し、UEのUV Editorを使ってみるのも楽でいいと思います。

毎フレームSkeletal Meshの頂点をサンプリングする

まず、Skeletal Meshの頂点をサンプリングするには、Skeletal Mesh Locationモジュールを使います。

Preview Meshに該当のMeshを入れ、Samplingの項目を上記画像のようにします(UV0以外を後のRender Textureへの書き込みに使用したい場合は、Smpled UV Indexの値を任意の数値にしてください)。
ここで、Vertex IDに設定しているIndexパラメータは、自分で作成したパラメータで、Execution Indexを格納させています。

なぜ、わざわざパラメータを作っているかというと、Particle Update内でReturn Exec Indexが使用される場合、Indexの値は一貫せず毎フレーム同じParticleでも違う値が返ってくるためです。
そのため、Execution IndexをParticle Spawn内でパラメータに格納し、Particle SpawnとParticle Update内でSkeletal Mesh Locationを使って頂点をサンプリングする際に、そのパラメータでVertex IDを指定することで、毎フレーム同じParticleが同じ頂点をサンプリングするようにできます。
最後に、Particle Update内のSkeletal Mesh Locationでは、Position SamplingをOutputに変更します。
ここがDefualtのApply(Rigid)になっていると、ParticleがMesh表面にこのタイミングで引っ付いてしまい、変形が正しく効かなくなります。あくまで、Skeletal Meshの頂点の位置は計算に使うだけで、実際のParticleのPositionを上書かないようにします。

これによりサンプリングされた各頂点の位置は、SampledPositionWithoutOffsetに格納されます。これをConstraintやDiffの計算に使用します。



中心点の設定
Static Meshの方の記事で簡単に説明しましたが、ここで解説している疑似Soft Body Simulationは、任意の点から各ParticleへのベクトルをConstraintにしたシンプルなPBDで動作しています。
この任意の点は、Static Meshの方の記事では、Niagara Systemの原点に設定していましたが、これは好きな所に設定しても問題ありません。
なので今回は、Skeletal Meshの任意のボーンの位置に設定できるように実装してみました。

- Skeletal Meshの任意のボーンの位置にParticleを1つ配置(Pivot Emitter)
- 1のParticleをParticle Attribute Readerで読み込み、その位置をConstraintの起点として使用
という流れになってます。
Skeletal Meshの任意のボーンの位置にParticleを出す
Skeletal Mesh Locationモジュールでは、頂点だけでなくボーンのサンプリングもできます。

Bone / Socket IndexでBoneのIndexを指定することで、任意のボーンをサンプリングできます。


Particle Attribute Readerで読み込み、その位置をConstraintの起点として使用
Particle Attribute Readerは任意のEmitterのParticleの情報を読み取れる便利な機能です。
Emitter NameにPivotを指定します。

次に、Pivot Emitterで任意のボーン位置に出したParticleの位置を取得し、Pivotというパラメータに格納します。


シンプルにParticle Attribute Readerを使って、Positionを読み取っているだけです。Pivot Emitterが出しているParticleは1つだけなので、Get Position by IndexのParticle Indexは0を入れておけば、そのParticleを参照することができます。
後は、このPivotパラメータを使って、Constraintの計算をします。


Pendulum StupのPemdulum PivotにもPivotパラメータを指定します。

あとは、レベル上に該当のSkeletal Meshを置き、その子にこの疑似Soft Body SimulationのNiagara Systemを入れます。

Animationを設定してプレイすれば、AnimationするSkeletal Meshで疑似Soft Body Simulationを動かすことができます!

補足
UE5.2.0のデフォルト状態だと影周りの表示がおかしくなるので、その修正方法を補足しておきます。
Virtual Shadow Mapsの影響

今回のサンプルで実際にプレイしてみると、影が取り残されて見える現象が起きます。
これはUE5の新機能のVirtual Shadow Mapsの影響で、影をキャッシュし、動きがない部分はキャッシュから影を描画する仕組みが入っているためです。
この動きがない部分をどう計算しているかは詳しくはわかりませんが、MeshのBoundsが関係していることは確かなので、BoundsのScaleを大きくすることで、変形によって落ちた影が毎フレームちゃんと更新されるようにすることができます。

アンチエイリアスの影響

UE5ではデフォルトでTSRのアンチエイリアスが設定されています。こちらの影響で速く動くものの軌跡にジャギリが出ることがあるので、これにより影部分に少し変な跡が出ています。
この辺はTSRの設定をもう少し細かく調整するなどあると思いますが、手っ取り早く直したい場合は、Project Settingsから、Anti-Aliasing MethodをTAAかFXAAに変更してあげてください。
おまけ
Meshの一部分だけ変形させたいのであれば、やり方としては以下の方針があるかと思います。
- Niagara System内で各頂点に配置したParticleに重みづけをし、Tightnessの値をParticle毎に調整する
- World Position OffsetをしているMaterial内でMaskを作って、World Position Offsetの強さを調整する
いずれの方法でも、Maskをどう作るかが肝かなと思います。普通にTextureでMaskを作ったり、Vertex Colorの使用や、Material内でノードを組んでMaskを作るのでもよいでしょう。
以下で提供しているサンプルデータでは、2のMaterial内でMaskを作ってWorld Position Offsetの強さを調整するサンプルも入っているので、気になる方はチェックしてみてください!
https://heyyohanashima.gumroad.com/l/gmgmhl