LongCut logo

Optimizing Niagara: a Practical Approach to Performant Worlds | Unreal Fest Orlando 2025

By Unreal Engine

Summary

Topics Covered

  • Parallel Niagara Hits Low-Core Hardware Hardest
  • Effect Types Halve Active Systems Without Visual Loss
  • Emitter Scalability Ramps Off Distant Non-Essentials
  • Lightweight Emitters Slash Costs for Simple Effects
  • Data Channels Consolidate High-Volume Spawns

Full Transcript

Hi everybody. Uh welcome to the final talk of the day, optimizing Niagara. A

particle approach, a particle approach, practical approach to optimizing uh Niagara. Uh I am Adam Kurali. I'm a

Niagara. Uh I am Adam Kurali. I'm a

senior technical artist. I've worked in games for about four years. Uh I've

worked on the uh cancelled Time Spitters game and shipped Funko Fusion in the recent years. I've also worked on a

recent years. I've also worked on a bunch of other stuff I'm not allowed to talk about. Um my special I specialize

talk about. Um my special I specialize in uh performance profiling and cont content optimization. I also do a little

content optimization. I also do a little bit of editor tooling here and there. I

work for Tanglewood Games. Uh we're

Unreal Engine experts. Uh we provide engineering support, platform porting, uh performance and optimization and other sort of technical consultation for um companies using Unreal Engine. And

there are some of the games that we worked on in recent years.

Uh what we're going to look at today uh is uh Niagara but from kind of an eagle eagle eye view from a top down. So we're

not going into the systems themselves optimizing the simulation. Uh we're

trying to reduce the um back button works. Uh trying to kind of reduce the the number of system ticks in the world and uh kind of fix the

performance impact of content that already exists in the in the level. So

with we often come in at the end of the g uh end of the production cycle and uh have to kind of work with content that's already been signed off and can't really be changed. Uh so first off we're going

be changed. Uh so first off we're going to look at the anatomy of a Niagara system. Now uh even though we're not

system. Now uh even though we're not really going to dig too deep into what's going on inside of a system itself in the emitters uh we want to kind of know what we're looking at uh and how it

works. Uh we're going to look at how we

works. Uh we're going to look at how we can identify per uh performance issues.

Uh we're going to look at some snap commands. Uh that's a you know very easy

commands. Uh that's a you know very easy and quick way of just debugging a few things. And then we're going to look at

things. And then we're going to look at the Niagara debugger, a fantastic tool inside of the editor uh to debug and uh uh profile Niagara systems. And then of course we're going to look at Unreal

Insights because no performance talk is complete without looking at Unreal Insights. Uh and then once we kind of

Insights. Uh and then once we kind of established our our ways of identifying the problems, we're going to then move on to mitigating those performance impacts. We're going to look at setting

impacts. We're going to look at setting up effect types and what they are. Uh

we're going to look at emitter scalability which is a little bit more kind of in there but not quite um changing the content itself. And then

we're going to look at kind of scalability as a as as a bigger picture.

And then we're also going to look at pooling uh the effects budget uh lightweight emitters and data channels. I know I said we're not going

channels. I know I said we're not going to make uh content changes. Those two at the bottom are just that but I'm I'm going to keep them. uh they're very very

important and fantastic tools. So uh the main assets for Niagara are going to be your system and your emitter. So the

that's how it looks like on the right um when you open up the Niagara editor and those are the little asset icons there when you're browsing in the content browser. The system is going to be our

browser. The system is going to be our main component. That's what contains the

main component. That's what contains the emitters. It also houses all your user

emitters. It also houses all your user parameters or variables that are globally accessible within the within the system itself. It handles systemwide scalability. And this is the bit that

scalability. And this is the bit that actually interfaces with the game and lives in the world.

Uh the emitter uh is this orange little thing. It is a stack of modules and this

thing. It is a stack of modules and this is what's kind of responsible for all the particle spawning, the particle simulation and the rendering of those particles. So if you look through that

particles. So if you look through that kind of stack, you'll see uh how we're kind of going down uh that module stack and how the different parts kind of come together. Uh the nice thing about these

together. Uh the nice thing about these is that they can be inherited from. So

once you created the asset, you can use it as a template which means that any sort of changes we need to do um optimization wise, so applying uh different scalability settings or or

just kind of um or changing render renderers that can propagate uh into other systems. Uh these things can only exist as part of a Niagara system asset.

So if you if you see uh VFX systems or VFX components, those are all going to be the system and not the emitter directly. as we move on to stat

directly. as we move on to stat commands. Well, my per performance is

commands. Well, my per performance is kind of weird. What's going on? Well,

everybody's going to reach for stat FPS and stat unit. If those are not enabled all the time, you're doing it wrong.

Please enable them. Um, the only problem is that, you know, we get the game thread, the the the draw, the RHI, and the GPU times. But Niagara is very complex. Uh, it is highly parallel. It

complex. Uh, it is highly parallel. It

goes really wide. Uh, it can be either CPU or GPU simulated. And uh because of that thankfully there are Niagara specific stat commands

um such as Niagara stat overview or stat Niagara overview. I'm going to say

Niagara overview. I'm going to say Niagara and stat a lot of times. I'm

sorry. Uh it looks something like this in in game. So that the top bit up there is the that display. It gives you kind of an aggregate view of where Niagara is costing us on the different threads. So

you'll see the render thread and the game thread. And right above it GT

game thread. And right above it GT concurrent. That's going to be all of

concurrent. That's going to be all of your worker threads. Now, that number is always going to be relatively big because you're doing a lot of work in parallel. As I said, it's it's uh highly

parallel. As I said, it's it's uh highly parallel. Um, but why that's important

