Keep Learning and Creative

UE4, 技術解説

【UE4.27】Niagara Advanced 解説応用編~PBDによる鎖シミュレーション~

Epic Gamesが配布している、Unreal Engineの様々な機能のサンプルをまとめたプロジェクト「機能別サンプル(Content Examples)」の中に、UE4.26以降から、Niagara AdvancedというMapがあります。

そこでは、Niagaraの新機能やそれを使った応用例など、とても参考になるサンプルが多く配置されていて、実装内容を見てみるとたくさんの学びがあります。

それらのサンプルについて、いくつかの記事に分けて解説を行っていきます。

ちなみに、機能別サンプルはEpic Games Launcherのラーニングタブからダウンロードできます。また、UEのバージョン毎に内容が違う可能性があるので注意してください。

写真もできるだけのせてはいますが、小さくて字が読みづらいので、できれば実際に該当のNiagaraを開きながらこの記事を読むと、より理解しやすいかと思います。

TLDR;)

  • NiagaraでPBDを実装して、RigidなConstraintのリアルタイムシミュレーションができるよ
  • Niagaraの既存モジュールと併用できるので、重力を加えたり、ランダムに揺らしたりとかが簡単にできるよ

はじめに

この記事では、以前紹介したPBDの応用編として、PBDを使った鎖シミュレーションのサンプルを解説していきます。これは、Niagara Advancedの「2.4 Iterative Constraints」になります。

2.4 Iterative Constraints の解説

このサンプルでは、鎖のように伸び縮みしないRigidなConstraintの動きをPBDでシミュレーションする手法が紹介されています。

上の動画で鎖がゆらゆら揺れていますが、鎖の一つ一つの輪っかはMeshのParticleになっています。一番上のParticleは、プログラムによってハードコードで動きが付けられ、それ以外のParticleは、既存のForce系のモジュールによって力が加えられつつ、PBDで位置の補正が行われ、最終的に繋がった鎖が動いているように見えています。

まずは、全体の概観を眺めてみましょう。

一つのEmitterのみで構成されています。Simulation Stageを使うので、GPU Simにする必要があります。

黄緑色の部分で、シミュレーション用の各種パラメータの初期化と、Particleの初期位置や回転などの設定をしています。

水色の部分が、既存モジュールで各種Forceを加えている部分です。

紫色のモジュールで、一番上のParticleだけ、Force等をキャンセルし、位置をハードコードでアニメーションさせる処理をしています。

そして、赤色の部分でPBDが実装されています。メインの実装は、Simulation Stage内のもので、1フレームに48回繰り返し処理を行っています。

それぞれ詳しく見ていきましょう。

鎖の初期化

まずは、シミュレーションに用いるパラメータの設定です。

Chain Origin:鎖の始点の位置
Chain Segment Length:鎖の各輪っか間の距離(Meshの大きさに合わせて設定します)
Number Of Chain Segments:鎖の輪っかの数(この値の数だけParticleをSpawnします)
Weight Per Chain Link:鎖の輪っかの重さ(このサンプルではこの値はシミュレーションには利用されません)

また、シミュレーション時に前後のParticleの位置を知る必要があるため、Particle Attribute Readerの初期化を行います。

Configure ChainはScrach Pad Moduleですが、中身は設定した値をEmitterのAttributesに格納しているだけです。

Spawn Burst Instantaneousで、Number Of Chain Segmentsで設定した値の数、Particleを一括でSpawnします。

Initialize Chain Constraintでは、各鎖の輪っかParticleの位置やパラメータの初期化をします。

まずは、各Particleの位置を初期化します。
Configure Chainで設定した、始点・Prticle数・Particle間の長さを元に、各Particleを-Z方向に一単位ずつ下げて配置しています。

次に、各Particleに以下の情報を持たせます。
ChainSegmentLength:Particle間の長さの初期値(これがPBDで制約条件となります)
ChainGoalDistanceFromOrigin:鎖全体の長さ
ChainEndPoint:自身が鎖の終端のParticleか否かのフラグ
ChainStartPoint:自身が鎖の始端のParticleか否かのフラグ

