はじめに
今回は、以下のようなNiagaraで簡単にできる疑似Soft Body Simulationの解説をしていきます。
「疑似」と付いているのは、一応PBD(Position Based Dynamics)によるSoft Body Simulationではあるものの、制約条件が極めて単純で、ボリューム保存の制約条件もないので、ちゃんとしたSoft Body Simulationではないためです。
ですが、それでも見た目的にいい感じなのと、何よりとても簡単に実現できるという点で凄い良いなと思っています。さすがNiagara!
さっそく実装方法を見ていきましょう!
また、ここで説明する内容を実装したサンプルデータを以下で提供しているので、実際に動くデータがすぐに欲しい方は、確認してみてください。
https://heyyohanashima.gumroad.com/l/rbtwx
環境
Unreal Engine 5.2.0
概要
作り方を簡単に説明すると、Niagaraで対象Meshの各頂点に配置したParticleを、Pendulumモジュールを使って疑似Soft Body Simulationさせて動かし、その移動差分をRender Targetに書き込んで、対象MeshのWorldPositionOffsetで実際に頂点を動かします。
Niagaraの全体像は以下の画像のようになっていて、色分けした数字は、下記の手順の番号に対応しています。
また、Render Targetへの書き込みをするため、GPU Simである必要があります。
さっそく、実装手順を見ていきましょう。
手順
- Meshの各頂点にParticleを配置する
- Pendulamのセットアップをする
- Particleの移動差分をRender Targetに書き込む
- MeshのマテリアルでWorld Position Offsetを使い変形させる
1. Meshの各頂点にParticleを配置する
まずは、対象Meshの頂点数を確認し、同じ数分のParticleをSpawn Burst Instantaneousで発生させます。
次に、Static Mesh Locationを使い、StaticMeshの所に、対象のStatic Meshをアサインします。
Meshの各頂点に一つずつParticleを発生させるには、Mesh Sampling TypeをVerticesにし、Vertex Sampling ModeをDirect Vertexにして、Vertex IDにExecution Indexを入れることで、実現できます。
Execution Indexは、Spawn Burst InstantaneousでParticleを1発で発生させた場合、Paritcleの通し番号になっているので、頂点数と同じ数を発生させれば、ちょうどMesh頂点のIDとParticleのExecution Indexが1対1で対応します。
これで、Meshの各頂点にParticleを一つずつ配置することができました。
2. Pendulumのセットアップをする
Epicがデフォルトで提供しているPendulumモジュールを使うと、疑似Soft Body Simulationが簡単に実現できます。
Pendulumモジュールの詳しい仕組みについては、別の記事で解説しようと思うので、今回は深く触れません。
一応簡単な説明としては、PBD(Position Based Dynamics)の手法で、各Particleの相対位置(ある点からの長さと方向)の制約条件(Constraint)を作り、その条件を満たすようにParticleを移動させるというものです。
ここでは、ConstraintとPendulum Setupの設定内容の説明だけしておきます。
基本的に、対象Meshの形を保持することが今回は必要なので、Constraintは単純にMeshのPivot(Niagara Systemの原点)から各頂点へのベクトルにします。
そして回転に対応するため、ConstraintをNiagara SystemのRotation値に応じて回転させます。
Pendulum Setupでは、Pendulum LengthにConstraintの長さを入れ、Pendulum Rest AxisにConstraintを入れます。
Spring Driven Constraint(Tightness)という設定値が、疑似Soft Bodyの固さを制御しているので、外から変えられるようにUser Parameterを設定しておきます。
後のモジュールは、Pendulum Setupを入れた時点で追加しないとエラーになるので、Fix Issueで必要なものを追加していきます。特に設定値を変える必要もないので、説明は割愛します。
これで、Particleについては、疑似Soft Body Simulationができました。とても簡単ですね!
3. Particleの移動差分をRender Targetに書き込む
続いて、Particleの移動情報を使って、対象Meshの頂点自体を動かすために、Render Targetへの書き込みを行います。
まずは、Render Targetの準備です。
Niagara内では、Emitter AttributesでRender Target 2D、User ParametersでTexture Render Targetのパラメータを作り、書き込み用のRender Targetのアセットも作成します。
次に、User ParametersのTexture Render Targetに、作成したRender Targteのアセットを設定します。
Emitter Spawnの所で、作成したRender Target 2DのSizeとRender Target User Parameterの設定をします。ここでSizeを設定すると、Render Targetのサイズも自動で決まるので、アセット側でサイズを設定する必要はありません。
今回4096×4096にしていますが、これはかなり容量がでかいです。しかし、今回は対象MeshのUVをそのまま使ってRender Targetに書き込んでいるので、各頂点のUVの差よりもRender TargetのTexelの大きさが小さくなっている必要があり(同じTexelに異なる頂点(Particle)の情報が書き込まれてはダメなので)、今回の例ではRender Targetの解像度を大きくせざるを得なくなりました。
これの最適化には、頂点の数を絞ったり、UV展開の工夫がありますが、そもそも書き込みにUVを使わず、頂点番号をVertex Colorに焼き付けて、それを使用することで書き込むTexelを必要最小限にするなど、色々工夫の余地はあるかと思います。
さて、少し脱線しましたが、次に必要なのはParticleの移動差分です。今回はDiffというパラメータを作って、毎フレーム以下のように計算します。
Constraintが対象MeshのPivotから各頂点へのベクトルになっているので、ENGINE OWNER Positionと足した位置が、その時の変形前の対象Meshの各頂点位置になります。
現在のPositionからその値を引くことで、疑似Soft Body Simulationによって得られたParticleの移動差分を求めることができ、この値を対象MeshのマテリアルのWorld Position Offsetで使うことで、Mesh自体を変形させることができます。
したがって、Render TargetにはこのDiffの値を書き込めばよいことになります。
最後に、Render Targetへの書き込みの実装を見ていきましょう。Scratch Padで実装します。
まずInputは、以下のようにセットします。UVについては、Static Mesh LoactionでParticleを各頂点に配置した際に、その頂点のUV情報も取得できるので、その値をセットします。
基本的には、Set Render Target Valueというノードで、Render Targetに書き込みをすれば良いだけですが、注意点があります。
書き込むときは、当然Texelに値を書き込んでいきますが、TextureをUVで読み込む時は、UVの位置がTexelの中心にない場合、2つのTexelの値を線形補完した値になります。
普通にTextureから色情報などを読む時には問題ないですが、今回のように各頂点独立の情報を読みたい時、変に関係ない情報と補完させるとおかしくなります。なので、書き込みと読み込み場所を確実に一致させる(=Texelの中心のみ使う)ように処理する必要があります。
それを踏まえて、書き込み側は、UVをRender Targetのサイズで乗算した後、適当にInt化するのではなく、統一的な処理で、確実にそのUV値が含まれるTexelのIndexになるように、Ceil→Subtract 1→Max 0をします。
ここまでで、Niagaraの実装は終わりです。Niagara Systemをレベル上に配置して適当に動かしてみると、Render Targetに値が書き込まれているのが、確認できます。
4. MeshのマテリアルでWorld Position Offsetを使い変形させる
最後に、Particleの移動差分が書き込まれたRender Targetを使い、対象Meshのマテリアル内でWorld Position Offsetにより変形させます。
注意点は、上述したTexelの中心を読むように、UVの値を補完する処理を入れるだけです。
Niagaraでの書き込みと同じ処理をした後に、その値に0.5を足し、Texture Sizeで割ることで、Texelの中心のUV値にすることができます。
このMaterialをアサインした対象Meshをレベル上に置き、その子供に疑似Simulation用のNiagaraを置いて、Meshを動かすと、冒頭の動画のようなお手軽Soft Bodyの完成です!
おまけ
以上で解説は終わりですが、せっかくなのでプレイアブルで操作できるようにしてみました。
おわりに
解説は少し長くなりましたが、やってみると実装自体はとてもシンプルです。Soft Bodyのような見た目が、こんなに簡単に作れるなんで、やはりNiagaraはすごいですね。
さて、疑似的なSoft Body Simulationは簡単に作れるとして、やはり本格的なSoft Body Simulationにも挑戦したくなりますよね。
その辺りも今後トライできればと思います!
This is so useful, thanks a ton for sharing this. Would it be possible to add some sort of weight to the vertices in order to get parts of a mesh to deform more than others?
I think you can use different Tightness depending on vertices, like you could use vertex color to control which parts should be tighter and read vertex color in Niagara, then apply different Tightness.
Also, you could control World Position Offset strength in material using some mask.