Particle tutorial trail spawner
|For PK-Fx Editor version : 1.5.1 and above
Main page: Particle tutorials
In this tutorial, we'll see:
- 1 Spawner evolver
- 2 Changing global trail aspect
- 3 Smoke trails
Our basic setup for the trails will be a simple effect that shoots a particle affected by gravity.
Here, the rig consists of a single infinite layer with a spawn rate around 0.5 particles per second.
We added a simple shape sampler 'sphere' named "SamplerVelocity" with a radius of 0.2, at position '0;3;2.7'
It is used in the spawn script to initialize the particle's velocity so that the particles shoot towards the shape:
The physics evolver has its 'ConstantAcceleration' set to '0; -9; 0'
This is what it should look like:
Now, we are going to create a trail. In Popcorn, trails are made by telling a particle to spawn other particles in its path. Our simple particles that shoot out towards our shape and fall to the ground will be the trail source that will spawn the trail's particles.
To create a trail, we'll need to add a new evolver: the CParticleEvolver_Spawner :
The new evolver added in the treeview contains all the elements of a standard particle layer, except it is an evolver.
You should instantly see a trail of particles forming behing each source particle:
There are various parameters that control the forming of the trail. The main ones are the 'SpawnMetric' and 'SpawnInterval' values.
They control the distance between each particle of the trail. The metric used can be 'Distance', 'Time', or 'Custom'. (For more details on the 'Custom' metric, see CParticleEvolver_Spawner)
Most of the trails use mainly the 'Distance' metric:
The SpawnInterval value basically says "the interval between each particle should be SpawnInterval units in SpawnMetric 's units."
For example, if the metric is 'Distance', setting a value of 0.02 in SpawnInterval will tell the system to spawn a trail particle as soon as the source particle has travelled more than 0.02 world units since the previous spawn.
If the metric is 'Time', setting a value of 0.175 in SpawnInterval will tell the system to spawn a trail particle as soon as 0.175 seconds have elapsed since the previous spawn.
Now, the current trails are too far apart. we'll bring the SpawnInterval value from 0.1 down to 0.01
The trails should now appear as a solid lines.
Changing global trail aspect
Now that we've got basic trails set-up, we're going to see how we can change the global trail aspect, and create more interesting effects.
We will first reduce the base layer's spawn rate down to 0.2 particles per second, and increase the trail's particle's life from 1.0 to 3.0 seconds by changing the trail particles spawn-script:
Within the trail particle spawn-script, as with standard layer spawn-scripts, we have access to the 'float' variable 'spawner.LifeRatio', that contains a value between 0.0 and 1.0 representing the life ratio of what spawned the particle.
In a standard layer, the 'spawner.LifeRatio' will go from 0 to 1 across the emitter's lifetime. But in case of a trail, its value is the LifeRatio of the parent particle.
Therefore, we can use that value to detect if the particle is spawned at the beginning or at the end of the trail.
For example, to make a trail that starts big and end small, we can set the 'Size' property of the trail particles to a value that depends on 'spawner.LifeRatio' :
This gives us a trail that starts big and ends small.
The particle size at the beginning of the trail will be: 0.4 * (1 - 0.0) = 0.4
And at the end: 0.4 * (1 - 1.0) = 0.0
It will vary linearly across the trail's lifetime:
Using flux functions
Initially, the particle spacing is constant (the SpawnInterval value in the trail evolver is a constant).
However, if, like above, we change the particle radius across the trail, with a constant interval, large particles will appear clumped together, and smaller ones will appear far apart, creating holes in the trail:
To correct this, we can either spawn a lot of particles, or find a way to tell the trail spawner to change its reference interval across the trail's lifetime.
Flux functions can be used to do just that: control particle spacing across the trail's lifetime.
To create a new flux function, right-click on the evolver trail, and select 'New Flux Function':
The editor will create a new flux curve, initially set to 0.0
As this curve represents the particle flux (equivalent to the particle rate, that is, the speed at which particles are emitted), a value of zero means no particles will be emitted...
Therefore, you will first see the trail disappear when creating a new flux function.
Just select the FluxFunction node and move the curve control-points to a value other than zero, and you will see the trail reappearing:
As you will have noticed, the higher the curve, the closer the particles.
We can now tweak the flux curve to make our particles touch each other across the trail, despite them having different sizes:
As a side note, in the above examples, the initial "solid" trail had around 1080 particles.
Using a flux curve, for a similar aspect, the particle count was brought down to 110 particles... that's ten times less !
Of course, those values will depend on your trails. Using a flux curve to get uniform spacing for a constant-size trail will be totally useless, as, by definition, the curve will be an horizontal line (a constant value). And it's faster not to use flux curves, so you should just tweak the SpawnInterval values instead.
Also note that flux curves do NOT replace the SpawnInterval value. They are combined with it, so you can change both to tweak your effect.
Using trigonometric functions to create spirals
We can also use the 'spawner.LifeRatio' to compute an angle, and use that angle to compute a rotation to create a spiral along the trail.
Make sure you also check the 'UseVelocityOrientedSpawnMatrix' checkbox in the CParticleEvolver_Spawner's properties to make the spiral's axes follow the direction of the trail:
This will make the trail particles spawn along a spiral around the source particle's path.
You can play around the constant values in the script to experiment with different spirals. (the value '10' above is the number of half-turns the trail will make, and the value '0.5' is the spiral radius)
Using a curve to control spiral radius
We can get a smoother and more interesting effect if we create a curve sampler, sample it and use it as the trail radius in the script:
Tweaking the curve makes it look like a cheap rendition of infogrames's logo :D
Of course, increasing the radius at some places in the curve makes the particles move further apart, and requires some tweaking of the spawn flux curve to keep the trail's solid look:
Here is the effect after a few changes in the layer's spawn rate and velocity-shape:
We're now going to transform our abstract spiral-trail into a more believable smoke-trail.
First, start by setting up the trail's billboard renderer to use the "Textures/BlastPackExtended.dds" texture, and its matching "Textures/BlastPackExtended.txt" atlas definition file.
Also, set the material to "AlphaBlend_Additive_Soft"
As soon as the atlas definition file is set, the billboard renderer will automatically add the 'TextureID' field to the list of particle fields (see the basic tutorials for more details on texture atlases).
Next, initialize the 'TextureID' field to the smoke sprites (texture ID 32, 33, 34, 35):
TextureID = rand(32, 36);
The trail looks a bit more like smoke now.
However, as you'll have noticed, the parent particle is still drawn as an ugly yellow blob.
We won't need to draw it. So just right-click on the parent particle's billboard renderer, and select 'Delete' to remove it.
Now, you will notice the entire trail disappears. Don't worry.
The 'Size' particle field is automatically added by the billboard renderer. Therefore, unless you manually declared it in the particle fields, removing the billboard renderer will remove the 'Size' property from the particles.
And the spawn script tries to modify 'Size' by affecting a value of 0.25 by default. Therefore, the spawn script produces a compile error:
Correct it by removing the line where 'Size' is used:
Hit Ctrl+S, and the trail will reappear, this time without the source particles drawn as ugly yellow dots.
Now, we'll add a curve sampler named "ParticleRadius" to change the particle radius, and replace in the spawn script:
Size = 0.4 * (1 - spawner.LifeRatio);
Size = ParticleRadius.sample(spawner.LifeRatio);
We'll then add a rotation evolver to the trail particles, to make the trail less repetitive.
In the spawn script add the following line:
Rotation = rand(-pi,pi)
To randomize the initial orientation of the particles.
This is what it should look like, after some tweaks to the flux function to accomodate the new sizes :
Making the trail expand with Velocity
Currently, once spawned, the trail particles stay completely static. To add motion to the trail, we're going to use the particle's velocities to make them shoot out of the curve's center.
As we already have the position in a spiral, we'll just set the velocity vector to the same vector, this way the particles will shoot out in the spiral's direction, and it will look like the spiral is expanding:
Note the trick "Velocity = Position" works only because in the spawn script, all the positions and vectors (including velocity) are in the particle's local-space:
To help you debug your scripts, you can visualize the velocity vectors of each particle directly in the viewport.
In the treeview, select "
Editor Properties > Global Properties ", and in the node properties panel, scroll down until you find "Float3StreamsToDebug", "DebugVelocityScale", and "ShowDebug"
- Check the "ShowDebug" box (or check the ShowDebug button in the viewport top toolbar)
- Make sure "Velocity" is in the list of the "Float3StreamsToDebug" property
- set "DebugVelocityScale" to a value around 2.0 or 3.0 to better see the vectors.
The velocity vectors should appear in the viewport.
Note that you can use this visualization to debug any particle field of type float, float2, float3 or float4. Just add their name to the "Float3StreamsToDebug" property, separated by semicolons ';', and change the scale if necessary to better see the vectors in the viewport.
We can now add some friction in the physics evolver to finalize the movement of the trail.
Faking lighting of the smoke
As it is, the smoke trail is pretty dull, we will animate its size and color.
We will light-up the beginning of the trail as is it was spawned by some burning debris that light-up the smoke.
First, create a bunch of new particle fields:
float4 Color float ColorCoeff float SizeCoeff
And a bunch of evolvers:
- Evolver spline float4 "Color"
- Evolver spline float "Size"
- Evolver script (to apply the color and size coefficients to the curves)
In the script evolver you just created, add the following lines:
Color *= ColorCoeff; Size *= SizeCoeff;
Without these, the Color and Size curves would overwrite the values we set in the spawn script.
also change the spawn script and replace
Size = ParticleRadius.sample(spawner.LifeRatio);
SizeCoeff = ParticleRadius.sample(spawner.LifeRatio);
ColorCoeff = 1 - spawner.LifeRatio;
That last line will make the ColorCoeff go from 1.0 to 0.0 from the trail's start to end.
It should produce something like:
Multiple trail evolvers
We're now going to add an animated fire trail to represent the burning debris.
This will take the form of a second CParticleEvolver_Spawner.
!! IMPORTANT !! There is a common pitfall you must be aware of when adding multiple trail evolvers:
Failing to specify a different partial interval field.
Note that this is not an issue anymore in Popcorn-SDK v1.5.3 and above, the runtime now automatically patches the particle declaration with the necessary fields if they are not specified manually. you can skip this whole section directly down to Burning source & flipbook evolver.
More details coming. First, let's just try to add a second trail, to see in practise what happens. Also change the spawn properties of the layer to make a fountain of trails to get a better view:
Adding the second evolver immediately produces a second trail, using the default rendering. It looks like it's almost working:
But if we look closer, we can spot problems. The trails aren't regular. they seem to be randomly broken:
If we look at the editor's log (in the content browser window), we'll see a warning (here, l.330):
The 'PartialSpawnInterval' field is a hidden field created by the CParticleEvolver_Spawner for its internal workings.
If you go and have a look at the list of particle fields, you can see that field at the bottom of the list:
Here, the color-coding of the fields tell us this field (as well as 'PrevPosition', also needed by the evolver spawner), is a hidden field. This means, we can't access it inside scripts.
This field is where the evolver keeps track of the movement fraction left at the end of each frame. It is what ensures that trails are rock-solid and regular across frames, no matter what the framerate is.
During a frame, a trail can spawn multiple particles, based on the frame time, and on the spawn interval:
If the trail spawner didn't keep track of what fraction of the spawn interval was left at the end of each frame, it would produce an incorrect trail,
with particles always spawned at the same point at the beginning of the frame:
In order to avoid that, the evolver uses the 'PartialSpawnInterval' field to keep track of the interval's fraction left at the end of each frame, to be able to perform correct spawn computations in the next frame:
Therefore, when two trail evolvers use the same partial-interval field, The following happens:
- Frame 1
- Trail-1 reads '
PartialSpawnInterval', spawns particles. So far so good. spawns are correct
- Trail-1 stores its final interval fraction for the next frame back in '
PartialSpawnInterval' now contains Trail-1's fraction
- Trail-2 reads '
PartialSpawnInterval', and instead of getting its own fraction from the previous frame, it gets Trail-1's fraction. This errorneously shifts the spawns.
- Trail-2 stores its final interval fraction back in '
PartialSpawnInterval', overwriting Trail-1's fraction.
PartialSpawnInterval' now contains Trail-2's fraction
- Trail-1 reads '
- Frame 2
- Trail-1 reads '
PartialSpawnInterval', it gets Trail-2's fraction from the previous frame, instead of its own, spawns particles. incorrectly...
- Trail-1 reads '
Obviously, the same goes on with 2, 3, 4, and more trails.
The solution when using multiple trail evolvers in the same layer is to specify a different spawn interval field for the 2nd, 3rd, etc.. spawners:
When checking back in the particle fields, you can see the second spawner has correctly created the new spawn interval field
When looking back at the trails, there should be no more interval errors:
NOTE : This only happens with trails in the SAME EVOLVE STATE ! There is no problem whatsoever in an effect that has multiple layers with a trail in each layer, as the particles are different, and each trail gets its own spawn interval inside each particle.
Burning source & flipbook evolver
Now that we've got our second trail working, time to make it look like fire !
Set-up the billboard renderer like for the smoke.
IMPORTANT : Selecting the same properties, material and textures will ensure both trails will be rendered in the same batch, meaning the particles will have correct sorting order. Otherwise, the renderer is forced to use two batches and make two draw-calls, as the particles would have different materials. And this does not produce correct sorting.
We're going to use the animated flame texture in the "BlastPackExtended.dds" atlas.
We could animate the sub-sprites by manually changing the 'TextureID' field in-script, like in previous tutorials.
But we'll rather use the more simple FlipBook evolver: CParticleEvolver_FlipBook.
The flipbook evolver expects the first and last texture IDs.
You can get those by selecting the billboard renderer, and hovering your mouse over the rects in the atlas viewer. The TextureID is shown in the top-left corner:
You should now see the sprites animating along the trail, giving the false impression of a non-animated, fading trail:
To improve the visuals of the trail, add a rotation evolver and tweak the Rotation, Life, and Velocity values:
Note that doing:
Velocity = rand(0, 0.5)._0x0;
is equivalent to:
Velocity = float3(0, rand(0, 0.5), 0);
You could also write (although a bit slower to compute at runtime):
Velocity = rand(float3(0), float3(0, 0.5, 0));
Finally, add Color and Size curves to further tweak the flames, as well as an evolve script to apply a color coeff:
The final spawn script of the flames looks like:
IMPORTANT : in the above example, extreme color intensities are used. With default editor settings, they will produce HDR glow in the viewport. However, note that if your final game does not have that kind of post-effect, the FX you create can have a very different look, and you'll need to use textures with baked-in glow.
To see what it looks like, you can selectively disable PK-Editor's post-effects by clicking on the post-effect button in the viewport's toolbar:
Screenshot of the final effect: