0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Unreal Engine 5のNiagaraのnamespaceを紐解く

Last updated at Posted at 2024-09-07

概要

Niagaraのnamespaceを、生成されるHLSLを確認しながら、どのように実装されているかを見ていきます。

Niagaraにおけるnamespaceとは、Particles, Transient, LocalなどのUnreal Engineで定義済みの名前です。

エディタ上では、
image.png
image.png

のように、すべて大文字で表記されます。

HLSLにおいてはParticlesやTransient等のnamespaceは、

HLSL
Context.Map.Particles.Position

のように、入れ子の構造体として実装されています。

namespace modifierはnamespaceを更にもう一段入れ子にします。

HLSL
Context.Map.Particles.Initial.Position

namespace及びnamespace modifierは単なる名前ではなく、Niagaraによって名前ごとの機能が与えられており、それら機能を以下で紐解いていきます。エンジンのバージョンは5.3を使用しました。

一般的なプログラミング言語で言うnamespaceとは意味が異なる点は注意します。一般的なプログラミング言語のnamespaceは名前衝突を避ける以上の機能はありませんが、Niagaraにおいては各namespaceはそれぞれ明確に用途が定められており、namespaceが違うと生成するコードにも違いが生じます。

各namespaceについて

Particles

image.png

パーティクル単位で値を持ち、フレームを超えて値が保持されるのが特徴です。

Particles Update Scriptでは以下のように定義されます。

Particle Update Script
/*0112*/		struct FParamMap0_Particles
/*0113*/		{
/*0114*/			float Age;
/*0115*/			float Lifetime;
/*0116*/			float Mass;
/*0117*/			float MaterialRandom;
/*0118*/			float MyParticlesFloat;    //←ここ
/*0119*/			float NormalizedAge;
/*0120*/			float3 Position;
/*0121*/			FParamMap0_Particles_Previous Previous;
/*0122*/			float RibbonUVDistance;
/*0123*/			FParamMap0_Particles_ShapeLocation ShapeLocation;
/*0124*/			int UniqueID;
/*0125*/		};

PositionやAgeなど、エピックゲームズで定義しているものが主ですが、VFX開発者が自由に定義することも出来、MyParticlesFloatがそれになります。

FParamMap0_Particles各値はフレームを超えて持ち越されます。そのためのコードが以下の通り自動的に生成されます。

Particle Update Script
Context.Map.Particles.MyParticlesFloat = InputDataFloat(0, 4);
...
OutputDataFloat(0, 4, TmpWriteIndex, Context.Map.Particles.MyParticlesFloat);

Transient

image.png

パーティクル単位で値を持てるのは Particlesと同等ですが、フレームを超えて値を持ち越すことができません。

Particles Update Scriptでは以下のように定義されます。

Particle Update Script
/*0086*/		struct FParamMap0_Transient
/*0087*/		{
/*0088*/			bool FirstFrame;
/*0089*/			float MyTransientFloat;   //←ここ
/*0090*/		};

デフォルトで定義されているものは bool FirstFrame; だけで、VFX開発者が定義するものが主になります。

Local

image.png

パーティクル単位で値を持てるのは Transientと同等ですが、モジュールのインスタンス毎に領域が割り当てられる点においてTransientとは異なっています。よって、別のモジュールに同名の変数を定義した場合でも、別の変数として扱われます。

以下は2つのモジュールから、TransientとLocalで同名変数を作り、それぞれ値を代入した場合に生成されるコードです。

Particle Update Script
struct FParamMap0_Transient
{
	bool FirstFrame;
    float MyTransientFloat;
};

...

struct FParamMap0_Local_MyScratchModule2
{
	float MyLocalFloat;
};

struct FParamMap0_Local_MyScratchModule1
{
    float MyLocalFloat;
};
		
struct FParamMap0_Local
{
    FParamMap0_Local_MyScratchModule1 MyScratchModule1;
    FParamMap0_Local_MyScratchModule2 MyScratchModule2;
};

...

struct FParamMap0
{
    FParamMap0_Array Array;
    FParamMap0_DataInstance DataInstance;
    FParamMap0_Emitter Emitter;
    FParamMap0_Engine Engine;
	FParamMap0_Local Local;
	FParamMap0_OUTPUT_VAR OUTPUT_VAR;
    FParamMap0_Particles Particles;
    FParamMap0_ParticleState ParticleState;
    FParamMap0_Transient Transient;
};

...

void MyScratchModule1_Emitter_Func_(inout FSimulationContext Context)
{
	float Constant8 = 0;
	float Constant9 = 0;
	Context.Map.Local.MyScratchModule1.MyLocalFloat = Constant8;
	Context.Map.Transient.MyTransientFloat = Constant9;
}

void MyScratchModule2_Emitter_Func_(inout FSimulationContext Context)
{
	float Constant10 = 1;
	float Constant11 = 1;
	Context.Map.Local.MyScratchModule2.MyLocalFloat = Constant10;
	Context.Map.Transient.MyTransientFloat = Constant11;
}

2つのモジュールから各変数に値を代入するコードが生成されました。Transientは同一の変数に対して操作が行われる一方で、Localは異なる構造体のインスタンスに対して操作を行っていることがわかります。

Emitter

Particlesに似ていますが、emitter stageにおいてのみ書き込みが可能です。
particle stageにおいては、Map Getは可能ですが、Map Setは不可能です。

例えば、particle stageに以下のノードを配置したとします。

image.png

Map SetでEmitter namespaceを指定したことでエラーになってしまいました。

image.png

そこで以下のように、Map SetをLocalネームスペースに変更し、コンパイルを通してHLSLを見てみます。

image.png

Particle Update Script
cbuffer FNiagaraExternalParameters
{
    float Emitter_MyEmitterFloat;
}

...

struct FParamMap0_Emitter
{
    float Age;
    float MyEmitterFloat;
    int RandomSeed;
};

....

void MyScratchModule2_Emitter_Func_(inout FSimulationContext Context)
{
    Context.Map.Local.MyScratchModule2.MyLocalFloat = Context.Map.Emitter.MyEmitterFloat;
}

....

void SimulateMain()
{
    ....
    Context.Map.Emitter.MyEmitterFloat = Emitter_MyEmitterFloat;
    ....
}

Emitter namespaceはparticle stageにおいて書き込めない理由、それは実体がcbufferであるためということがわかりました。

もちろん、emitter stageにおいてはEmitter namespaceにMap Setすることが可能となっています。

StackContext

StackContext namespaceは、emitter stageではEmitter namespace、particle stageではParticles namespaceに翻訳されるようです。

STACKCONTEXTにマウスポインタをあてると説明文が出てきました。

image.png

実験として、emitter stageの "Emitter Update"で以下のノードを書いてみます。

image.png

結論から言うと、MyEmitterFloatとMyStackContextFloatは全く同等のコードを生成しました。

System Update Script
struct FParamMap0_Particles
{
    float MyParticlesFloat;
};

...

struct FParamMap0_Empty
{
    float Age;
    float CurrentLoopDelay;
    float CurrentLoopDuration;
    float DistanceTraveled;
    int ExecutionState;
    int ExecutionStateSource;
    int LoopCount;
    float LoopedAge;
	float MyEmitterFloat;
    float MyStackContextFloat;
    float NormalizedLoopAge;
    FParamMap0_Empty_Scalability Scalability;
    FParamMap0_Empty_SpawnRate SpawnRate;
};

...

void MyScratchModuleEmitterUpdate_Empty_Func_(inout FSimulationContext Context)
{
    float Constant68 = 0;
    float Constant69 = 0;
    float Constant70 = 0;
    Context.Map.Empty.MyEmitterFloat = Constant68;
    Context.Map.Particles.MyParticlesFloat = Constant69;
    Context.Map.Empty.MyStackContextFloat = Constant70;
}

...

void SimulateMain()
{
...
Context.Map.Empty.MyEmitterFloat = InputDataFloat(0, 5);
Context.Map.Empty.MyStackContextFloat = InputDataFloat(0, 6);
...
OutputDataFloat(0, 5, TmpWriteIndex, Context.Map.Empty.MyEmitterFloat);
OutputDataFloat(0, 6, TmpWriteIndex, Context.Map.Empty.MyStackContextFloat);
...
}

Context.Map.Particles.MyParticlesFloat は、コンパイルエラーこそ出さなかったものの、これを保存するコードがないため代入した値は捨てられるものと思われます。

また、同様にparticles stageの Particle Updateでも同様の実験をしてみました。

image.png

案の定、MyParticlesFloatとMyStackContextFloatは同等のコードを生成しました。

Particle Update Scriptは以下のとおりです。

Particle Update Script
struct FParamMap0_Particles
{
    float Age;
    float Lifetime;
    float Mass;
    float MaterialRandom;
    float MyParticlesFloat;
    float MyStackContextFloat;
    float NormalizedAge;
    float3 Position;
    FParamMap0_Particles_Previous Previous;
    float RibbonUVDistance;
    FParamMap0_Particles_ShapeLocation ShapeLocation;
    int UniqueID;
};

...

void MyScratchModule1_Emitter_Func_(inout FSimulationContext Context)
{
    float Constant7 = 0;
    float Constant8 = 0;
    float Constant9 = 0;
    float Constant10 = 0;
    Context.Map.Particles.MyParticlesFloat = Constant7;
    Context.Map.Local.MyScratchModule1.MyLocalFloat = Constant8;
    Context.Map.Transient.MyTransientFloat = Constant9;
    Context.Map.Particles.MyStackContextFloat = Constant10;
}

...

void SimulateMain()
{
...
    Context.Map.Particles.MyParticlesFloat = InputDataFloat(0, 4);
    Context.Map.Array.MyParticlesFloat = 4;
    Context.Map.Particles.MyStackContextFloat = InputDataFloat(0, 5);
    Context.Map.Array.MyStackContextFloat = 5;
...
    OutputDataFloat(0, 4, TmpWriteIndex, Context.Map.Particles.MyParticlesFloat);
    OutputDataFloat(0, 5, TmpWriteIndex, Context.Map.Particles.MyStackContextFloat);
...
}

また、Particle UpdateからEmitter Updateで設定した StackContext namespace のMyStackContextFloat に Emitter namespaceでアクセスできることを確認しました。

emitter stageで設定した値をparticle stageで読み込み、Particles.Colorに代入して表示される色が変化することを確認しました。

image.png

この際、cbufferとしてemitter stageから値が渡されていることがわかります。

Particle Update Script
cbuffer FNiagaraExternalParameters
{
    float Emitter_MyStackContextFloat;
}

ここで以下のようにStackContext namespaceを指定すると、ここはparticle stageであるためParticles namespaceと翻訳され、Particles.MyStackContextFloatが読み込まれることになります。

image.png

Input

モジュールの引数として使われるInput namespaceを見てみます。

image.png

image.png

特に引数に何も指定していなかった場合、cbufferが実体となりました。

Particle Update Script
cbuffer FNiagaraExternalParameters
{
    float Constants_Emitter_MyScratchModule1_MyInputFloat;
}

...

void MyScratchModule1_Emitter_Func_(inout FSimulationContext Context)
{
    Context.Map.Particles.MyParticlesFloat = Constants_Emitter_MyScratchModule1_MyInputFloat;
}

定数ではなく変数を引数に渡してみます。

image.png

すると、FParamMap0_MyScratchModule1というモジュールの引数を集めた構造体を経由して、値が渡されるようになりました。

Particle Update Script
struct FParamMap0_MyScratchModule1
{
    float MyInputFloat;
};

...

struct FParamMap0
{
    FParamMap0_Array Array;
    FParamMap0_DataInstance DataInstance;
    FParamMap0_Emitter Emitter;
    FParamMap0_Engine Engine;
	FParamMap0_Local Local;
	FParamMap0_MyScratchModule1 MyScratchModule1;
	FParamMap0_OUTPUT_VAR OUTPUT_VAR;
	FParamMap0_Particles Particles;
	FParamMap0_ParticleState ParticleState;
	FParamMap0_Transient Transient;
};

...

void MyScratchModule1_Emitter_Func_(inout FSimulationContext Context)
{
    Context.Map.Particles.MyParticlesFloat = Context.Map.MyScratchModule1.MyInputFloat;
}

void Simulate(inout FSimulationContext Context)
{
...
	Context.Map.MyScratchModule1.MyInputFloat = Context.Map.Particles.Age;
...
}

Output

Output namespaceを指定すると、

image.png

という、間に MODULE というキーワードが挟まります。
ノードは以下のように組みました。

image.png

このHLSLを見てみます。

Particle Update Script
struct FParamMap0_OUTPUT_VAR_MyScratchModule1
{
    float MyOutputFloat;
};

struct FParamMap0_OUTPUT_VAR
{
    FParamMap0_OUTPUT_VAR_MyScratchModule1 MyScratchModule1;
    FParamMap0_OUTPUT_VAR_ParticleState ParticleState;
};

...

struct FParamMap0
{
    FParamMap0_Array Array;
    FParamMap0_DataInstance DataInstance;
	FParamMap0_Emitter Emitter;
    FParamMap0_Engine Engine;
    FParamMap0_Local Local;
	FParamMap0_MyScratchModule1 MyScratchModule1;
    FParamMap0_OUTPUT_VAR OUTPUT_VAR;
    FParamMap0_Particles Particles;
	FParamMap0_ParticleState ParticleState;
	FParamMap0_Transient Transient;
};

...

void MyScratchModule1_Emitter_Func_(inout FSimulationContext Context)
{
    Context.Map.OUTPUT_VAR.MyScratchModule1.MyOutputFloat = Context.Map.MyScratchModule1.MyInputFloat;
}

値はフレームを跨ぐための保存がなされないので、生成されるコードの性質はTransientに近いです。
Transientと異なるのは、その使い方です。
Output namespaceはMap Getで値を取得することはできず、そのかわりにモジュールの引数に指定できます。

image.png

その際の名前は、
image.png
のように、モジュール内で書き込む時には MODULE とされていた部分がモジュールの別名に置き換わっています。

この別名が便利で、同じ種類のモジュールを複数組み込んだ場合、モジュールの別名が異なることによりそれぞれの出力値が上書きされることなく保持されます。

具体的な使用例としては、以下の様に Static Mesh Locationを2つ使用して二者をLerp Positionというdynamic inputで線形補間します。この例により、モジュールの別名があることで2つのStatic Mesh Locationの出力値を区別できていることがわかります。

image.png

User

image.png

Blueprint等、外部から値を設定出来る変数になります。
cbufferで実装されていました。つまり、エミッタやパーティクルからは読み込み専用であり、原理的に書き込みはできません。

Particle Update Script
cbuffer FNiagaraExternalParameters
{
    float4 User_MyParticleColor;
}

struct FParamMap0_User
{
    float4 MyParticleColor;
};
...
void SimulateMain()
{
...
	Context.Map.User.MyParticleColor = User_MyParticleColor;
...
	Simulate(Context);
...
}

System

image.png

エミッタを束ねる Niagara Systemから設定できる値となります。

下記のように、パーティクルの色をRGをNiagara Systemから、GをNiagara Systemから設定しつつもStackcontext namespaceとし、AをUserから設定しました。

image.png

System stageのStackcontext namespaceはSystem namespaceと解釈されそうですが、当方で試した結果正しく値が設定されませんでした。Stackcontext namespaceをSystem namespaceに変更すると正しく動作しました。

コードを見ると、UserとSystemは同一のcbufferに配置されていました。

Particle Update Script
cbuffer FNiagaraExternalParameters
{
	float System_MySystemFloat;
	float System_MySystemStackcontextFloat;
	float System_MySecondSystemFloat;
	float User_MyUserFloat;
}

FNiagaraExternalParametersは、emitter stageからparticle stageに値が渡る場合のその値の格納場所でもあり、モジュールの引数に定数を用いた場合にその定数が格納される場所でもあります。つまり、定数値、User namespace、他のstageからの値渡しはみな同じcbufferを用いるため、パフォーマンスに大差ないと推測できます。

Engine

Unreal Engineから与えられ、VFX開発者からは読み取り専用の一連の変数です。以下はfloat型変数をengineの名前でフィルタリングした結果です。

image.png

Engine namespaceで一括りにされてはいますが、コードを確認すると複数のcbufferに分割されています。

Particle Update Script
cbuffer FNiagaraGlobalParameters
{
	float Engine_WorldDeltaTime;
	float Engine_DeltaTime;
	float Engine_InverseDeltaTime;
	float Engine_Time;
	float Engine_RealTime;
	int Engine_QualityLevel;
	int Engine_PaddingInt32_0;
	int Engine_PaddingInt32_1;
}

