These maggots are completely animated by shader. The Particle System spawns them in a random position and the shader does the rest.
I want to start by saying that this tutorial by Andrey Torchinsky is what made me realize the full potential of shaders and that I actually can do it myself, even though I’ve always been terrible at any kind of math. I heavily recommend to go check that one out first if you haven’t already. He explains it so well and it’s the base for basically any shader I make that has mesh animation. You’ll find that the Wiggle part of this Maggot shader is an edited version of his.
I’ve found that node-based shaders are easily translated between Unreal and Unity, the only painful bit is the Space node to use, as in particle/local/object/world position. Basically all of the shaders I’ve done in Unreal were principally made to be used in Cascade Particle Systems and some things don’t quite work as expected. I hope that’s not the case with Niagara, but I haven’t had the opportunity to work with that one yet.
Anyway, lets get to the shader:
As I mentioned before, the Wiggle part you can understand by looking at Andrey’s tutorial, it’s the exact same logic with different values and translated into Unreal’s nodes. The tough part for this shader was the rotation with forward movement and that is what I’m going to explain in more detail.
It took me a while to find out how to make this work, lots of trial and error. The rotation and movement are pretty straight forward by themselves, but making them work together properly is a bit more tricky than it might seem, specially if you don’t have the right Position nodes.
For getting the forward vector to be able to direct the movement in the right direction I ended up using the plain old formula: (Sin(yaw), Cos(yaw), 0). The yaw being the random rotation (value from 0 – 1) that I get per particle in the Particle System thanks to Dynamic Parameters. That then gets multiplied by the MovementSpeed, and ParticleRelativeTime (0 – 1) so that there’s constant movement. If you use the Time node then they’ll show up in a random place. I’ll get back to that Add(0, 0.15) node in a bit.
The rotation is done for me thanks to that RotateAboutAxis node. NormalizedRotationAxis is about which axis the rotation will use, in this case I want Z so I input (0, 0, 1). RotationAngle gets the same input as the angle for the forward vector (I’ll circle back to that (1-x) node later). ParticlePosition for the Pivot Point because I’m using it in a particle system and they will never change direction once they start existing. Offsets happening in the shader don’t update colliders, shaders, pivots, nor particle position, it’s merely visual. And AbsoluteWorldPosition in Position.
Now, going back to those little tweaks I made to the angle. To be honest I’m not fully sure why they need to exist, my main suspect is the orientation of the mesh but I could be wrong. The only thing that I know is that without it then the rotation and movement wouldn’t quite match, they would be moving sideways/backwards. So you might not need these two nodes if you decide to reproduce this.
The subtraction to merge them. The rotated maggot goes into the SplitComponents that make the wiggle happen, this wiggle uses UVs and is in the Z axis so it doesn’t matter what rotation it has. Then I Append it together and Subtract the Movement values from it. I did Subtraction so that they move in the right orientation, with the head forward; depending on your mesh’s orientation you might want to do an Add instead.
I know there’s probably some small tweaks that would make it more optimized but with my limited understanding of math and the lack of information there is for shaders that work with Cascade I’m honestly quite proud of this one.
The trial and error
I find that there’s more learning when you see what doesn’t work than just given the answer of what works. So I’ll skim through my process of getting to the right answer:
Originally I was subtracting the movement directly to the Y axis, but that doesn’t work if I want random rotations since they’ll always be moving in the same direction. The first step was making both the rotations and movement work independently.
Then a coworker reminded me of the existence of Normalize, this set-up was making perfect sense, but it was not working. It was making every maggot get super big with time or behave with some weird offset, specially when the Particle System was close to the Origin point.
I tried many different variations of this, switching up a bit the order and in between that RotateAboutWorldAxis_cheap and the original RotateAboutAxis node, seeing if any sort of combination worked. But nope. Then I thought about going deeper and just get the normalized vector myself instead of having a RotateAboutAxis node in between, looked up the formula and it started to get me closer. Then I realized it was kind of working but with a fixed offset, which is when I implemented the tweaks with the Add (0, 0.5) and (1-x) nodes. And it worked.
So yeah, enjoy 🙂