parallel. Um, but why that's important is the fewer cores you have, the bigger that impact is going to be. So, if you have a 16 core beast, that number might not make a difference to your final

frame rate. But if you're down on a

frame rate. But if you're down on a Nintendo Switch or you just have a four core machine, the same four milliseconds is going to hit you very differently.

So to dig further deep into that we can look at step Niagara uh which is going to give us a more kind of detailed view of what's actually happening uh what kind of calls are actually adding up

into that game thread and CNC cost. So

these are some of the examples of of what can uh add to those costs. So if if you have a lot of simulation or really complex simulation lots of particles they're going to show up in different

places. And one of the big things about

places. And one of the big things about the render thread as well is it's not just for rendering. All GPU simulation needs a GPU dispatch for the compute to

actually happen. Uh so there is actually

actually happen. Uh so there is actually potentially a big impact on RT even when you're not seeing those particle systems. Obviously also render rendering calling and all sorts is also happening over

there. Um this is roughly what it looks

there. Um this is roughly what it looks like in uh Lyro and you're looking in the editor. Uh there's not much

the editor. Uh there's not much happening but there's also a lot of numbers still jumping about. So you can see that even just with a few Niagara systems at play, you have a lot of things going on in the background.

Moving on to the next one. Uh stat

systems. Uh this is an overview of on a in a per system basis. So it's still going to give you very similar information but with a slightly different uh formatting. Uh this also includes the system path which is going

to be super handy to actually find which system is the problem here. And again

this shows you uh the different costs for the different threads.

And then the pair with this would be stat Niagara emitters. Same as above but for emitters. But in this case uh it

for emitters. But in this case uh it shows you the emitter and in which system it is part of. So as we established earlier you can have uh each system can have multiple emitters. I

don't know if I actually established that more systems can have multiple emitters and you can use the same emitter in multiple systems. So the idea here is that even though you know that okay fire one is causing a problem it

might be a problem in one system but not in the other. Uh and again it shows you uh different positions of the cost. So

again same idea here as uh as before you can see the individual uh emitters and which thread they are costing us on uh and which system like some of the

systems. So for example, Gunpad has a bunch of emitters in it to build up the whole system and then all of those are going to have different uh impacts for you. Niagara debugger, I absolutely love

you. Niagara debugger, I absolutely love this thing. The only problem it has is

this thing. The only problem it has is it's only accessible in editor. So once

you package, you can't really have access to it. Um and you can find it in tool debug Niagara debugger. It is a comprehensive debug tool uh that has

well the debug hood and the effects outliner that we're interested in but it has a bunch of other functionality as well uh specifically for people working with VFX and created a VFX this can be very useful but otherwise for our

purposes the debug cut is going to be kind of our main area where we're looking so stat uh it gives us a statike display again same thing different flavor um you can look at the different

performance numbers scalability what particle system is in what state within the scalability and also one of my favorite things GPU compute. It's really

difficult to see the actual GPU cost of certain things. This this actually gives

certain things. This this actually gives you a really good uh idea of what's going on on the GPU and what is costing us much. Um and it also has in world

us much. Um and it also has in world display for debugging and attributes display. Now, uh, again, for for a the

display. Now, uh, again, for for a the performance standpoint, we don't really care about the attributes for the particles, but it can be super useful to see if you have, you know, a ton of particle systems in certain areas that you can't even see because potentially

you disabled the visibility or the renderer, but it's still ticking in the background, so the system is still active. And then the effects outliner is

active. And then the effects outliner is going to uh grab a snapshot of a frame and uh display you a bunch of information. So again, same information

information. So again, same information in a trench coat. Um, but it also tells you which actor owns that particle system, which again can be super useful

for for figuring out where the problems are coming from. It also tells you the state of uh the particle system state for pooling, uh whether it's active, it's called. Uh so there's just a lot of

it's called. Uh so there's just a lot of stuff going on. And also again performance numbers, we love them. Same

thing, different flavor. Uh so this is a quick rundown. you go tools uh debug

quick rundown. you go tools uh debug debugger make sure to enable show overview otherwise you don't actually get the numbers in the top left uh and then we can do a little fly around and

see okay these are the systems that are currently active it tells you how many emitters are part of the system how many of those are active how many particles it has um you can also filter this so obviously if you have a ton of different

particle systems it can become just noise but you can s uh filter this um by emitter name or system name or any anything else.

And then here I'm just kind of flicking through the different view modes. Again,

this is this is going to be one of those where as you're working with Niagara performance more and more, you'll kind of sus out uh what views are going to be

useful for you. Um but yeah, and then moving on to the effects outliner here.

Again I did a capture and then you can see okay this system we have 15 of these systems they are in these blueprints and they are doing well these are are actually actually not initialized

because they play at a specific point but you get the idea so you can really dig deep into what's going on in the scene just uh by using the effects outliner

and then Unreal Insights uh this is the gold standard when it comes to performance I think I spend at least 30% if not 50% of the time of my I'm looking at insights captures. Uh I think I think

our set tangle would be we we spend a lot of time in insights captures. Um

what's what's great about it is is uh actually roll back. What you need to do before you're doing any captures for Niagara specifically is you want to make sure that you enabled uh stat named events. Otherwise Niagara will not show

events. Otherwise Niagara will not show up in your capture. So that's mighty mighty important. Um, otherwise, you

mighty important. Um, otherwise, you know, it's the same thing. Trace file,

whatever channels you want. Just make

sure stat named events is in there somewhere. Uh, why I really like it is

somewhere. Uh, why I really like it is because it can be recorded uh outside of the editor. So, again, you can have a

the editor. So, again, you can have a package build or something. Uh, and the file that it outputs is portable. You

can send it to your colleagues. You can,