cbuffer FNiagaraSystemParameters
{
	float Engine_Owner_TimeSinceRendered;
	float Engine_Owner_LODDistance;
	float Engine_Owner_LODDistanceFraction;
	float Engine_System_Age;
	int Engine_Owner_ExecutionState;
	int Engine_System_TickCount;
	int Engine_System_NumEmitters;
	int Engine_System_NumEmittersAlive;
	int Engine_System_SignificanceIndex;
	int Engine_System_RandomSeed;
	int Engine_System_CurrentTimeStep;
	int Engine_System_NumTimeSteps;
	float Engine_System_TimeStepFraction;
	int Engine_System_NumParticles;
	int Engine_System_PaddingInt32_0;
	int Engine_System_PaddingInt32_1;
}

cbuffer FNiagaraOwnerParameters
{
	float4x4 Engine_Owner_SystemLocalToWorld;
	float4x4 Engine_Owner_SystemWorldToLocal;
	float4x4 Engine_Owner_SystemLocalToWorldTransposed;
	float4x4 Engine_Owner_SystemWorldToLocalTransposed;
	float4x4 Engine_Owner_SystemLocalToWorldNoScale;
	float4x4 Engine_Owner_SystemWorldToLocalNoScale;
	float4 Engine_Owner_Rotation;
	float3 Engine_Owner_Position;
	int Engine_Owner_PaddingInt32_0;
	float3 Engine_Owner_Velocity;
	int Engine_Owner_PaddingInt32_1;
	float3 Engine_Owner_SystemXAxis;
	int Engine_Owner_PaddingInt32_2;
	float3 Engine_Owner_SystemYAxis;
	int Engine_Owner_PaddingInt32_3;
	float3 Engine_Owner_SystemZAxis;
	int Engine_Owner_PaddingInt32_4;
	float3 Engine_Owner_Scale;
	int Engine_Owner_PaddingInt32_5;
	float4 Engine_Owner_LWCTile;
}

cbuffer FNiagaraEmitterParameters
{
	int Engine_Emitter_NumParticles;
	int Engine_Emitter_TotalSpawnedParticles;
	float Engine_Emitter_SpawnCountScale;
	float Emitter_Age;
	int Emitter_RandomSeed;
	int Engine_Emitter_InstanceSeed;
	int Engine_Emitter_PaddingInt32_0;
	int Engine_Emitter_PaddingInt32_1;
}

おそらく、FNiagaraGlobalParametersは全てのNiagara Systemのインスタンスで共通、FNiagaraEmitterParametersはエミッタ単位、FNiagaraSystemParametersはNiagara System単位、FNiagaraOwnerParametersはアクターかNiagara Component単位でcbufferが生成されているのではと推測します。

Namespace Modifierについて

namespaceと変数名の間にもう一つ名前が挟まることがあります。これをNamespace Modifierと呼びます。

image.png

image.png

Initial

Initial Namespace Modifierは対応する名前の変数の初期値を保持します。

Particles.Initial.Position等の形をとります。エディタで見ると、

image.png

のように見えます。

Initial Namespace Modifierは明示的に生成する必要はありません。仮想的にはParticle Spawnステージの最後の時点で存在しているParticle namespaceの変数の全てを対象とし、Initial Namespace Modifierが付与されてその初期値が複製されます。

例えば、Particle Spawnステージの最後でParticle.Colorが存在していると、Particle.Initial.Colorに自動的に値が複製されます。

image.png

Particle Spawnステージの最後の時点の値がInitial Namespace Modifierが付与されて複製されることを実験で確かめてみます。Particles.MyColorという変数を三箇所で設定し、Particles.Initial.MyColorをパーティクルの色として設定するようにします。

image.png

プレビューに表示されたパーティクルの色から、Particle Spawnステージの最後で設定している水色がParticles.Initial.MyColorに複製されている事が確認できました。

仮想的にはParticle Spawnステージの最後で存在するParticles namespaceの変数全てがInitial Namespace Modifierが付与されて複製されます。しかし、実際の使用実績がないとコードは省略されるようです。

例えば、Particle Updateステージでfloat型の変数の一覧を見ると、Lifetime、Mass、MaterialRandomが選択肢にあります。
image.png

生成されたコードを見てみます。

Particle Update Script
struct FParamMap0_Particles_Initial
{
    float4 MyColor;
};

