Fixing first frame gaps in Niagara ribbons

Recently working with ribbons I’ve stumbled upon a problem that led me to a pretty deep investigation of how Niagara emitters and Attribute Readers work and I think it’s worth sharing.

The Crime

Let’s say we have a Source emitter that spawns a random amount of particles at random points in time and we have another emitter which should generate ribbons following those particles. The one important restriction: ribbons should start precisely at the particle’s spawn location. For example imagine a bullet flying from the muzzle and for some reason we want the ribbon to start from a muzzle location.

final_result
Gray squares at the bottom illustrate spawn locations

The obvious approach is to use an attribute reader in the Ribbon emitter. The process would be to:

  1. Read the NumParticles from the Source emitter (which should return the total amount of particles alive in the Source emitter for the current frame)
  2. Spawn the same amount of ribbon particles
  3. Read the attributes from the source particles and be done

Except we will get gaps between the spawn location of the particle and the start of a ribbon, which might be fine in some cases but it’s completely unacceptable in others.

To illustrate this I have the following simple setup.

We start with the Source emitter. First we create a SpawnThisFrame attribute in which we calculate the amount of particles that should be spawned at the current frame. Here we spawn one or two particles with 10% probability. Then we pass this attribute to the Spawn Per Frame module. I disabled Interpolated Spawning as I want particles to be at their spawn positions in their first frames, so I can sample them from the Ribbon emitter and start ribbons also from the spawn locations. Require Persistent IDs is enabled so we can later read it from the Ribbon emitter and use it for setting RibbonID.

At spawn we put particles on random discrete positions:

The rest of the emitter is just the usual simulation, nothing special, so I’m not showing it here.

In the real-world case the number of particles to spawn and their starting positions may come from some gameplay logic via user-parameters, instead of being just random.

The Ribbon emitter does exactly as I described before: reads the NumParticles from the Source, spawns the same amount of particles and reads the attributes from source particles. I added Sprite Renderer so we can see every ribbon particle.

I’ve also illustrated the spawn locations with SpawnPoints emitter which simply places sprites to those spawn locations. The logic for Position is the same as in Source emitter except for randomization.

And here they are: gaps between spawn locations and ribbons.

ribbon_gap_01

The Criminal

The reason for such behavior is the NumParticles attribute.

setup_07

For CPU simulations it actually contains the amount of particles alive for the previous frame and not for the current as one would expect. For GPU simulations the situation is even “worse” and this attribute may contain the amount of particles spawned since the last time the emitter was able to fetch the actual amount of particles alive from the GPU, which might be several frames ago. And for some reason it still doesn’t account for the particles spawned at the current frame. So using this parameter to decide how many particles to spawn for the Ribbon emitter we are always lagging behind:

  • Frame 1:
    Source emitter spawns 1 particle at spawn location P0.
    NumParticles is 0.
    Thus the Ribbon emitter doesn’t spawn any particles.
  • Frame 2:
    Let’s say Source emitter doesn’t spawn particles, but the first particle is already moved to a new position P1 after the ParticleUpdate stage.
    NumParticles is 1.
    The Ribbon emitter spawns 1 particle and reads the position P1 from the source particle - and this position already differs from the spawn location P0, hence the gap.

The Solution

How can we solve this?

One option would be using Events instead of attribute reader as they don’t rely on the NumParticles attribute and don’t have such problems. However this constrains us to using only CPU emitters which is not great so let’s consider another approach.

While we don’t know how many source particles are alive in the current frame (some of them might have died) we do know how many particles there were in the previous frame and how many particles we spawned in this frame. However the information about how many particles we want to spawn in this frame in our current setup resides in the Source emitter and we can’t read emitter attributes with an emitter-level attribute reader.

So one solution is to move the calculation of the SpawnThisFrame attribute to System level so it will be available in the Ribbon emitter. And if this information is passed by user-parameters then it’s already available for all the emitters and we are already good.

Then in the Ribbon emitter we spawn ParticleCount the number of particles that is equal to NumParticles from the Source emitter (which will give us at least the amount of particles alive in the previous frame) plus SpawnThisFrame the number of particles that should be spawned in the current frame from the System attribute or user-parameter. This amount may be bigger than the actual number of particles alive in the Source emitter (some particle might have died in the current frame but we don’t know about it yet), so when reading the attributes from the Source emitter we should check if the read is valid and kill the particle if it’s not.

And voilà! The problem is solved.

ribbon_gap_03

The Investigation

For those of you who want to verify this behavior or are just curious here are some more details from my investigation.

First, to check what is going on with NumParticles we can write this attribute to some other emitter attribute (let’s call it ParticleCount for example) and visualize it via the Niagara Debugger. This is the most simple emitter that spawns ParticlesToSpawn amount of particles every frame. And ParticleToSpawn increments by one every frame just to get something changing.

If we pause the debugger and refresh the system we will see that at the first frame we have one particle but the ParticleCount is zero. Step to the next frame and we will see that now we have three particles, but the ParticleCount is one. In the next frame there will be six particles and ParticleCount is three and so on.

I’ve also found this function GetNumParticles in Unreal’s source code NiagaraEmitterInstance.cpp, with comments explaining why we get TotalSpawnedParticles in the NumParticles for the GPU emitter.

If you have any other solutions or thoughts on this - I will be happy to hear them :slight_smile:

3 Likes