始端のParticleにはForceの影響を与えないようにするため、InverseMassというAttributesを作り、始端のParticleには0、それ以外には1を入れます。

PBDでは、自身の前後のParticleの位置から、距離を計算し、制約条件を元に自身の位置を調整していきます。そのため、後にParticle Attribute Readerで前後ParticleのAtteributesを参照できるように、前後のParticleのIDを取得し、保持しておきます。

IDは、Execution Indexと違い、新たなParticleが生成されても必ず異なる値になるため、今回のように対象のParticleが完全に固定される場合は、IDを使った方がより安全になります。

WeightPerChainLinkで設定した値をMassに格納します。Massの値は、Force等の計算をNiagaraがする時に影響してきます。

鎖の初期化のパートの最後に、ParticleのRotationの初期化をしています。鎖を、順々に90度回転して組み合わさるようなルックにするため、Execution Indexの偶数・奇数でそれぞれ回転する/しないようRotationを設定しています。

Forceの設定

Force部分は、既存モジュールのDrag・Gravity Force・Curl Noise Force・Wind Forceを追加しているだけなので、説明は割愛します。

逆に、ここに他にも既存モジュールを追加して色々な動きを作ることが可能です。

ちなみに、全てのForceモジュールを非アクティブにすると、上のような動きになります。後述しますが、鎖の始点がハードコードで動いているので、それに合わせて残りのParticleがPBDによって追従しています。

始端の動きの設定

Update Chain Segments After Forcesで、始端のForceをキャンセルし、動きを自前でつけています。これは、Scrach Pad Moduleなので中身を見てみます。

PhysicsForce・Velocity・PhysicsDragにInverseMassを乗算しています。なので、始端のみこれらの値が0になります。

そして、始端の位置を、ChainOriginからChain Origin Offsetを足した位置に設定しています。

Chain Origin Offsetには、Sineカーブでx,y,zそれぞれ時間で動くように設定しています。これによって、鎖の始端がアニメーションしています。

ちなみに、始端のアニメーションをやめると、以下の動画のように始端が固定され、下部がForceでゆらゆら揺れるだけの動きになります。

おまけの色付け

概観の所で特に触れませんでしたが、Find Kinetic and Potential EnergyとColorize Chain Based on Kinetic Energy(Color Module)のモジュールがありますが、これはFind Kinetic and Potential EnergyでKinetic Energy(運動エネルギー)を計算して、その大きさに応じて青く光らせているだけなので、詳細な説明は割愛します。

PBDの実装

PBDは、力学的な計算によって物体の速度から位置を計算するのではなく、制約条件を満たすように各物体の位置を繰り返し調整することで、最適解に収束させるという方針で物体の位置を計算します。この方法は、正確性に欠ける部分はありますが、複雑な動きのシミュレーションも比較的速く行えるので、リアルタイムのシミュレーションとしても注目されている分野です。

今回のサンプルでの制約条件は、Particle間の距離の最大が一定(Chain Segment Lengthの値=5.4)になります。Particle Updateのステージで、外力や慣性速度などによって決定された位置から、Simulation Stageでその制約条件を満たすように調整します。

調整の方法はとてもシンプルで、前後のParticleとの距離を求め、制約の長さを超過した分その方向に動かすだけです。

前後のParticleで計算を行うので、最終的には2つのベクトルを適当に重みづけして合算した方向に動かします。Particle毎に質量を設定して重みづけをするなどできますが、このサンプルでは単純に足して2で割っています。

これをただ1回やるだけでは、全然全体の制約条件が満たされるように調整されませんが、これを繰り返し行うことで徐々に収束していきます。このサンプルでも、1フレームに48回この調整を全Particleに対して計算しています。

回数を増やせば増やすほど精度は上がりますが、もちろんより時間がかかります。

では、具体的な実装を見ていきましょう。
PBDを実装しているSimulation Stage内のモジュールは4つです。

以下のように、48回繰り返すように設定されています。

Solve Chain Constraintが、上述した位置の調整の計算を行っています。
中身を見ていきましょう。