struct FParamMap0_Particles
{
    float Age;
    float4 Color;
    FParamMap0_Particles_Initial Initial;
    float Lifetime;
    float Mass;
    float MaterialRandom;
    float4 MyColor;
    float NormalizedAge;
	float3 Position;
    FParamMap0_Particles_Previous Previous;
    float RibbonUVDistance;
    FParamMap0_Particles_ShapeLocation ShapeLocation;
    float2 SpriteSize;
    int UniqueID;
};
...


void SimulateMain()
{
...
    Context.Map.Particles.Initial.MyColor.r = InputDataFloat(0, 6);
    Context.Map.Array.Initial.MyColor = 6;
    Context.Map.Particles.Initial.MyColor.g = InputDataFloat(0, 7);
    Context.Map.Array.Initial.MyColor = 7;
    Context.Map.Particles.Initial.MyColor.b = InputDataFloat(0, 8);
    Context.Map.Array.Initial.MyColor = 8;
    Context.Map.Particles.Initial.MyColor.a = InputDataFloat(0, 9);
    Context.Map.Array.Initial.MyColor = 9;
...
    Simulate(Context);
...
    OutputDataFloat(0, 6, TmpWriteIndex, Context.Map.Particles.Initial.MyColor.r);
    OutputDataFloat(0, 7, TmpWriteIndex, Context.Map.Particles.Initial.MyColor.g);
    OutputDataFloat(0, 8, TmpWriteIndex, Context.Map.Particles.Initial.MyColor.b);
    OutputDataFloat(0, 9, TmpWriteIndex, Context.Map.Particles.Initial.MyColor.a);
...
}

Initial namespace modifierが付与された変数のうち、実際にコードとして生成されていたのはParticle Updateステージで使用実績のあったParticles.Initial.MyColorのみです。一方、変数の一覧にあったParticles.Initial.Lifetimeその他は、仮想的には存在しているもののその実体を裏付けるコードは生成されていません。

Previous

Previous namespace modifierは対応する名前の変数の前回の値を保持します。

Particles.Previous.Position等の形をとります。エディタで見ると、

image.png

のように見えます。

Previous namespace modifierは明示的に生成する必要がありません。Particle Updateステージで使用されるParticles namespaceの変数であれば、対応するPrevious namespace modifierが付与された変数が仮想的に存在しています。

値が複製されるタイミングは、Particle Updateステージの最初です。値に変更を加える前であるため、前フレームの値となるわけです。

Previous namespace modifierも実際に使用することがコード実体を自動生成する条件となっています。例えば以下のようにMap GetでPrevious Namespace Modifierを指定して値を取得します。

image.png

このモジュールを使うと、以下のようなコードが得られました。

Particle Update Script
struct FParamMap0_Particles_Previous
{
    float4 Color;
    float Mass;
    float3 Position;
    float2 SpriteSize;
    float3 Velocity;
};
...
struct FParamMap0_Particles
{
    float Age;
    float4 Color;
    float DistanceTraveled;
    float Lifetime;
    float Mass;
    float MaterialRandom;
    float NormalizedAge;
    float3 Position;
    FParamMap0_Particles_Presolve Presolve;
    FParamMap0_Particles_Previous Previous;
    float RibbonUVDistance;
    FParamMap0_Particles_ShapeLocation ShapeLocation;
    float2 SpriteSize;
    int UniqueID;
    float3 Velocity;
};
...
void SimulateMain()
{
...
    Context.Map.Particles.Previous.Color = 	Context.Map.Particles.Color;
    Context.Map.Particles.Previous.Mass = 	Context.Map.Particles.Mass;
    Context.Map.Particles.Previous.Position = 	Context.Map.Particles.Position;
    Context.Map.Particles.Previous.SpriteSize = 	Context.Map.Particles.SpriteSize;
    Context.Map.Particles.Previous.Velocity = 	Context.Map.Particles.Velocity;
...
    Simulate(Context);
...
    OutputDataFloat(0, 22, TmpWriteIndex, Context.Map.Particles.Previous.Color.r);
    OutputDataFloat(0, 23, TmpWriteIndex, Context.Map.Particles.Previous.Color.g);
    OutputDataFloat(0, 24, TmpWriteIndex, Context.Map.Particles.Previous.Color.b);
    OutputDataFloat(0, 25, TmpWriteIndex, Context.Map.Particles.Previous.Color.a);
    OutputDataFloat(0, 26, TmpWriteIndex, Context.Map.Particles.Previous.Mass);
    OutputDataFloat(0, 27, TmpWriteIndex, Context.Map.Particles.Previous.Position.x);
    OutputDataFloat(0, 28, TmpWriteIndex, Context.Map.Particles.Previous.Position.y);
    OutputDataFloat(0, 29, TmpWriteIndex, Context.Map.Particles.Previous.Position.z);
    OutputDataFloat(0, 30, TmpWriteIndex, Context.Map.Particles.Previous.SpriteSize.x);
    OutputDataFloat(0, 31, TmpWriteIndex, Context.Map.Particles.Previous.SpriteSize.y);
    OutputDataFloat(0, 32, TmpWriteIndex, Context.Map.Particles.Previous.Velocity.x);
    OutputDataFloat(0, 33, TmpWriteIndex, Context.Map.Particles.Previous.Velocity.y);
    OutputDataFloat(0, 34, TmpWriteIndex, Context.Map.Particles.Previous.Velocity.z);
...
}