you know, with when we work with clients, a lot of times we get sent traces to look through. Uh, and it's and it's fantastic to look through it. Uh,

it gives you frame and time context. It

also has a log built into it. you can

bookmark stuff. Uh it's it's absolutely fantastic. And why this is important is

fantastic. And why this is important is sometimes Niagara might not be at fault when the performance actually goes bad, but it might look like it's Niagara's fault. Maybe someone is spawning too

fault. Maybe someone is spawning too many characters and those characters are all on fire all at the same time, things like that. Or they someone is spawning

like that. Or they someone is spawning too many Niagara systems. Uh this is a really good way of finding it. Uh it

does give you system level uh costs and also global Niagara costs. So something

like the uh Niagara manager is going to be its own thing. And then you can also just search for specific Niagara system.

So if you know that you have, I don't know, you have problems when you're firing a gun, maybe it's the muzzle flash, maybe it's the shell eject, maybe it is the hit effects that are happening, and then you can just type in NS and it will give you all the things

that are happening in your uh in your capture. Uh this isn't an Unreal

capture. Uh this isn't an Unreal Insights talk specifically, but my colleague uh Jake Simpson did a really good talk last year uh optimizing the game thread. He does touch on Niagara in

game thread. He does touch on Niagara in there. Uh go watch his talk. Fantastic

there. Uh go watch his talk. Fantastic

game threads. Go look at his thing. Uh

if I mention any links there at the end there's going to be a link tree uh that you can look up. There's going to be a bunch of references to documentation as well. Um yeah, this is a quick uh

well. Um yeah, this is a quick uh capture. So when you're when you're in

capture. So when you're when you're in Can I restart that video? Sorry. Uh, you

can capture in the editor as well.

There's a little tick box to enable stat named events for you. Make sure that's ticked on. Do a little happy dance

ticked on. Do a little happy dance because you're doing trace captures.

And then open it up. Now, insights can be incredibly intimidating if you haven't used it before. Uh, because it's got a lot of numbers, but it's got pretty colors. And I like pretty colors

pretty colors. And I like pretty colors because I'm technically an artist. Um,

so yeah. Again, here I'm just kind of searching for Niagara or NSC_.

And you'll see what I mean. When Niagara

is really wide, it's just in random places in the frame. So, how it decides where it's going to sit in the frame depends on what's actually happening in the simulation. So, in some of them, you

the simulation. So, in some of them, you might need frame data. On some of them, you're sampling skeletons. Uh on others, again, you're sending GPU information out and then your GPU does the simulation. Uh and then you're maybe

simulation. Uh and then you're maybe reading back from the the GPU sim. So

it's very difficult to kind of pinpoint what's actually going on in the in the content a lot of times. But you see it's random worker threads have 3 milliseconds of Niagara simulation on them and then it goes back up uh gets

collected gets sent off to rendering and everything else. So there's a lot there.

everything else. So there's a lot there.

Um but now we looked at how to see where the problem is. Now let's fix it.

Effect types are my absolute favorite thing. They've been in Unreal 5 for as

thing. They've been in Unreal 5 for as long as I can remember.

um and they are effectively the backbone of most of the things that we're going to talk about today. So these these are similar to texture groups in some sense.

You can create these assets and then define behaviors. They can act as

define behaviors. They can act as significance managers. They control your

significance managers. They control your culling and everything else. But let's

run through from the top. Um,

scalability as the word is thrown around a lot in Niagara, and I do apologize because they mean ever so slightly different things, but it's all about making things go fast. Um, we can decide

if any system that's implementing this effect type should uh be allowed to call for local players. So, if the system is owned by the local player, it you can enable it or disable it if if we're allowing a calling for it. And then just

below that, you see update frequency. we

can decide if we're only checking at spawn time or every second, every tick.

Um, you'll need to make those decisions depending on the context of the particle system. In some scenarios, spawn only is

system. In some scenarios, spawn only is enough. Uh, in other scenarios, you

enough. Uh, in other scenarios, you might need to check every tick if you're far enough away or if if the uh the uh thing is uh too w or something. And then

the call reaction just be below that decides how we're going to handle removing this particle system or how we're going to call this particle system. So there's clear, sorry, there's

system. So there's clear, sorry, there's kill and kill. Kill and clear, asleep, uh asleep and clear, uh and pause, I believe, are the are the main ones. Uh

so kill is just going to nuke it completely. So you can't can't restart

completely. So you can't can't restart it, can't do anything with it. It's just

gone. Um a sleep is going to put it to sleep. So deactivate the particle system

sleep. So deactivate the particle system and then you can uh bring it back to life. Um if the

life. Um if the um the if it's within range, then it shouldn't be called anymore. And then

just below that is the significance handler. Now, by default, there's no

handler. Now, by default, there's no significance handler set. Uh you can leave it like that. That's perfectly

fine. Sometimes you don't need any significance handling for this. You're

only using this for uh for specific distance culling. Uh and then you don't

distance culling. Uh and then you don't have to worry about it too much. Um but

uh there's distance based and age based by default. You can also extend this

by default. You can also extend this with your own uh classes in C++. I

haven't really found a need for it just yet, but there's definitely eventually we're going to run into something where we need to be very specific with it. Um,

and then moving on are our settings. So,

in each effect type, you have an array of settings. Uh, the first one, the big

of settings. Uh, the first one, the big blue line across is going to be our scalability line. We're going to come

scalability line. We're going to come back to that once I uh when we when we actually talk about the scalability within Niagara.

Uh, and then just below that you have max distance, which is, you know, generic distance calling. So the

difference here between this distance skull and what you would normally do for static meshes is that this has nothing to do with the GPU. This is purely us disabling a particle system simulating.

So removing the tick of that particle system. Uh all of this is still being

