【ue4】【原理】特效系统Cascade与Niagara

Cascade 原理

编辑器

Cascade 的实质是一个编辑器。 就像角色编辑器(Persona)下的骨骼编辑器(SkeletonEditor)、动画编辑器(AnimationEditor)等一样。

下图为整个 Cascade 模块的源码目录, 它位于 Engine\Source\Editor 下。

FCascadeModule

FCascadeModule 为 整个 Module 的入口

可以看到, 它包含一个 FCascade* 类型的数组, 而 FCascade 则是一个粒子编辑器的实体,后面会分解。

当我们双击一个粒子资源打开一个新的粒子编辑器时, FCascadeModule 便会调用 CreateCascade() 往数组里添加一个新的 FCascade 实例

FCascade

FCascade 则为整个__编辑器__的逻辑聚集地。

一个 FCascade 类的对象就是一个粒子编辑器窗口。

而我们在这个窗口下进行的所有操作,如增/删发射器、增/删 Module、预览界面,都在这个类实现。

所以这个类就需要包含一个 UParticleSystem 和一个 UCascadeParticleSystemComponent 类来对上述操作进行支持。 其中 UCascadeParticleSystemComponent 继承自 UParticleSystemComponent

UParticleSystemUParticleSystemComponent 则为 Engine\Source\Runtime\Engine\Classes\Particles 文件夹下定义的一些类。 除了 Classes 文件夹, PrivatePublic 里也有一些对应的文件。

所以说事实上 Cascade 特效系统实际上是基于引擎内部运行时的 Engine Module 中的 Particles 部分而封装的一个上层GUI编辑器。

下面我们来分解一下这个在 Engine Module 内拥有一席之地的 Particles

我们先来看几个关键的类, 然后再分析一下这个粒子系统的 模块 技术, 最后尝试建立自定义的 模块, 以对该粒子系统进行扩展。

Emiiter.h

AEmitter 类为拖动 ParticleSystem 类型的资源到场景中自动生成的 Actor 类。 这一点前面也提到了。

Component

Actor 类包含三个 Component 组件,其中最重要的便是 UParticleSystemComponent, 用来执行粒子系统相关操作,这个放在后面详解。 可通过 PSC->GetOwner() 获得当前 PSC 所属的 AEmitter 类对象。

还有两个分别是 UBillboardComponentUArrowComponent, 分别为 始终朝向相机的2D图片和 表示图片方向的箭头。 它们只在定义了 WITH_EDITORONLY_DATA宏 的情况下才被定义, 在构造函数里被初始化。

bool

AEmitter 类的大部分函数都只是对 UParticleSystemComponent 类的一些函数进行简单的封装而已, 甚至与之使用了相同的函数名称, 而 AEmitter 类里的这些 bool 类型的变量,大部分都是控制需不需执行 PSC 里的同名函数的。

1
2
3
4
5
6
7
void AEmitter::AutoPopulateInstanceProperties()
{
if (ParticleSystemComponent)
{
ParticleSystemComponent->AutoPopulateInstanceProperties();
}
}

其中 AEmitter::AutoPopulateInstanceProperties() 里调用了 PSCAutoPopulateInstanceProperties() 函数。

bCurrentlyActive

表示 当前特效是否处于激活状态

是一个同步变量,用于同步更新客户端上的特效状态

1
2
3
4
5
6
7
8
UPROPERTY(replicatedUsing=OnRep_bCurrentlyActive)
uint32 bCurrentlyActive:1;


void AEmitter::OnRep_bCurrentlyActive()
{
ParticleSystemComponent->SetActive(bCurrentlyActive);
}

bDestroyOnSystemFinish

表示 特效在结束播放后是否销毁

1
2
3
4
5
6
7
8
void AEmitter::OnParticleSystemFinished(UParticleSystemComponent* FinishedComponent)
{
if (bDestroyOnSystemFinish)
{
SetLifeSpan(0.0001f);
}
bCurrentlyActive = false;
}

使用 Actor::SetLifeSpan() 函数设置剩余的寿命。 > [Tips] SetLifeSpan() 使用计时器设置一定的时间之后执行 Destroy() 函数。

