I started as a junior vfx artist (it will be a year in January) and through this, I learned Niagara and a lot of new things with VFX.
But now there is a question that I have been asked and thought about.
What can be done to optimize vfx (in niagara in particular but in general)?
I saw that Lod could be used, fixes and non-fixed bound and scalability setting.
I’m as well using material instances, unlit material in general (I’m doing stylized fx), textures of 512x512 in general, making mesh as light as possible (except necessities of course), and deform my UVs but I’m sure that there might be more and I wonder if someone in the community could help me to optimize more ^^
I mostly lurk around here so not a VFX expert, as such take these with a grain of salt:
I dabble in gamedev and from what I’ve seen the main way you’re going to be able to optimize your effects is by reducing the number of draw calls you send your GPU. Graphics cards are really good at drawing lots of instances of one thing quickly, but asking them to draw different things can be slow because the CPU needs to send them info every time that thing changes. For example, if you have 5 emitters, each with a different shader, the CPU is going to need to call the GPU 5 times. If you manage to make all those use the same shader, maybe by packing all your texture into a texture sheet, the CPU will only need to send that one draw call.
I’ll let you google / youtube around for more info on how to reduce draw calls, I’m sure there are plenty of guides around.
Beyond that, Unreal has really good performance tracking tools, so I’d suggest just trying things out. tweak some options and compare render time.
When using UE, Draw is almost never an issue (unless you do silly things). Just keep on eye on it. If it suddenly does spike, you’re probably doing a silly thing and you should figure out why, and fix it.
Here are a few things to watch out for when optimizing :
Think about overdraw : overdraw happens if multiple translucent objects need to be renderered over top of each other.
Overdraw can easily kill gpu performance even with simple shaders, so needs to be taken care off, either by reducing coverage (size) or reducing particle count.
Does this need to be translucent/additive : Additive and translucent objects do not draw into the depth buffer, and as such, do not stop fragments below them from drawing. It is therefor almost universally better to use opaque/masked elements than translucent ones where possible.
CPU particles => GPU particles : CPU particles need to simulate on the game thread. Since game logic like AI is already running there, this often becomes a point of strain. The CPU is also a lot worse at processing larger numbers of particles, look for places where you can switch emitters to simulate on gpu.
Per particle => One time : It’s often possible to find calculations that can be done once and than applied to every particle, in that case of Niagara this can be done by doing the calculation on the emitter or system level and storing the result in a system or emitter scoped parameter.
Reduce Emitter/System/Component count : Each of these come with their own pool/batch and other management level logic that creates overhead. Reducing these in numbers by combining them can improve performance and reduce bulking.
Use pooling : Pooling reserves existing items for later use. This is usually in the form of initializing ones on the first use, then waiting for a timeout until release. When a new item is requested use the pooled item instead. (you don’t need to implement this yourself, UE has solutions for component and particle pooling etc)
This can reduce spiking when frequently spawning large numbers of components or particles.
Check for simulation on unnecessary elements : Using Stats Niagara Systems for example you can see which systems are simulating and where. Check if at a certain point in the level there are unexpected effects simulating, and try to figure out why and fix it. Scalability can help turn off particle simulation at distance/when not in view.
There’s more, but these are the ones I could think of that you haven’t mention yourself yet.
Oh wow this is a comprehensive answer!
Interesting about draw calls in UE, is this because the engine takes care of batching stuff by itself when compiling a build ? Or simply because modern desktop hardware is good enough to not worry about it ?
Thank you both of you ^^.
When you say “storing the result in a system” how do you do that @Niels? Thank you so much for your help again it helps a lot
For Niagara :
In SystemSpawn or SystemUpdate you can write to a parameter in the System namespace.
If you look at your parameter window you’ll see there’s a bunch there already System.Age, System.bCompleteOnInactive etc…
You can make custom ones by using a setter module or by writing your own modules
OOOOH I see, this is so smart xD and I’m using the user part but never put anything in System attributes, I’ll try that thank you again, Niels.
I may bother you again in the future tho sorry in advance
Mainly that you will have other performance problems before drawcalls from effects starts showing up on todays hardware. Niagara instances and emitters are expensive as hell in their own right. 1000 drawcalls is very cheap. 1000 Niagara systems is death.
Also, atlasing your textures won’t really help you with draw calls as you’d apply the materials to different renderers anyway. It would only help in specific cases where the same material would be used for multiple purposes on the same renderer and I can’t think of an example for it. Maybe some kind of lensflare setup?