system. Uh all of this is still being handled somewhere else though. So uh

uh the the Niagara scalability manager tick is going to be uh paying most of that cost. And then below that you have

that cost. And then below that you have the two max instance counts. So it's

effect type instances or system instances. That's effectively a hard cap

instances. That's effectively a hard cap of how many of the the particle systems we can have. So if it's the max effect type, then any particle system that is implementing this effect type, you can

only have let's say 15 of at a time. And

that's a hard cap. And then the significance manager is the bit that decides if a system should be alive or not. So if it's newer or if it's if it's

not. So if it's newer or if it's if it's closer to us. And then uh yeah, the max system is kind of self-explanatory. I

think it's if the system has multiple copies of itself. uh do we want to allow it to spawn or not? And then call proxy mode. You can you can use this to

mode. You can you can use this to instead of just completely removing the system, just use an already simulated version of it. So if there's another torch somewhere doing its little fire thing, uh then you can just copy that

because it's already simulated and we're just rendering that all over and over again. Uh you're still paying the render

again. Uh you're still paying the render thread cost on that. So don't it it's not nothing is ever perfectly free. Uh

but you're no longer ticking the system anymore. And then below that are generic

anymore. And then below that are generic callulling settings. Uh if we're going

callulling settings. Uh if we're going to call before uh if we're going to do a pre-all check before spawning, if if it's not in view, let's not even start ticking it. Uh and then how long before?

ticking it. Uh and then how long before?

So if if the system is outside of our view or behind something, it hasn't been rendered for some amount of time, do we want to call it or not? And budget

scaling I'm going to skip over because that has a whole its own section. Uh and

then just below that, emitter scalability. Uh this one is literally

scalability. Uh this one is literally just a multiplier for emitter spawn rates. Uh so you can you can just say

rates. Uh so you can you can just say okay on low quality settings uh I'm going to only spawn 50% of my particles and something like that. So

again this is in the effect type. So

this effect type is telling the particle system and the emitters within it that you should be spawning fewer particles.

Where should you use this? You should

use this everywhere. Uh that's that's how I see it. Uh the problem with that thought is if you have uh an extremely high system count in the level, this can

also get slow because you're still checking all of these particle systems if they are matching uh the settings that you're using. So uh one of the projects we've worked on, they had I

think round about 7,000 particle systems just for leaves falling out of trees.

It took whenever you added something to that same effect type, you had I if I remember correctly around a 30 millisecond spike because the significance manager had to just quickly

go through everything and and refresh the array. Um so yeah, it can go wrong.

the array. Um so yeah, it can go wrong.

Uh why use it? Well, uh I kind of explained what it does. So use it because it's good. It can it can help you uh optimize your scene very very easily. It's a fantastic first step into

easily. It's a fantastic first step into getting a lot of performance back from uh content that's already been generated, levels that's already been built uh or even just prevent new systems from spawning when you're doing

a lot of things on screen. Uh the setup is relatively straightforward. You

create and configure the asset uh asset type and then you just assign it to the system. Dead simple. You go rightclick

system. Dead simple. You go rightclick effects advanced effect type. Give it a name.

Uh, come on, Adam, you can type. Come on

past, Adam. Uh, and then you configure it. Um, I'll be honest, in this video I'm just randomly clicking buttons and and putting in numbers. Um,

so let's move on to what happens when we set this up. So, uh, this was done in Lyra. Uh, I did a 10 bot match, so it's

Lyra. Uh, I did a 10 bot match, so it's 5v5. I also have my CPU limited to four

5v5. I also have my CPU limited to four threads in this case to demonstrate that you know on lower-end hardware Niagara can get very slow. Um but I also

completely hamstrung all the optimization that the guys did on Lyra.

Uh so these numbers are not representative of the actual demo as the actual uh sample project itself because I basically removed everything that Epic did. It's worth pointing out that if you

did. It's worth pointing out that if you go and look at what Lyra did for a lot of the optimizations, it's a fantastic resource for that. They did some really really smart stuff. They use everything that I'm talking about in here. Uh but

yeah, uh going through the actual numbers, so you can see that going from not having effect types to having effect types, they we more than halfed our number of active particle systems.

That's fantastic. The game also looks

That's fantastic. The game also looks exactly the same. Um, and then just kind of quickly skipping through skipping through some of these numbers, uh, the our average frame rate came down

massively and more importantly, I think our spikes came down significantly as well. So, we're less than half on our

well. So, we're less than half on our render thread in our worst hitch, which is great. Uh, and then you can see some

is great. Uh, and then you can see some of the numbers there. So, uh, roughly about.7 milliseconds off the game

about.7 milliseconds off the game thread. Not much happened there. Uh, 3.5

thread. Not much happened there. Uh, 3.5

millconds of the render thread.

Fantastic. And then from the uh concurrent, so all the the threaded work that we're doing, we're saving 2.15 milliseconds. So again, the fewer cores

milliseconds. So again, the fewer cores you have, the worse that's going to be or the better the saving uh in in our case here. Um let's look at a couple of

case here. Um let's look at a couple of examples of what I've set up here. So

I've set up this NE one shot. This is

fantastic for fire and forget type effects. So muzzle flashes, foot,

effects. So muzzle flashes, foot, actually, did I put muzzle flashes in here? No, I didn't. Uh footsteps, uh

here? No, I didn't. Uh footsteps, uh impact effects, and the shell ejection.

So again, I hamstrung a lot of these.

They are not actually this expensive.

Um, so what I've done here is I limited the max number of active uh systems. So I didn't do it on the effect type. I've

done it on the systems. It was good enough. Uh, but you could add the effect

enough. Uh, but you could add the effect type level cap as well if you want. The

