Hi guys. I have a Niagara system used as a very performant way to visualize projectiles. Works like a charm with x30 performance boost compared to actors for same task.
It works following way:
- I have C++ subsystem that outputs an array of projectile positions. I feed this array to Niagara system and spawn/kill particles to exactly match array length.
- I loop through the particles and for every particle I assign position of according element from array (using execution index).
In other words I manually move particles to my positions every frame.
The only problem I get is when I hit some critical amount of particles at which particles start to change their index and order in particle array. I know this is normal and unique particle IDs are not persistent. For persistent ones special checkbox in emitter should be set to true but this would not help in my case.
So in frame 1 particle A should be at pos 1,1,1 and B at 2,1,4
At frame 2 they should move for example 1 unit in Z axis A 1,1,2 and B 2,1,5
Instead I get B 1,1,2 and A 2,1,5
Particles have only mesh renderers and in theory this should not matter and we should see an illusion of movement. And indeed it works good on high speed. On lower speeds I get some weird bug, edges of particles start to flicker. I suppose the renderer tries to somehow optimize the draw call since it expects to see same particle in position close to previous frame. Or its anti aliasing issue or some other render bug. It is there on high speeds as well but just not so noticeable.
May be there is a way to fight this bug or somehow use persistent ID to control particular particle but this makes whole thing much more complicated.
I’ve tried something similar in the past and what worked best for me, was to keep particles assigned to a specific array entry. I achived this by just pre-spawning a specific amount of particles and never killing them. Similar to pooled actor spawning. This way persistent IDs work well. As each particle is an exact entry in the array. This might have some overhead though. (But should be reasonable if you are using GPU particles).
Maybe there is a way of achieving persistent IDs that match the array precicesly, even when respawning/killing particles?
I think what you might be seeing is motion blur? As a test, you could try disable motion blur on the particles or in your project in general, just to make sure it is that.
Hi! First of all many great thoughts, thank you! You got the problem completely, looks like you really came across similar task before.
You led me to idea of using Niagara with prespawned particles and never killed them, and more important is to do similar thing in projectile pool / projectile manager. Usually one should not have “gaps” in the pool, this is whole point of the pool to run it in one cycle one by one (in addition to performance boost because you do not create additional objects). But if we just mark unused projectile-elements in manager we can sync it with Niagara with some small overhead. The main problem is how to sync index of projectile in manager to particle in Niagara, since its one way communication.
Yes we would have to check if projectile is used or not in manager, but this is a simple O-notation f(n). Then make an array in emitter and save persistent ID in it, and element index would mach element index in manager. Then we can get persistent ID of projectile from it. And for used = false just hide the particle. I just do not know how to hide the particle from rendering, I only know how to kill it
Another great thought of you is to try assigning persistent ID ourselves. If we can assign it same index we have in manager then it would be perfect solution. Not sure if it is possible at all but I would look in spawn burst script.
Update: I tried disabling motion blur and it worked!!! I spend so much time with this issue. So many people tried and could not help. And it was so easy I cant believe it. Thank you Thank you Thank you !
Disabling motion blur or setting it to approximate introduces new artifact, sort of trace behind the mesh. Still this looks much better then flickering. I will try to make persistent ID based solution.
I have been experimenting with motion blur. There are 3 settings: disabled, approximate, precise.
Disabled leads to this trail effect.
Approximate leads to same trail, but if we bind correct velocity it works good.
Documentation: Approximates the motion vectors from current velocity. Saves memory and performance, but can result in lower quality motion blur and/or anti-aliasing.
I don`t know how precise work. My guess it saves particles position at start of frame to some buffer. Then solving forces and velocity works out. And Blur interpolates between old and new position, since there is no way to feed position at previous frame I dont see a way to control it without moving particular particles with use of persistent ID.
Finally I have found the solution, looks very easy but took 2 month to get there :), wanna share it.
Rewrite position in Particles.Previous.Position and this would work with Motion Vector Setting = Precise.
Another option is passing Velocity to Particles.Velocity. Then one should use Approximate for Motion Vector Setting. When you think about it, it makes a lot of sense.
I’m glad you found a solution that works!
With Motion Vector settings set to approximate, Niagara allows you to control MotionBlur via the ‘Velocity’ binding (assuming your Material supports velocities/motion vectors). So you can bind this to whatever you need to alter how much MotionBlur to apply and in which direction. It does not need to be linked to velocity.
Yeah, I have tried binding custom velocity. For example one can multiply Particle.Velocity by custom float in custom module and get an interesting blur effect.
“assuming your Material supports velocities/motion vectors” -
This is very interesting, never heard of such material properties. How does it work?
Translucent Materials do not output velocities by default. You will need to tick this box in the material to force it. Not a problem on opaque / masked materials, as they output velocities by default.