Hey everyone,
I have been playing around with ribbons and I figured I would share my findings. The goal was to use a non-looping flipbook that tiles along the U coordinates. The ribbon is also using the “tile by distance” UV setup so the UVs dont strech/compress as the ribbon grows or shrinks, it simply tiles more as the ribbon gets longer. When you have a looping flipbook, you can just plug in a time node into your material flipbook and control things that way. Things are not so straightforward when you have a non-looping flipbook. Here are the results.
I started by making an effect that is spawning a bunch of particles that fly outwards. I wanted to use the spawn particles from other emitter to have each particle create a ribbon trail behind it as it flies. Imagine an explosion with a bunch of smoke trails.
Luckily, the sample particle from other emitter (counterpart to spawn from other emitter) has options to handle the ribbon link orders so each particle creates its own separate ribbon instead of linking every particle together and making a massive tangle of geometry.
Normally when you use a flipbook, you tie the animation to the normalized age of the particle. Since a ribbon is made of many particles that can have different normalized ages, this causes each segment of the ribbon to display a different frame. As you can imagine, this causes a mismatch and nasty artifacts along the entire ribbon length. The sample particles from other emitter module does have options for the particles to inherit age and lifetime, however it doesn’t seem work well in this situation. You still get a lot of artifacts along the ribbon length.
(Both age and lifetime inherited from other emitter)
If we use the debug scalar on the lifetime we can see why. The values for each particle aren’t exactly the same. They are close, but its still off enough to cause issues. I suspect its caused by variation in framerate but I’m not certain.
The solution is to control the age/lifetime manually for each ribbon. We need to get the ID of each ribbon (inherited from the other emitter) and assign lifetime value, ensuring that each particle with the same ID gets the same value. Luckily it only requires 2 small scratchpads to get working, here is how you do it.
First, create a scratchpad in the particle spawn. Make sure it is after the sample particle from other emitter. You need to inherit the ribbon settings from there for this to work.
Inside the scratchpad set it up like this…
The first section (section 1) is the core bit and the only thing that is required. The second section is optional. That allows you to randomly multiply the tiling distance per ribbon. Helps break up the uniformity. The hash float will generate a deterministic random value which will be used to drive the lerp between your lifetime values. (min/max ribbon lifetime)
Next, create a second scratchpad in the particle update script. We will use this to normalize the lifetime of this RibbonLifetime value we have created. The clamp is required because sometimes the normalized age stops just above 1, (ie: 1.00003) which will mangle the UV.
Once you have this normalized age param, assign it in the ribbon renderer normalized age binding. In the ribbon renderer settings, also set the UVs to “tiled by distance” and adjust the tiling length. If you did part 2 of the first scratchpad, those min/max values are multipliers on that tiling distance you set.
Once you do that, each of the particles in your ribbon will have the exact same normalized age value, based on the min/max age you set in the first scratchpad. This way you can have multiple ribbons with different lifetimes and the animation will play perfectly based on that.
Make sure you put the age normalize scratchpad above the particle state in the update stack. I also disable the kill particles when lifetime has elapsed. This allows me to stop the back of the ribbon from dying too soon (if your lifetime in the initialize particle is too short) and the lifetime is applied to the entire ribbon length instead of per segment. (ignoring the lifetime in the initialize module)
To ensure that the ribbon dies when the normalized age is elapsed i add a kill particle module and set a bool to kill the particles when the normalized ribbon like is greater than 0.989.
Thats all the setup required on the niagara side. We also need to do a few things in the material. The mipmaps sometime cause issues so we need to fix that. The animation on the tip of the ribbon also doesnt play smoothly sometimes so we need to fix that as well.
To fix the mipmapping issue, we use the mip derivatives and the ddx and ddy. Select your texture sampler where your flipbook is applied and set the mip value mode to derivative. This will enable the ddx/ddy pins on the sampler. Create the ddx/ddy nodes and plug them into the sampler and the flipbook UV center output pin. (not UV pin) This will fix any mip issues across each segment.
Sometimes the segments near the tip of the ribbon will not animate smoothly and jitter between frames. To fix this, add a frac node between the tex coord and UV input.
Youre done! Now you can enjoy non-looping flipbooks that can tile along the length of your ribbon. Its useful for things like smoke trails such as tendrils from explosions that can bend, but still have an animated flipbook on them.
Caveats:
At the moment this only works if youre spawning a burst. It sort of works if the leader particles are spawning as a rate but only for as long as the max lifetime of the ribbon due to the lifetime normalization being tied to the system age. I might update it in the future to work with an unlimited spawning rate for the leader particles.