まず、Particle Attribute Readerで、保持していた前後のParticleのIDを元に、それらの位置を取得します。

そして、Calculate Link Constraintモジュールスクリプトに、自身の位置と前後のParticleの位置、制約距離、ピン止めするかのフラグ等を渡します。
このモジュールスクリプトがアウトプットしているUpdatedPositionが位置調整済みのポジションになるので、それをそのままPosition Attributesに入れています。

ここで、ピン止めのフラグには自身が始端かどうかのフラグを入れているので、始端であれば位置調整の影響を受けず、位置は変わりません。

Calculate Link Constraintの中身を見ていきます。

まず、自身から前のParticleへのベクトルを求め、その大きさが制約距離より大きければ、制約距離を引いた分の大きさの同方向のベクトルを計算しています。制約距離より小さければ0ベクトルになります。

後ろのParticleも同じ方法で計算され、ベクトルが求められています。

そして、両方のベクトルを足して2で割った値が、元々の自身の位置に足され、UpdatedPositionとしてアウトプットされます。この時、CurrentPinnedのフラグがTrueなら足されずに、そのまま元の位置がアウトプットされます。

このように、位置調整の計算がCalculate Link Constraint内で行われています。

次に、ここでは鎖の向きを調整するために、前のParticleへのベクトルを求め、ChainLinkDirectionに格納してアウトプットしています。
終端のParticleだけ前のParticleが存在しないので、後ろのParticleへのベクトルを使うようにしています。

そして、次のOrient Mesh to Vectorモジュール(既存モジュール)のLook At Directionに先ほどの前のParticleへのベクトルを入れることで、そちらの方向に鎖がしっかりアラインするようになります。

最後のCalculate Accurate Velocityで、調整された位置を元に速度が再計算されます。

計算方法は至ってシンプルで、調整後のポジションから前フレームのポジションを引いてdeltatimeで割っているだけです。

このようにPBDでは、速度から位置を計算するのではなく、位置から速度を再計算します。この再計算をしないと、次のフレームのUpdate Particleのステージで、物理的にちゃんと速度から位置を求める時に、速度が実際に見えている動きに嚙み合わず、おかしな感じになってしまいます。

最後に、しれっと説明をスキップしていたのですが、Particle Update ステージの中に、Constrain Chain to Max LengthというScratch Padモジュールがあります。

中身を見ると簡単な説明がありますが、これは、Simulation StageのPBDでの位置の調整を助けるために、事前にChainOriginから鎖全体の長さより遠くに行ってしまったParticleを鎖全体の長さのとこまで戻す処理をしています。今回はRigidなConstraintなので、鎖全体の長さより遠くにParticleが行く=制約条件を満たさないことになるので、このような事前調整をかけて、PBDでの位置調整の収束を助けることをしています。ただ、これは無くてもこのサンプルでは問題なく動きます。

実装自体は、同じCalculate Link Constraintモジュールスクリプトを使って、渡すパラメータを変えているだけなので、説明は割愛します。

以上で、「2.4 Iterative Constraints」の解説は終わりです。

おわりに

意外ととてもシンプルなロジックで、本物っぽいRigid Constraintの動きが作れることが分かって個人的には驚きました。

気になる負荷ですが、このサンプル(32 Particle×48回繰り返し)でPBDの計算にかかる時間は1ms前後ぐらいです(RTX2080)。インゲームだと状況によってはという感じかもしれませんが、映像等の用途なら全然いけそうです。

試しに100 Particle×60回繰り返しとかにしてみましたが、1~3msほどでした。まあ、用法容量はお守りにという感じですね。

また、これの良い所は既存モジュールと併用可能なことで、やはりCollisionとってインタラクティブにしたいですよね。

ということで、試しにマリオのワンワン作ってみました。
これはまた別の記事で紹介したいと思います。

結論的には、Collisionとってインタラクティブにやるのは、普通の方法だとまだ色々と課題がありそうという感じです、残念ながら…
ただそれでも使っていけるケースは色々あると思うので、どんどんPBDの作例が増えれば良いなと思います。

では、よきCGライフを!

Leave a Reply