フレームの最初にParticles.Previous.ColorとContext.Map.Particles.Previous.Massへ、
それぞれ対応する変数から値を代入していることが確認できました。Particles.Previous.Velocity等も同様に処理されていますが、これはSolve Forces And Velocity等の既存モジュールの中で使われているために生成されています。

Module

Module namespace modifierをモジュール内で使うと、エミッタで使用時にその名前がエミッタにおけるモジュールの別名に置き換わります。これは、Output namespaceで自動的に付与されるModuleというキーワードの正体です。

実は、Module namespace modifierはOutput namespace以外と組み合わせて使うことができます。

image.png

Particles namespaceとModule namespace modifierを組み合わせた例です。Output namespaceと異なるのは、フレームを超えてその値が保持されるコードが生成されることです。例えば以下のようなモジュールNMS_ColorModuleを作成します。

image.png

このモジュールをエミッタに3つ連ねて配置しました。

image.png

生成されるコードを確認します。

Particle Update Script
struct FParamMap0_Particles_NMS_ColorModule
{
	float4 ModuleColor;
};

struct FParamMap0_Particles
{
	float Age;
	float Lifetime;
	float Mass;
	float MaterialRandom;
	FParamMap0_Particles_NMS_ColorModule NMS_ColorModule;
	FParamMap0_Particles_NMS_ColorModule001 NMS_ColorModule001;
	FParamMap0_Particles_NMS_ColorModule002 NMS_ColorModule002;
	float NormalizedAge;
	float3 Position;
	FParamMap0_Particles_Previous Previous;
	float RibbonUVDistance;
	int UniqueID;
};
...
void SimulateMain()
{
...
	Context.Map.Particles.NMS_ColorModule.ModuleColor.r = InputDataFloat(0, 4);
	Context.Map.Array.NMS_ColorModule.ModuleColor = 4;
	Context.Map.Particles.NMS_ColorModule.ModuleColor.g = InputDataFloat(0, 5);
	Context.Map.Array.NMS_ColorModule.ModuleColor = 5;
	Context.Map.Particles.NMS_ColorModule.ModuleColor.b = InputDataFloat(0, 6);
	Context.Map.Array.NMS_ColorModule.ModuleColor = 6;
	Context.Map.Particles.NMS_ColorModule.ModuleColor.a = InputDataFloat(0, 7);
	Context.Map.Array.NMS_ColorModule.ModuleColor = 7;
	Context.Map.Particles.NMS_ColorModule001.ModuleColor.r = InputDataFloat(0, 8);
	Context.Map.Array.NMS_ColorModule001.ModuleColor = 8;
	Context.Map.Particles.NMS_ColorModule001.ModuleColor.g = InputDataFloat(0, 9);
	Context.Map.Array.NMS_ColorModule001.ModuleColor = 9;
	Context.Map.Particles.NMS_ColorModule001.ModuleColor.b = InputDataFloat(0, 10);
	Context.Map.Array.NMS_ColorModule001.ModuleColor = 10;
	Context.Map.Particles.NMS_ColorModule001.ModuleColor.a = InputDataFloat(0, 11);
	Context.Map.Array.NMS_ColorModule001.ModuleColor = 11;
	Context.Map.Particles.NMS_ColorModule002.ModuleColor.r = InputDataFloat(0, 12);
	Context.Map.Array.NMS_ColorModule002.ModuleColor = 12;
	Context.Map.Particles.NMS_ColorModule002.ModuleColor.g = InputDataFloat(0, 13);
	Context.Map.Array.NMS_ColorModule002.ModuleColor = 13;
	Context.Map.Particles.NMS_ColorModule002.ModuleColor.b = InputDataFloat(0, 14);
	Context.Map.Array.NMS_ColorModule002.ModuleColor = 14;
	Context.Map.Particles.NMS_ColorModule002.ModuleColor.a = InputDataFloat(0, 15);
	Context.Map.Array.NMS_ColorModule002.ModuleColor = 15;
...
	Simulate(Context);
...
	OutputDataFloat(0, 4, TmpWriteIndex, Context.Map.Particles.NMS_ColorModule.ModuleColor.r);
	OutputDataFloat(0, 5, TmpWriteIndex, Context.Map.Particles.NMS_ColorModule.ModuleColor.g);
	OutputDataFloat(0, 6, TmpWriteIndex, Context.Map.Particles.NMS_ColorModule.ModuleColor.b);
	OutputDataFloat(0, 7, TmpWriteIndex, Context.Map.Particles.NMS_ColorModule.ModuleColor.a);
	OutputDataFloat(0, 8, TmpWriteIndex, Context.Map.Particles.NMS_ColorModule001.ModuleColor.r);
	OutputDataFloat(0, 9, TmpWriteIndex, Context.Map.Particles.NMS_ColorModule001.ModuleColor.g);
	OutputDataFloat(0, 10, TmpWriteIndex, Context.Map.Particles.NMS_ColorModule001.ModuleColor.b);
	OutputDataFloat(0, 11, TmpWriteIndex, Context.Map.Particles.NMS_ColorModule001.ModuleColor.a);
	OutputDataFloat(0, 12, TmpWriteIndex, Context.Map.Particles.NMS_ColorModule002.ModuleColor.r);
	OutputDataFloat(0, 13, TmpWriteIndex, Context.Map.Particles.NMS_ColorModule002.ModuleColor.g);
	OutputDataFloat(0, 14, TmpWriteIndex, Context.Map.Particles.NMS_ColorModule002.ModuleColor.b);
	OutputDataFloat(0, 15, TmpWriteIndex, Context.Map.Particles.NMS_ColorModule002.ModuleColor.a);
...
}