bPostUpdateTickGroup

表示 PSC是否需要设置TickGroup

1
2
3
4
5
6
7
8
9
10
11
void AEmitter::SetTemplate(UParticleSystem* NewTemplate)
{
if (ParticleSystemComponent)
{
ParticleSystemComponent->SetTemplate(NewTemplate);
if (bPostUpdateTickGroup)
{
ParticleSystemComponent->SetTickGroup(TG_PostUpdateWork);
}
}
}

广播事件

1
2
3
4
5
6
7
8
9
10
11
12
13
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams( FParticleSpawnSignature, FName, EventName, float, EmitterTime, FVector, Location, FVector, Velocity);

UPROPERTY(BlueprintAssignable)
FParticleSpawnSignature OnParticleSpawn; // 粒子生成时的事件

UPROPERTY(BlueprintAssignable)
FParticleBurstSignature OnParticleBurst; // 粒子爆裂时的事件

UPROPERTY(BlueprintAssignable)
FParticleDeathSignature OnParticleDeath; // 粒子消亡时的事件

UPROPERTY(BlueprintAssignable)
FParticleCollisionSignature OnParticleCollide; // 粒子碰撞时的事件

[Tips] BlueprintAssignable 表示广播事件可在蓝图中分配。

这些 multicast 类型的事件由 Engine\Source\Runtime\Engine\Classes\Particles 目录下的 ParticleEventManager.h 文件中的 AParticleEventManager 作统一管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
void AParticleEventManager::HandleParticleSpawnEvents( UParticleSystemComponent* Component, const TArray<FParticleEventSpawnData>& SpawnEvents )
{
AEmitter* Emitter = Cast<AEmitter>(Component->GetOwner());
for (int32 EventIndex = 0; EventIndex < SpawnEvents.Num(); ++EventIndex)
{
const FParticleEventSpawnData& SpawnEvent = SpawnEvents[EventIndex];
Component->OnParticleSpawn.Broadcast(SpawnEvent.EventName, SpawnEvent.EmitterTime, SpawnEvent.Location, SpawnEvent.Velocity);
if (Emitter)
{
Emitter->OnParticleSpawn.Broadcast(SpawnEvent.EventName, SpawnEvent.EmitterTime, SpawnEvent.Location, SpawnEvent.Velocity);
}
}
}

其他

AutoPopulateInstanceProperties() -- PSC的包装函数,自动填充实例属性

GetDetailedInfoInternal() -- PSC的包装函数,获得内部引用的 PS 的路径

GetReferencedContentObjects() -- 添加引用的 PS 到数组里

ParticleSystemComponent.h

UParticleSystemComponent 类是整个特效系统的功能组件,它控制着整个特效系统的显示流程及以存储着粒子释放时的各种数据。 这里主要分析部分本人认为十分重要(勉强看的懂)的地方。

其中最主要的两个成员变量是 TemplateEmitterInstancesTemplate 是类型为 UParticleSystem 的对象。 EmitterInstances 是类型为 FParticleEmitterInstance 的结构体变量。 这两个变量会在后面详细分析。

EventData

PSC 所在的头文件里有一大堆 EventData 类的结构体。 它们保存了各个整个类型的结构,以供在 AParticleEventManger 中处理粒子事件时把所需参数传给 OnXXX 的Broadcast, 从而将事件广播出去。

这在前面分析 AEmitter 时也提到了。

Pool

在新建一个 PSC 时,可以从内存池(Pool) 里分配内存, 也可以不。 从内存池里分配可以选择是否自动回收内存。

此功能是由 PSC 中类型为 EPSCPoolMethod 的变量 PoolingMethod 来支持的。

EPSCPoolMethod 枚举类声明于 Engine\Source\Runtime\Engine\Classes\Particles 下的 WorldPSCPool.h 文件,即与 PSC 在同一个目录下。

在这个文件夹下,总共有4种数据类型

EPSCPoolMethod

FPSCPoolElem

FPSCPool