usually it's a good idea to have the effect type limit slightly more. So I

usually do double, but you might your mileage may vary. It depends on how many systems you're using uh in your game.

Uh, and I used distance as my significance handler for this because I figured for most of these effects near the player is more important. Um, yes,

gun impacts are in here, but the impact when you hit a player, that little spark that isn't because that is a gameplay critical effect.

Um, I've also set this up to instantly visibility call because these all of these are very uh short and small effects. So, you know, they don't last

effects. So, you know, they don't last more than a second. They're all fairly self-contained and you know if it happens outside of your view but you're panning really quickly you're not going to miss that smoke puff from the foot of

someone uh or say likewise if you run behind the wall really quickly you're not going to miss these hit effects and another one to look at uh ne pickup persistence. So, this is slightly

persistence. So, this is slightly different. Uh, this is only used on NS

different. Uh, this is only used on NS Gunpad. It's a bit of a interesting

Gunpad. It's a bit of a interesting setup for that particle system. It has a lot of emitters. Uh, and because I said I'm not going to make any changes to it, uh, to the content because I'm a good

boy. Uh, I just ran with it. So, it's

boy. Uh, I just ran with it. So, it's

now a gameplay critical particle system because it has you have to be able to see it. Uh, so this particle system or

see it. Uh, so this particle system or this effect type was set up uh, specifically for looping gameplay critical systems. I've set it up for uh five system instances. So these are

actually relatively costly particle systems, but we can r the the the sight lines on the Lyra demo map give me maybe five visible at a time. So if at any angle when I was playing through it, I

couldn't really see more than five of these. But even if I do see more than

these. But even if I do see more than five of these, we're using distance uh as the significance. So the closest five to us will always be ver visible because those are the most relevant. I can't get

the uh health pack from 200 meters away.

And I don't really care if anybody else does cuz uh then they get a little effect on them on themselves. And then

yeah, we we delayed the visibility calling on these. So the reason for that is if you're uh strafing back and forth from behind cover or you're yanking the camera around, you don't want to see these particle systems pop in and out of

existence because this has a little bit of a wind up time. The particles need to kind of start flying up. And if you constantly you look away, it gets called. You look back and then it starts

called. You look back and then it starts again. And then you look away, look

again. And then you look away, look back, starts again. So I put a a 1second delay on this. You could probably go higher in the uh on it, but it worked well enough in my testing.

Awesome.

Emitter scalability. Scalability, the

the word that we're throwing about a bunch. So, this one is actually in the

bunch. So, this one is actually in the emitter settings themselves. You do have to open up Niagara editor for this.

Um this is very very useful for more complex system where you have a large number of emitters and you want to reduce complexity through removing certain emitters from the simulation or

just rem reducing the complexity for the uh emitter itself. So the simulation itself. So as I said this lives in the

itself. So as I said this lives in the uh emitter setting. So when you open up uh the Nagra editor, you click on one of the emitters and then under the emitter state, you just flick the scalability

mode over from system to self and then you get exposed to all these lovely settings. A bunch of these are the exact

settings. A bunch of these are the exact same thing that we talked about for effect types, but those are for the entire system. These are for the emitter

entire system. These are for the emitter specifically.

Um why you should use this? Well, it's

it gives you per emitter fine control.

Uh and it lets you remove parts of a complex system. Uh yeah, I just ran

complex system. Uh yeah, I just ran through the setup. Sorry. Uh so it's in in each individual uh emitter. Uh you

can set up distance culling, you can set up spawn spawn count scale and visibility culling. Similarly what we've

visibility culling. Similarly what we've done with the systems. What's really neat about this is like imagine you have a uh campfire type particle system. So

you have the main body of the fire, the big flames with the sprites, the sprite sheet going through it. Uh you have little embers flying off and you have a smoke stack for example. So we need the smoke stack to stay stay about because

that's going to be important. We need

the big uh fire sprite still available, but the embers we don't care about after a certain amount of distance. So, we can ramp off the spawn rate and then just turn it off at the distance. So, then we

removed a bunch of particles that we don't need.

Uh cool. And now we go back to Niagara scalability. Uh the as I said,

scalability. Uh the as I said, scalability is one of those words that I'm going to throw going to be throwing around a lot. uh we deal with scalability on on the daily at Tango Wood for different platforms for scaling

games down or scaling games up. Uh and

uh as I mentioned that those blue bars are are kind of our main uh thing here.

Uh those uh values so low, medium, high, epic and cinematic they are listening to FXN Niagara quality level Niagara dotquality level uh 01234

I believe. uh and that lives under SGFX

I believe. uh and that lives under SGFX quality. So those numbers by default are

quality. So those numbers by default are the same. So SGFX quality zero is going

the same. So SGFX quality zero is going to give you FX quality level zero as well. Um

as well. Um on the on the right there you can see the that's a that's an effect type settings and in there you can set up individual

full setting uh different settings per quality level. So you can do all the

quality level. So you can do all the things we talked about earlier about effect types. All of those settings you

effect types. All of those settings you can set a custom value for or custom override for individual uh quality levels. And what's also neat about these

levels. And what's also neat about these is you can also do platform level overrides. So if you're working with

overrides. So if you're working with multiple platforms and you want to say like, okay, I don't ever want to uh see Epic quality level on a Nintendo Switch or I don't ever want to see it on Linux

because we are shipping on Steam Deck.

Uh you can do that through here as well.

Um yeah, as I said, modify spawn rates.

So it's it's all the same settings we've discussed earlier. And then inside

discussed earlier. And then inside Niagara you can also inside the Niagara editor you can also disable specific emitters completely on different

scalability levels or the entire system.

So even uh let's say you you want to remove a particle system from again a lowend low-end platform or just from low settings completely but you don't want to go around and find all the blueprints

that's implementing all these systems and go and remove them on that or uh set different visibility settings. You can

do it uh straight in the editor. Uh so

in the Niagara editor uh you have that scalability button. You click that and

scalability button. You click that and it just transforms a little bit. Uh on

the right it turns into the scalability editor and it gives you all the different values that are coming down from the effect type as well. And then

you can just kind of click clack on some of these systems. And you can also preview which one is going to be alive and which one is not going to be alive.

Super useful. I love it. I use it a bunch. And then there you'll see when I

bunch. And then there you'll see when I click the little arrow in a second that I can do different platform overrides.

So in this case the high settings will never apply to Linux. I can set up a completely custom high settings specifically for Linux. Or I could do the same thing for PS5 or Xbox or whatever else.

And then you see as as I add things to it, it does tell me if there are conflicts within the stack as well, which is quite handy.

Yeah. Cool. Pooling. Uh pooling is is one of those things that a lot of programmers are going to be very familiar with. You like to pull objects

familiar with. You like to pull objects if you're spawning a lot of a lot of something and deleting it a bunch of times. You don't want to cause object

times. You don't want to cause object churn and then potentially down the line cause hitching because garbage collection kicks in and needs to delete a bunch of objects from memory. Um by

default I think you should use it everywhere again uh but because it's all set up for you already. But the main contenders for actually enabling pooling

on these is going to be on systems that you spawn and remove often. So this

isn't quite the same as something like a muzzle flash that's you know you're spamming a lot of. There's different

solutions for that that I'm going to propose in a second. Uh but think about something like a heal effect. Your

character runs into a healing or you use a healing potion and you have this like green thing go past you and then sparks fly whatever you're healed. that effect

then gets removed. But we we're probably going to use it again in, you know, 30 seconds, a minute, whatever. Uh that's

when you should have pooling set up for these because, you know, you're going to churn through a bunch of them. Uh but

there might not be, you know, 10,000 in a second. Um

a second. Um as I said, there's for for high volume spawned things, there are better solutions to this. You can potentially set up something like a system as a service where uh again, liar is a

fantastic thing for this. uh their shell ejection and their muzzle flashes all work on this uh setup where they have a boolean that you tick on and then the system is playing and then you untick it and the system is no longer playing. Um

it's really really cool. I highly

recommend checking out that setup. Um

but yeah, why use it? So, as I kind of mentioned, it can it uh Oh, I didn't actually say that. Well, it will also reduce your spawn cost. So, if you if you already using uh a system that's already been spawned and you're just

activating it again, you don't have to pay the cost of spawning it, creating the object and everything else, you can also pre-spawn these if you prime the pool. But that can also cause hitching

pool. But that can also cause hitching if you're loading it at the wrong time.

So again, we can't just win very easily.

And the other very important part of pooling is if you're pooling too many of a a thing, it will cost you memory. Uh

and again, depending on what you're doing, that might be a problem. So do

bear that in mind. Again, as I mentioned, you can reduce the GC impact if you're churning through a lot of objects. Uh, but that's kind of minor. I

objects. Uh, but that's kind of minor. I

I rarely had problems with that before.

Uh, the setup again, it's fairly fairly straightforward. You can configure it in

straightforward. You can configure it in the system, but all particle all Niagara systems come preconfigured to have a pool of 32. So, it will keep around 32 if you spawn them correctly. So, that's

the important part. You you need to specify the the the pooling policy at spawn time. Uh, you have two types. You

spawn time. Uh, you have two types. You

have auto and manual. And when you're doing manual, you must release it back to pool and not destroy it.

Conveniently, you get a warning if you do this wrong. Uh I've seen a lot of warnings in in some places because it was done wrong. Um cool. To set this up, you go into the Niagara editor, you

click on the system, and you kick the back button because you messed it up. Uh

hello. Go back one more. Go back

forward. There we go. Click on the thing and it's hidden under a little arrow that no one tells you about. Um, so

there's there's a little error in the system settings and it will give you this dropdown and you can see the kind of max pool size and the pool prime size there. So all of them are by default set

there. So all of them are by default set up to have um 32 systems available if you're pooling them. Uh, and then the pool prime size is going to be if when

the first system is spawned or when this is first loaded, it will give you that many systems ready to go. Now you can run out of the pool. So, if you're spawning too many, you can run over the

pool. Uh, and the overrun overflow is

pool. Uh, and the overrun overflow is not going to be pulled anymore. So,

those are still going into the garbage.

Uh, going back to effect types, you can just set up a system limit that's exactly that number. And then you can't run out of pool cuz you're stopping it from happening. Um, so when you're

from happening. Um, so when you're spawning things, this is this is in blueprint. There's obviously code

blueprint. There's obviously code equivalent to all of this. Um, you're

looking for the pooling method down there. So, auto release and manual

there. So, auto release and manual release. Uh, auto release is really

release. Uh, auto release is really nice. you just do a little fire and

nice. you just do a little fire and forget effect that you don't need to clean up. You don't need to babysit. Uh

clean up. You don't need to babysit. Uh

again, like a footstep or or a hit effect or the little heal thing that I mentioned earlier. Uh it cleans itself

mentioned earlier. Uh it cleans itself up once it deactivates and releases itself back to the pool. Manual, on the other hand, you probably want to use it for stuff like uh a character being on fire. So, we have an a gameplay effect

fire. So, we have an a gameplay effect and a gameplay cue that's playing that we want to remove at a specific point.

Uh once we're when we're doing that, we just remove uh or release the pool and then the effect is gone. but we still have it. Uh, awesome.

have it. Uh, awesome.

FX budgets. So, this is one of those things that I didn't really use too much. Um, I it's a little bit tricky to

