ゲーム開発
Unity
UnrealEngine
C++
Blender
ゲーム数学
ゲームAI
グラフィックス
サウンド
アニメーション
GBDK
制作日記
IT関連
ツール開発
フロントエンド関連
サーバサイド関連
WordPress関連
ソフトウェア設計
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
ラーメン日記
四コマ漫画
その他
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • Blender
    • ゲーム数学
    • ゲームAI
    • グラフィックス
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • IT関連
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • WordPress関連
    • ソフトウェア設計
    • おすすめ技術書
  • 音楽
    • DTM
    • 楽器・機材
    • ピアノ
  • ラーメン日記
    • 四コマ漫画
      • その他
        • おすすめアイテム
        • おもしろコラム
      1. ホーム
      2. 20240530_01_ue5_gpu_simulation

      【UE5】Niagara SimulationStageによるシミュレーション環境構築

      UnrealEngineグラフィックスNiagaraGPGPU群集シミュレーション
      2024-05-30

      マイケル
      マイケル
      みなさんこんにちは! マイケルです!
      エレキベア
      エレキベア
      こんにちクマ〜〜〜
      マイケル
      マイケル
      今回はUnrealEngineのNiagaraに搭載されている SimulationStage という機能について紹介します。 こちらはNiagaraでGPUによるシミュレーションを行うための機能で、いわゆるUnityでいうComputeShader的な使い方をすることができます。
      エレキベア
      エレキベア
      NiagaraでGPUシミュレーションが行えるクマか・・・ 確かにパーティクルは大量オブジェクトを扱うクマが、あまりイメージが沸かないクマ
      マイケル
      マイケル
      有名な例だと、流体シミュレーションエフェクトの NiagaraFluid もSimulationStageを用いて実装されているようです。

      GitHub - NiagaraFluid

      エレキベア
      エレキベア
      確かに流体シミュレーションはGPU計算が必要になるクマね
      マイケル
      マイケル
      なお、今回使用したUEのバージョンは下記になります。 バージョン違いによっては実装が異なる場合もあるためご了承ください。
      • UnrealEngineバージョン
        • 5.32

      参考書籍

      マイケル
      マイケル
      調査・実装するにあたり、下記の書籍を参考にさせていただきました。 日本語書籍で、Niagaraの基本的な使い方から流体シミュレーションまで含まれているのでおすすめです。

      Unreal Engine 5で学ぶビジュアルエフェクト実装 基本機能からNia...

      エレキベア
      エレキベア
      流体シミュレーションまで書いてあるとは、かなり実践的な本クマね
      マイケル
      マイケル
      また、SimulationStageではHLSLのシェーダーコードを書くことができます。 こちらは下記書籍が体系的にまとまっていておすすめです。

      HLSL シェーダーの魔導書 シェーディングの基礎からレイトレーシングまで

      エレキベア
      エレキベア
      がっつり使うとなるとシェーダーの知識も必要になるクマね

      Niagara SimulationStageの概要

      マイケル
      マイケル
      具体的な実装に入る前に、そもそもNiagaraやSimulationStageとは何なのか?について軽く紹介しておこうと思います。

      Niagaraとは

      マイケル
      マイケル
      Niagaraとは、UnrealEngineに搭載されているビジュアルエフェクトシステムです。 Niagaraシステムは大きく「システム」「エミッタ」で構成されています。

      Niagaraの概要 - Unreal Engine 5.3

      20240530_01_ue5_gpu_simulation_01
      ▲左:システム、右:エミッタ

      エレキベア
      エレキベア
      UnityでいうところのShuriken的なものクマね エミッタは聞きなれないクマ〜〜
      マイケル
      マイケル
      システムはレベルに配置することのできる単位で、エミッタはパーティクルの放出源です。 システムはエミッタを複数持つことができます。
      Niagaraシステムの構成
      • システム
        • レベルに配置することのできる単位
        • エミッタを複数持つことができる
      • エミッタ
        • パーティクルの放出源
        • 他のシステムからも使用することができる
      エレキベア
      エレキベア
      これがNiagaraの基本構成なのクマね

      SimulationStageとは

      マイケル
      マイケル
      SimulationStageはエミッタ内に追加できるステージで、GPUによる複雑なシミュレーション計算を行うことができます。

      Niagaraの主要な概念 - UnrealEngine5.3

      SimulationStageとは
      • Niagraに搭載されている、GPUにより複雑なシミュレーション計算を行うステージ
        • Niagara標準機能では作れないような複雑なエフェクトが作成できる
        • 直接GPUに対応したHLSLコードを書くことができる
        • 他の粒子のアトリビュートを取得できる
        • パーティクルだけでなく、RenderTarget2Dのような特定のDataInterfaceに対しても処理ができる
      エレキベア
      エレキベア
      そこまで使われていないのか、公式ドキュメントにもあまり記載がないクマね・・・
      マイケル
      マイケル
      シミュレーション計算はHLSLコードを実際に書いて制御することができます。 ゲームエンジンでComputeShaderを使用する場合には実装コストも大きいですが、SimulationStageを使用すると描画部分をNiagaraに任せることができるので、最小限の計算を実装するだけでシミュレーションを行えるというメリットがあります。
      エレキベア
      エレキベア
      確かにComputeShaderでの実装は計算部分以外の実装も大変クマからね・・・
      マイケル
      マイケル
      特にUnrealEngineでComputeShaderを実装する場合、 ・グローバルシェーダを用いる ・外部モジュールを導入する ・エンジンを改造する といった対応が必要になり、エンジンのバージョンごとにノウハウが変わるリスクがあります。 それを考えると、SimulationStageを使用して手軽に計算を行えるというのは大きなメリットだと思います。
      エレキベア
      エレキベア
      UnrealEngineだと標準機能でComnputeShaderを使うようになってないのクマね

      Niagara SimulationStageの環境構築

      マイケル
      マイケル
      それでは実際にSimulationStageの環境を構築してみます。 Niagaraの初期状態として、今回はFountainテンプレートを使用しました。
      20240530_01_ue5_gpu_simulation_10
      ▲Fountainテンプレートを使用

      20240530_01_ue5_gpu_simulation_01
      ▲初期構成

      エレキベア
      エレキベア
      上に放射するだけのシンプルなエフェクトクマね

      エミッタのプロパティの設定

      マイケル
      マイケル
      まずGPU計算を有効にするために、エミッタのプロパティの設定を変更する必要があります。 Sim Targetを「GPUCompute Sim」Calculate Bounds Modeを「Fixed」に指定します。 Fixed Boundsは計算を行う領域になるため、状況に合わせて変更しましょう。
      20240530_01_ue5_gpu_simulation_02
      ▲GPUを使用するように設定

      エレキベア
      エレキベア
      ここを変更しないとGPUで計算されないクマね

      SimulationStageの追加

      マイケル
      マイケル
      そして次にSimulationStageをエミッタに追加します。 こちらは右上の「+Stage」ボタンから追加することができます。
      20240530_01_ue5_gpu_simulation_03
      ▲SimulationStageの追加

      20240530_01_ue5_gpu_simulation_04
      ▲任意の名前を付ける

      エレキベア
      エレキベア
      デフォルトではSimulationStageは入っていないのクマね

      スクラッチパッドの追加

      マイケル
      マイケル
      ここから少し複雑になりますが、SimulationStageの「+」ボタンから「スクラッチパッド」というモジュールを追加します。
      20240530_01_ue5_gpu_simulation_05
      ▲スクラッチパッドモジュールの追加

      20240530_01_ue5_gpu_simulation_06
      ▲こちらも任意の名前を付けておく

      マイケル
      マイケル
      スクラッチパッドは標準で用意されていないモジュールを作成したり、HLSLコードを記述することができるモジュールで、初期状態は下記のようになっています。
      20240530_01_ue5_gpu_simulation_07
      ▲スクラッチパッドの内容

      エレキベア
      エレキベア
      ノードベースになってるクマね ここに処理を書くことでモジュールとして扱えるということクマか
      マイケル
      マイケル
      ここで試しにHLSLコードを書いてみます。 CustsomHLSLノードを追加して下記のように記述します。
      20240530_01_ue5_gpu_simulation_08
      ▲CustomHLSLノードの追加

      float3 addVelocity = float3(10, 0, 0);
      OutPosition = InPosition + addVelocity;
      
      ▲速度を加算する処理
      マイケル
      マイケル
      Map Get、Map Setモジュールにそれぞれ「PARTICLE Position (各粒子の位置)」を追加し、CustomHLSLノードに接続することで入出力の変数が追加されます。 こちらの名前がHLSL内で参照できる変数名になるので、間違えないようにしましょう。
      20240530_01_ue5_gpu_simulation_09
      ▲パーティクルの位置を更新するよう設定

      マイケル
      マイケル
      コンパイル後にエフェクトを見てみると、加えた速度により放出される方向が変わっていることが確認できるはずです。
      20240530_01_ue5_gpu_simulation_11
      ▲放出される方向が変わる

      エレキベア
      エレキベア
      HLSLコードで操作するイメージが湧いたクマ〜〜

      ParticleAttributeReaderの追加

      マイケル
      マイケル
      以上でHLSLコードは書けるようになりましたが、実際にシミュレーション計算を行うには自身以外のパーティクル情報を参照する必要があるケースが多いです。 そのためにAttributeReaderという機能を介して参照できるよう設定します。
      エレキベア
      エレキベア
      他のパーティクルを参照できる機能があるのクマね
      マイケル
      マイケル
      AttributeReaderはエミッタの属性(変数)として定義してスクラッチパッドに渡すようにします。 というわけでまずはParticleAttributeReader型の変数を追加します。
      20240530_01_ue5_gpu_simulation_12
      ▲エミッタ属性にParticleAttributeReader型の属性を追加

      マイケル
      マイケル
      そしてエミッタのスポーンの「+」ボタンから「パラメータの追加」を追加し、用意したParticleAttributeReaderの属性を設定します。
      20240530_01_ue5_gpu_simulation_13
      ▲エミッタのスポーンに「パラメータの追加」を追加

      20240530_01_ue5_gpu_simulation_14
      ▲エミッタ属性に追加した属性を設定(エミッタ名は手入力)

      20240530_01_ue5_gpu_simulation_15
      ▲ここまで設定した状態

      エレキベア
      エレキベア
      これでエミッタのスポーン時にParticleAttributeRederが設定されるようになったクマね
      マイケル
      マイケル
      あとはスクラッチパッド内で、入力としてParticleAttributeReader型の属性を受け取るようにします。 これでHLSLコード内で特定インデックスのAttribute情報を取得することが出来るようになります。
      20240530_01_ue5_gpu_simulation_16
      ▲スクラッチパッドにParticleAttributeReaderとNumParticles(パーティクル数)を追加

      for (int i = 0; i < InNumParticles - 1; i++)
      {
          bool valid;
          float3 p;
          InParticleAttributeReader.GetVectorByIndex<Attribute="Position">(i, valid, p);
      
          // TODO ここでシミュレーション処理を行う
      }
      OutPosition = float3(1, 2, 3);
      
      ▲ParticleAttributeReader経由で各パーティクル情報を参照できるようになった
      20240530_01_ue5_gpu_simulation_17
      ▲エミッタの設定で作成したAttributeReaderをスクラッチパッドに追加する

      エレキベア
      エレキベア
      これでシミュレーション計算もやりたい放題クマね
      マイケル
      マイケル
      なお各パーティクルに設定した属性の値は、Attribute Spreadウィンドウより確認することができます。 属性がどのように変化しているかを確認できるので、こちらも状況に応じて活用しましょう。
      20240530_01_ue5_gpu_simulation_18
      ▲Attribute Spreadウィンドウにて、各パーティクルの属性を確認できる

      Boidsシミュレーションの実装例

      マイケル
      マイケル
      最後にSimulationStageを活用した実装例として、魚の群れをシミュレーションする例をざっくりと紹介します。 Boidsという群集シミュレーションアルゴリズムを使用しているのですが、こちらはUnityで実装した例を下記記事で紹介しているので興味がある方はご参照ください!
      【Unity】Boidsアルゴリズムを用いて魚の群集シミュレーションを実装する
      2024-05-28
      エレキベア
      エレキベア
      UnityではComputeShaderで実装したクマね
      マイケル
      マイケル
      今回実装した内容は下記のリポジトリにあげています。 手元で動かしたい方はこちらご使用ください。

      GitHub - ue5-boids-simulation

      エレキベア
      エレキベア
      これもエフェクトとして動いてるなんて面白いクマね

      ベースとなるエフェクトの実装

      マイケル
      マイケル
      シミュレーション計算を行う前に、まずはベースとなるエフェクトを実装します。 今回はEmpty状態のエミッタを作成した状態から始めました。
      20240530_01_ue5_gpu_simulation_20
      ▲Empty状態で作成して設定した状態

      マイケル
      マイケル
      そして各パラメータをそれぞれ下記のように設定します。
      20240530_01_ue5_gpu_simulation_21
      ▲パーティクルの数を設定

      20240530_01_ue5_gpu_simulation_22
      ▲パーティクルの生存期間を延ばす

      20240530_01_ue5_gpu_simulation_23
      ▲生成範囲をBox状で広めに取る

      20240530_01_ue5_gpu_simulation_24
      ▲ランダムに少しだけ動かす

      20240530_01_ue5_gpu_simulation_25
      ▲魚のスプライトマテリアルを設定

      マイケル
      マイケル
      以上で、Box形状でじわじわと魚が動くエフェクトが完成します。
      20240530_01_ue5_gpu_simulation_26
      ▲Box形状でじわじわと動くエフェクトが出来た

      エレキベア
      エレキベア
      ここから魚達を動かしていくクマね

      シミュレーション計算の実装

      マイケル
      マイケル
      シミュレーション計算の準備として、「2. Niaraga SimulationStageの環境構築」の手順に沿って ・エミッタのプロパティの設定 ・SimulationStageの追加 ・スクラッチパッドの追加 ・ParticleAttributeReaderの追加 を行います。
      20240530_01_ue5_gpu_simulation_27
      ▲SimulationStage、スクラッチパッド、ParticleAttributeReaderをそれぞれ設定

      エレキベア
      エレキベア
      SimulationStageを使える状態にするクマね
      マイケル
      マイケル
      そしてスクラッチパッド内にシミュレーション計算に必要なパラメータを用意し、最終的に速度として返却するように設定します。 パラメータの詳細については省略しますが、気になる方はUnity実装時の記事をご参照ください!
      20240530_01_ue5_gpu_simulation_28
      ▲シミュレーション計算に必要な項目を渡し、最終的には速度として設定する

      パラメータ
      概要
      SeparationDistance
      適用する他の個体との半径: 分離
      AlignmentDistance
      適用する他の個体との半径: 整列
      CohesionDistance
      適用する他の個体との半径: 結合
      SeparationCoefficient
      適用時の重み係数: 分離
      AlignmentCoefficient
      適用時の重み係数: 整列
      CohesionCoefficient
      適用時の重み係数: 結合
      WallAvoidanceCoefficient
      壁を避ける強さの重み
      SpaceScale
      シミュレーション範囲 サイズ
      エレキベア
      エレキベア
      Boidsシミュレーションは「分離」「整列」「結合」の状態を判定するのだったクマね
      マイケル
      マイケル
      HLSLによる計算は下記のように実装しました。 ParticleAttributeReaderから各パーティクルの値を取得しつつ、Boidsシミュレーションによる計算を行っています。
      OutVelocity = float3(0.0, 0.0, 0.0);
      
      float3 _SeparationPositionSum = float3(0.0, 0.0, 0.0);
      float3 _AlignmentVelocitySum = float3(0.0, 0.0, 0.0);
      float3 _CohesionPositionSum = float3(0.0, 0.0, 0.0);
      int _SeparationCount = 0;
      int _AlignmentCount = 0;
      int _CohesionCount = 0;
      
      bool _Valid;
      float3 _Acceleration = float3(0.0, 0.0, 0.0);
      
      // boidsの数だけチェック
      for(int i = 0; i < InN; i++)
      {
      	// 自身は対象としない
      	if(i==InID)
      	{
      		continue;
      	}
      
      	// 対象インデックスの位置を取得
      	float3 targetPosition;
      	InAttributeReader.GetVectorByIndex<Attribute="Position">(i, _Valid, targetPosition);
      
      	// 自身との位置差分
      	float3 diffPosition = InPosition - targetPosition;
      	float diffLength = length(diffPosition);
      
      	// 分離: 近づきすぎたら離れる
      	if (diffLength < InSeparationDistance)
      	{
      		_SeparationPositionSum += diffPosition;
      		_SeparationCount++;
      	}
      
          // 整列: 近くの向きに合わせる
      	if (diffLength < InAlignmentDistance)
      	{
      		float3 targetVelocity;
      		InAttributeReader.GetVectorByIndex<Attribute="Velocity">(i, _Valid, targetVelocity);
      		_AlignmentVelocitySum += targetVelocity;
      		_AlignmentCount++;
      	}
      
      	// 結合: 近くのboidsの重心に近づく
      	if (diffLength < InCohesionDistance)
      	{
      		_CohesionPositionSum += targetPosition;
      		_CohesionCount++;
      	}
      }
      
      // 平均を取り加速度に加算する
      if(_SeparationCount > 0)
      {
      	float3 separationPosition = _SeparationPositionSum / _SeparationCount;
      	_Acceleration += separationPosition * InSeparationCoefficient;
      }
      if(_AlignmentCount > 0)
      {
      	float3 alignmentVelocity = _AlignmentVelocitySum / _AlignmentCount;
      	_Acceleration += alignmentVelocity * InAlignmentCoefficient;
      }
      if(_CohesionCount > 0)
      {
      	float3 cohesionPosition = _CohesionPositionSum / _CohesionCount;
      	_Acceleration += (cohesionPosition - InPosition) * InCohesionCoefficient;
      }
      
      // 境界処理: 壁際に来たら反発力を加える
      float3 avoidance = float3(0, 0, 0);
      avoidance.x = (InPosition.x < -InSpaceScale) ? avoidance.x + 1.0 : avoidance.x;
      avoidance.x = (InPosition.x > InSpaceScale) ? avoidance.x - 1.0 : avoidance.x;
      avoidance.y = (InPosition.y < -InSpaceScale) ? avoidance.y + 1.0 : avoidance.y;
      avoidance.y = (InPosition.y > InSpaceScale) ? avoidance.y - 1.0 : avoidance.y;
      avoidance.z = (InPosition.z < -InSpaceScale) ? avoidance.z + 1.0 : avoidance.z;
      avoidance.z = (InPosition.z > InSpaceScale) ? avoidance.z - 1.0 : avoidance.z;
      _Acceleration += avoidance * InWallAvoidanceCoefficient;
      
      // 最終的な速度
      OutVelocity = InVelocity + _Acceleration;
      
      
      ▲Boidsシミュレーションの計算
      エレキベア
      エレキベア
      (これノードに直接書くの割とつらいクマね・・・)
      マイケル
      マイケル
      最後にスクラッチパッドに渡すパラメータ値を設定して微調整すれば実装は完了です。
      20240530_01_ue5_gpu_simulation_29
      ▲変数に値をそれぞれ設定

      20240530_01_ue5_gpu_simulation_30
      ▲速度が速くなりすぎないよう制限をかける

      20240530_01_ue5_gpu_simulation_31
      ▲スプライトのサイズを調整

      20240530_01_ue5_gpu_simulation_32
      ▲群れのシミュレーションが実装できた

      エレキベア
      エレキベア
      やったクマ〜〜〜〜

      おわりに

      マイケル
      マイケル
      というわけで今回はNiagaraのSimulationStageによるシミュレーション計算の実装でした! どうだったかな??
      エレキベア
      エレキベア
      環境構築は覚えないといけないクマが、シミュレーション計算部分の実装に注力できるのはいいクマね 一度作ったエミッタは使いまわせるのも魅力だと思ったクマ
      マイケル
      マイケル
      Boidsシミュレーションを実装するのに、Unityの時は描画周りの実装も大変だったから計算部分だけ実装すればいいのは大きな魅力だね!
      マイケル
      マイケル
      というわけで今回はこの辺で! アデューー!!
      エレキベア
      エレキベア
      クマ〜〜〜〜

      【UE5】Niagara SimulationStageによるシミュレーション環境構築 〜完〜


      UnrealEngineグラフィックスNiagaraGPGPU群集シミュレーション
      2024-05-30

      関連記事
      【書籍紹介】「コンピュータグラフィックス」に出てくる用語をまとめる【CGエンジニア検定】
      2024-07-13
      【Unity】Boidsアルゴリズムを用いて魚の群集シミュレーションを実装する
      2024-05-28
      【UE5】第三回 ミニゲーム制作で学ぶUnrealC++ 〜UI・仕上げ実装 編〜
      2024-05-18
      【UE5】第二回 ミニゲーム制作で学ぶUnrealC++ 〜キャラクター・ゲーム実装 編〜
      2024-05-18
      【UE5】第一回 ミニゲーム制作で学ぶUnrealC++ 〜UnrealC++の概要 編〜
      2024-05-18
      【Unity】第二回 シェーダーライティング入門 〜テクスチャマップを使用したライティング〜(法線マップ、スペキュラマップ、AOマップ)【シェーダー】
      2023-03-14
      【Unity】第一回 シェーダーライティング入門 〜基本のライティング〜(Lambert、Phong、HalfLambert、Blinn-Phong、リムライト)【シェーダー】
      2023-02-28
      【Unity】URPでシェーダー実装した際に発生した不具合と対処方法まとめ
      2023-02-13