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 video analysis...