3つのモジュールのインスタンス毎にフレームを超えて値を保存するためのコードが生成されています。同一のモジュールの異なるインスタンスにそれぞれ異なるパラメータを与え、それぞれ独立に何かをシミュレートさせる、という用途に活用できそうです。

Custom

namespace modifierは自由に名前をつけることもできます。その場合は特にnamespace modifierとしての機能が付与されるわけではなく、単に名前の一部になります。
例えば以下は、 Particles.MyTestModifier.MyTest という変数に値を設定しています。

image.png

生成されるコードは以下の通りです。他のnamespace modifier同様に入れ子の構造体になります。それ以外は通常の変数と何もかわりません。

Particle Update Script
struct FParamMap0_Particles_MyTestModifier
{
	float MyTest;
};

struct FParamMap0_Particles
{
	float Age;
	float4 Color;
	float Lifetime;
	float Mass;
	float MaterialRandom;
	FParamMap0_Particles_MyTestModifier MyTestModifier;
	float NormalizedAge;
	float3 Position;
	FParamMap0_Particles_Previous Previous;
	float RibbonUVDistance;
	FParamMap0_Particles_ShapeLocation ShapeLocation;
	float2 SpriteSize;
	int UniqueID;
};
...
void SimulateMain()
{
...
    Context.Map.Particles.MyTestModifier.MyTest = InputDataFloat(0, 8);
    Context.Map.Array.MyTestModifier.MyTest = 8;
...
    Simulate(Context);
...
    OutputDataFloat(0, 8, TmpWriteIndex, Context.Map.Particles.MyTestModifier.MyTest);
...
}

その他

Output namespaceには2つ目のnamespace modifierを追加で付けることができます。

image.png

出力値は他のモジュールの引数として使用できました。

image.png

2つ目のnamespace modifierにもModuleという名前をつけることもできます。

image.png

その際、モジュールの別名に置き換えられるのは1つ目のModule namespace modifierのみとなりました。

image.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?