much. Um, I it's a little bit tricky to to configure in some cases, but uh the idea is uh that you can use this very

well for non-critical effects. So when

you have a high intensity combat in your game, for example, or just a lot of things going on, you can use this to reduce the number of systems that are active or how much how many particles

they are spawning based on a budget. Um,

so you can see the little uh graphs in the corner that I totally didn't steal from the Epic documentation uh that you you have uh a Yeah, there we go. So it

it you can li dynamically limit uh calling instances and the number of instance uh calling distances and the instance count uh with these and the configuration for

this is a little bit messy and that's probably why I don't really see it used to in too many places to configure the budgets you have to put everything in INI files and uh it's it's not like

super nice and clean but uh the actual configuration how the particle systems are going to react to things is all happening through effect types. Again,

uh everything happens through effect types. It's great. Um some C bars for

types. It's great. Um some C bars for you to play with. So when when you first want to enable it and have a little play with it, uh you need to enable it with effects budget enabled and then effects budget in editor enable uh just to make

it nice and simple. And then under effects budget asterisk there there are a bunch more settings that you can play with. So how much budget are you giving

with. So how much budget are you giving in the game thread? How much budget are you giving in the render thread? And

then this is going to look after that.

So when you're getting near your budget, uh you can see on the graph on the the left side the left graph that as we're getting closer to the budget uh we are reducing the number of uh we're reducing

the distance culling for example or the number of particles that are spawning or we can go nuclear on this and we can enable this max global budget use. So if

the budget use is at 100% or if 50% we can say that anything that is using this effect type is just done. We're just

nuking them. So again, uh stuff like footsteps. So if you have a if you have

footsteps. So if you have a if you have a high high intensity combat happening, you don't really care about footsteps. I

know I go say footsteps a lot. Uh you

can just go like, okay, if we're using more than half of our budget, we don't care about footsteps again until we are back under that budget. That being said,

uh if your budget is too tight, so as in like the uh tracker obviously tracks the budget, but what can happen is you're calling a lot of systems. Uh sorry,

wrong way. So you're calling a lot of

wrong way. So you're calling a lot of systems, so your cost is going down. So

you're now under budget, but then the budget allows more systems to spawn. So

now you're spawning more systems and then you end up in this cycle of up, down, up, down, up, down. It's really

fun to look at. Um the bottom one down there affects budget adjusted usage decay rate. Difficult word. Uh it

decay rate. Difficult word. Uh it

essentially just slows down how quickly the budget recovers. So if you have a big spike, you're not immediately going back down and then you're letting things to to uh spawning and so you can slow down the budget recovery from that. It's

really cool. Um awesome. And now we're kind of wearing into we need to touch content. Uh lightweight emitters.

content. Uh lightweight emitters.

They've been introduced in 54 I think.

Uh that's what I have written down and I believe past Adam. Um these are really really cool. So this kind of hearkens

really cool. So this kind of hearkens back to the time of of uh Cascade where everything was fixed function and they are fantastic. They're really cool. The

are fantastic. They're really cool. The

one problem with them is that they're only really useful for simple systems out of the box. Um and the reason for that is is uh well why use it? Let's go

through this and I'll come back to this.

So, why these are great is they can reduce your emitter and system costs dramatically and also your memory footprint if you're setting this up right. The annoying thing with them is

right. The annoying thing with them is that they're incredibly limited out of the box. So, uh you have no scratch pad

the box. So, uh you have no scratch pad access, you have some data binding, but not a ton, and you have no dynamic inputs. So, you can kind of see where

inputs. So, you can kind of see where where this is going. You you you lose a lot of the Niagara pizzazz of I can just do whatever I want in Niagara. It's a

big compute shader. I do whatever I want. Um and if you disable the the

want. Um and if you disable the the system state then you end up in an even faster place. Uh but I say it's very

faster place. Uh but I say it's very limited. It is but you can extend it in

limited. It is but you can extend it in C++. So if you have a lovely engineering

C++. So if you have a lovely engineering team uh with you, you can ask them nicely like can I have this and can I have that and can we do this fancy sampling or whatever. [snorts] Um this

blew my mind when I first configured this right because the initially when I tested it it wasn't too impressive and then I uh set it up. So you can see the costs here. Uh this is again for

costs here. Uh this is again for footprints or sorry for for uh footsteps because I love mixing footsteps. Uh this

is the same setup, right? So 10 bots running about in in Lyra kicking up dust and being very annoying in general. So

1.3 milliseconds on the game thread.

That's insane just for footsteps. Um

[snorts] the render thread isn't too bad. 300

microsconds and a little bit on the GPU because this is a GPU system apparently.

And 2.7 megabytes in memory. Again,

doesn't sound like a ton, but when you look at it next to the lightweight emitter, uh it's very difficult to see, but we went from 1.3 milliseconds to 200

microsconds, so 2 milliseconds on the game thread. Uh and then the render

game thread. Uh and then the render thread didn't change much because we still have to render all of these, but because we are no longer dispatching to the GPU, we did save.1 milliseconds

there as well. Obviously, GPU cost is gone. And then the memory is onetenth of

gone. And then the memory is onetenth of what we started with. So, I I really really like this thing. I wish it was a little bit more flexible out of the box, but it does work. It's fantastic.

And then uh Niagara data channels. This

is the new big thing. By new, I mean it got introduced in 53, so not that new.

Uh but it got upgraded in 54 and then upgraded again in 55. So most of the things I'm going to show you in a second. You can just do it through a

second. You can just do it through a wizard now in Unreal Engine 55. Just

right click, add spawn by data channel.

It just does it for you. It's great. Um,

but the idea with this is is uh it's fantastic for aggregating high volume spawned effects. So, hit effects,