FWorldPSCPool

  • EPSCPoolMethod 是一个枚举类,主要用于标识某个 PSC 是否使用对象池分配空间。

  • FPSCPoolElem 是一个结构体, 它表示一个内存池里的内存对象。 它包含一个 UParticleSystemComponent* 类型的变量即PSC; 以及一个 float 表示此 PSC 最后的使用时间。

  • FPSCPool 为一个单独内存池,它主要包含一个 UParticleSystemComponent* 类型的数组。 不过源码注释推荐将此数组改成__循环队列(TCircularQueue)__来提高效率。

  • FWorldPSCPool 则是可供外部使用的强大的内存池群。 它有一个TMap对象, 使得每个 UParticleSystem 类型的资源都对应着一个内存池。

1
TMap<UParticleSystem*, FPSCPool> WorldParticleSystemPools;

ActivateSystem()

当我们需要激活粒子生成器使其发射粒子时,就需要调用 ActivateSystem() 函数。

此函数最主要的功能大致可分为 初始化将更改传播到渲染器 两个部分。

初始化 则包括 FParticleEmitterInstance 的初始化 和 LOD 的初始化及其他。

下面是抽出了笔者能理解的部分代码,以理清此函数的大体流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void UParticleSystemComponent::ActivateSystem(bool bFlagAsJustAttached)
{
if(IsTemplate() == true || !IsRegistered() || !FApp::CanEverRender()) return;

// 输出日志
if(UE_LOG_ACTIVE(LogParticles, VeryVerbose)) OutLog();

//初始化工作
if(GIsAllowingParticles && bDetailModeAllowsRendering && Template)
{
// Auto Attach
if(bAutoManageAttachment) SetAutoAttachment();

// Init Active
bWasCompleted = false; bWasDeactivated = false;
bIsActive = true; bWasActive = false;
SetComponentTickEnabled(true);

// Init LOD
SetLodLevel(DesiredLODLevel);

// Init EmitterInstance -- 如果没有Instance的话
if(EmitterInstances.Num()==0||(bIsGameWorld&&(!bAutoActivate||bHasBeenActivated)))
{
InitializeSystem(); // 从 UParticleEmitter 到 FParticleEmitterInstance
}
else if(EmitterInstances.Num() > 0 && !bIsGameWorld)
{
for(int32 i = 0; i < EmitterInstances.Num(); ++i)
{
if(EmitterInstances[i])
{
EmitterInstances[i]->Rewind();
EmitterInstances[i]->SetHaltSpawning(false);
EmitterInstances[i]->SetHaltSpawningExternal(false);
}
}
}

// Init Warmup -- 初始化预热阶段
if(WarmupTime != 0.0f) InitWarmup();
}

// 传播修改到渲染器
MarkRenderStateDirty();

}

其中 InitializeSystem() 函数的实现流程大致如下。

其中调用了 InitParticles() 函数来真正实现从 UParticleEmitter 生成 FParticleEmitterInstance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

void UParticleSystemComponent::InitializeSystem()
{
if(!IsRegistered() || FXSystem == NULL) return;

if(GIsAllowingParticles && bDetailModeAllowsRendering)
{
if(IsTemplate() == true) return;

// Set Delay
if(Template != NULL)
{
EmitterDelay = Template->Delay;
}

// Init Instance -- 分配 Emitter Instances 和 粒子数据
InitParticles();

if(IsRegistered()) SetActive(true);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

void UParticleSystemComponent::InitParticles()
{
if(IsTemplate() == true) return;

if(Template != NULL)
{
int32 NumInstances = EmitterInstances.Num();
int32 NumEmitters = Template->Emitters.Num();

for(int32 Idx = 0; Idx < NumEmitters; ++Idx)
{
UParticleEmitter* Emitter = Template->Emitters[Idx];
if(Emitter)
{
FParticleEmitterInstance* Instance = EmitterInstances[Idx];

}
}
}
}

DynamicData

ParticleSystem.h

ParticleEmitterInstances.h

ParticleEmitter.h

ParticleModule.h

other

Niagara 原理