spawned effects. So, hit effects, tracers, muzzle flashes. Um, again, why to why use it? Because it's great for the thing I just said. Uh, you can consolidate consolidate spawns within a

grid to reduce the spawn cost. So, you

have one particle system that is just spawning all the particles for you. uh

and that obviously reduces the number of system tick uh and is just generally a nice thing. It also has pooling built

nice thing. It also has pooling built in.

There are two types of it. There's

global and instance uh channels.

Uh they do a little bit of a different thing. I'm just looking at my clock and

thing. I'm just looking at my clock and realized I need to go through this a little bit quicker. Um we'll come back to that anyway. And the setup is a little bit more involved. So again, this is this is one of those where we have to make content changes. We have to go in

and edit the thing. And you have to specifically build the particle system to support this. And you also have to spawn it in a special way. Um,

I'm just going to let this play for a minute. Uh, it's that simple. You just

minute. Uh, it's that simple. You just

create a data channel asset and then you configure it. Uh, I'll have a uh stills

configure it. Uh, I'll have a uh stills of these. Uh, so don't worry about what

of these. Uh, so don't worry about what you just saw over there. Um, so the idea is you have two types of data channels.

you have the the data channel global which uh is a communication which is which is a the data channel that handles communication between everything that's in the world. So if you have uh you're writing to this data channel everything

has access to both writing to this channel and reading from this channel.

Uh which can be useful for you know communicating back to blueprints for example from a Niagara system which is really cool. Um alternatively what we

really cool. Um alternatively what we are going to be looking at here is the data channel islands. The idea with this one is is it can partition the world into grids for you. So it when you're

spawning the particle system, it spawns a box around itself effectively or like it only listens to writes and reads within that box. So you're not communicating with everything all the

time. It also spawns you a particle

time. It also spawns you a particle system and that particle system is then going to listen to everything else.

Um, cool. These are the bits you need to add. Well, the the system side is is

add. Well, the the system side is is more just a nicity, but on the emitter side, you need a data channel reader and uh sorry, you need to prep the data channel and spawn the particles and then

in the particle, you need to then read from the data channel. Uh what I like to do though is I like to initialize my data channel reader in the system. Um

and you also have to add this complete if unused module to your uh particle system. uh the main module of it uh

system. uh the main module of it uh effectively just kills the system if there's no more spawns. It hasn't been uh spawns in a certain amount of time.

And then in here I created a new parameter where I just in uh initialize the uh data channel reader. It's you

know it's just there so I don't have to initialize it for every emitter individually. Uh moving on to the

individually. Uh moving on to the emitter side of things. Uh this is where you effectively spawn the particles based off of the payload from the data

channel. Uh I'm going to leave that up

channel. Uh I'm going to leave that up for a second, but there's brilliant documentation I'm going to link to after this. Uh this is this is just like one

this. Uh this is this is just like one way of doing it. So the idea here is that you uh pull in the the data channel reader, you feed it the emitter ID, and that spawn conditional is the thing that

actually does the particle spawning for us based on the uh the payload from the um data channel. Moving on to the particle spawn. So once the particle has

particle spawn. So once the particle has spawned, this is the thing we do to it.

Uh we pull in the data channel again. We

tell us tell it that look this is the particle that we are. Uh where should I be? And then we read all the data from

be? And then we read all the data from the payload again for the particle itself. And then we apply the things. So

itself. And then we apply the things. So

you know where the particle should be, what forces should apply, things like that. And then effectively that's it. It

that. And then effectively that's it. It

should just work on the particle system side. Now spawning these things. Uh

side. Now spawning these things. Uh

going through uh blueprint, you have two nodes to use for this. Both have their own uses. Uh the one on the right, the

own uses. Uh the one on the right, the batch one. Uh go back real quick. the

batch one. Uh go back real quick. the

one on the right on the batch one that works very similar to when you spawn a particle system and you get a little reference to that object and then you can write the individual um parameters

the one on the left uh just exposes all the parameters for you immediately so you can just plum it in immediately and all all going to work and you'll see the three booleans on both of these uh visible in blueprint visible in Niagara

CPU visible in Niagara GPU that's which layer within the channel we are writing to and what it is visible to so you can even use this to communicate between blueprints which is kind of cool gameplay play messaging system. Who

needs it?

Awesome. And here's a quick view of what it looks like. So this is before. So

when you're, you know, shooting, you see all these particle systems spawning.

Those are all unique systems. We shoot up to, I think, around 600 milliseconds at one point on the game thread. But

then we switch this over to data channels.

No more systems. What's going on? But

everything still works. If you if you look closely, there is a single system in the middle of the level. Uh but our cost is dramatically down on the game thread. render thread again because

thread. render thread again because we're rendering all these particles still. Uh there's still a cost, but on

still. Uh there's still a cost, but on the game thread, we have a massive saving because we're not spawning systems. We have one system that does the spawning of the particles for us.

It's great. Awesome. We're coming to the end of this now and I put together this reference table uh that kind of just gives you an idea to what what to look for, what kind of systems you can use uh

for different things. So, what how what I like to do is I like to categorize my things into gameplay critical and non-gameplay critical. based on that you

non-gameplay critical. based on that you might use different uh different things for these uh the stuff that are on one side. So

you see that the top ones gameplay critical you have all those in one big blob these are all fantastic together but the ones when I say or it's mutually exclusive you can't do niagra data channels and lightweight emitters

because you can't have custom nodes in in the lightweight emitter. So that's

the kind of idea there is is some of these might not work with others but uh the kind of reference table should give you a good idea about what to use there.

I know we're coming up some questions.

Do we have time for questions?

Fantastic. We have time for questions.

Let's go.

[applause]

Loading...

Loading video analysis...