The particle graph describes the behavior of a particle. It is contained inside a layer.
Links between nodes represent particle data (Velocity, Position, Size, ..)
Wider lines means more data is transferred.
(for example a wire carrying a float3 will appear wider than a wire carrying a float).
The particle graph is a single graph which defines the behavior of a particle both when it is spawned, when it evolves during simulation, and when it is rendered.
In PopcornFX v2, there is no explicit per-particle data storage. You do not have to (and cannot) explicitly say you want to “store” or “load” a “float” value named “BurstSpeed” or “ColorScale”, or whatever. Instead, you will simply wire the value you computed to whatever node uses it.
The graph is read from left to right, and data flows in that direction. A node that produces some data will be on the left hand side of a wire, a node that consumes that data will be on the right hand side.
So typically, you will find initialization / spawn nodes on the left of the graph, renderers on the right, and nodes that update the particle values each frame in the middle.
Each node can have multiple input pins and output pins. It takes some values in from the left of the graph, performs some computations, and output results to the right.
Nodes also have properties. When you select a node, its properties will be shown in the propertygrid panel.
Nodes are color-coded based on the current visualization mode of the graph. You can quickly switch between different modes that highlight different important information. The base mode colors wires based on the type of data they carry, and nodes based on the function they have.
The bigger the memory footprint of the data a wire carries, the bigger the wire.
To have a properly running simulation, a single node is mandatory: The “SetLife” node
However, for the particle to do something useful, other nodes are usually needed, either renderers or event generators / triggers.
Each particle needs to have its life set (by default, particles have their life set to 0). Life can be set to 0, some non infinite value, or infinity. Setting a particle’s life is done by using the SetLife node:
Particles with finite life have a usable and useful property we call LifeRatio. LifeRatio is the particle’s normalized age, and allows to drive behaviors based on the particle’s life.
Particles with infinite life will always have a LifeRatio of zero, and never die unless the game engine explicitly kills all the particles of an effect instance (expensive operation, requires explicit handling).
In most cases, you will want your infinite particles to die when the effect instance is stopped.
If you use the “InfiniteLife” template, it will take care of this for you.
You can also give it a time-to-die value after the effect has stopped, and it will allow you to gracefully fade out your particles.
Without a renderer, the particle will not be displayed onscreen.
This might be fine in some cases, if the purpose of the particle is to emit events and trigger some other, visible particles.
As renderers are expecting worldspace coordinates, and as most simulation nodes work in worldspace, you will almost always want transform nodes to convert the initial positions, velocities, or other 3D quantities from local to worldspace. The transform nodes will properly take into account the position and orientation of the effect instance when spawned in the game engine, or the parent event’s transform payload, if present.
For more details, see the Concepts > Transforms page.
The PopcornFX graph is all about manipulating data. This section describes the different types of data you can use.
Scalars and vectors
Numeric values in PopcornFX graphs and scripts are vectors from 1 to 4 dimensions.
‘vectors’ are basically a list of values, here are a few examples:
- an RGBA color is a 4-dimensional vector, containing 4 components: red, green, blue, and alpha.
- a position in 3D space is a 3-dimensional vector, containing the X, Y, and Z coordinates.
- an UV Texture coordinate is a 2-dimensional vector.
- a life duration in seconds is a scalar (a 1-dimensional vector containing a single value: the life duration)
1-dimensional vectors are also called ‘scalars’. they are a simple, single number.
You can combine scalars and vectors in arithmetic operations, but you can’t combine vectors of two different dimensions. For example, you can add a scalar with a 3D vector, or two 3D vectors together, but you can’t add a 2D vector with a 3D vector.
Graph wires will see their thickness change based on the size of the vector they carry:
Floating point numbers
A ‘float’ value is a signed number, which can represent fractional values. For example “10.35” or “9” or “-0.75”
Floats come in 4 different flavors: ‘float’, ‘float2’, ‘float3’ and ‘float4’
They are 1D (scalar), 2D, 3D, and 4D float vectors respectively.
They are stored as 32-bits IEEE-754 floating-point numbers.
Wires carrying floats are displayed in an olive green:
Special floats: The ‘float’ type can also represent two special values ‘infinity’ and ‘-infinity’.
These are used to specify things like the lifetime of an particle that should never die during the simulation, or the length of a raycast that should have no end. It is also used as a return value by some functions to report a “not found” value. For instance when an intersection function that reports the distance of the closest hit on a given geometric shape will return ‘infinity’ to say it didn’t hit anything.
You can check if a value is finite by using the “isfinite()” script function, or the “isFinite” node. To check if a value is explicitly infinite or NaN, use the “isInf” or “isNaN” nodes
An ‘int’ value is a signed integer number, it cannot represent fractional values. For example “10” or “9” or “-1301”
Integers come in 4 different flavors: ‘int’, ‘int2’, ‘int3’ and ‘int4’
They are 1D (scalar), 2D, 3D, and 4D int vectors respectively.
They are stored as 32-bits numbers. The biggest value they can store is 2147483647, and the smallest is -2147483648.
They obey standard 32-bits signed integer arithmetic, meaning that adding ‘1’ to the largest integer 2147483647 will “wrap around” the range, and result in the value -2147483648 instead of 2147483648. Similarly, subtracting ‘10’ from the smallest integer -2147483648 will wrap-around and result in 2147483638 instead of -2147483658.
So special care must be taken when you use integers at the very limits of their ranges.
Wires carrying integers are displayed in teal green:
A ‘bool’ value can only represent two values: ‘true’ or ‘false’
Booleans come in 4 different flavors: ‘bool’, ‘bool2’, ‘bool3’ and ‘bool4’
They are 1D (scalar), 2D, 3D, and 4D bool vectors respectively.
They are typically the result of logical operations such as comparing if a value is lower than another.
Other higher level operations such as testing if a point is contained inside a volume also return boolean values.
Wires carrying booleans are displayed in a shade of green/turquoise:
Note: booleans and integers share the same node colors.
A value of type ‘orientation’ represents a 3D rotation.
It can be used to rotate vectors, orient meshes, or any kind of 3D rotation operation.
Wires carrying orientations are displayed in orange:
Orientations are internally represented as quaternions, which contains 4 float values, therefore the wires have the width of a float4 vector.
However, they are edited in the editor’s propertygrid as 3D euler angles.
Resources are another type of data. These come from sampler/resource nodes, and can represent curves, images, vector-fields, geometries (primitive shapes or triangle meshes), audio waveforms or spectrums, text buffers, animation paths, or event streams.
Wires carrying resources are displayed in white, resource nodes are purple:
Spatial data is used by the “spatial layers” feature, which is used for particle to particle interactions, and enables particles to insert custom data at a specific location in space, and other particles to perform various queries in this spatial datastructure.
There are two main “spatial” datatypes in the graph: Spatial layers, and Spatial payloads.
Wires carrying spatial layers are displayed in red, Wires carrying spatial payloads are displayed in a darker shade of purple:
Events are not really a type of ‘data’, but like regular data, they have a special color and wire display. All event wires are displayed as discontinuous lines.
Depending on the graph being viewed, their appearance will change based on realtime metrics captured from the effect, to give an idea of how many events are currently flowing through the wire.
Links between nodes are colored based on their type, here is a recap of the various wires you can find in a graph:
- Discretized datas (see Discretization)
- Spatial layer payloads
- Spatial layers
The nodegraph editor can display nodes using different color schemes, to help visualize different information.
The default color scheme colors nodes based on their type and high-level functionality:
Most nodes will be colored with the same color scheme as the data they are manipulating.
Arithmetic nodes will be colored like float and int data:
Sampler nodes will be colored purple, like resource data
Renderer nodes, which are used to render particles as billboards, ribbons, meshes, lights or sounds, are not specific to any given kind of data. They will be colored in a shade of yellow:
Template nodes, which encapsulate sub graphs and allow to easily reuse them, will appear blue by default:
(Note: this is the default color of template nodes, but you can specify which node color to use when you create a new template, so you will see “math” templates, “renderer” templates, “sampler” templates, etc..)
Script nodes, which allow to type custom expressions, will appear in a shade of blue:
Nodes in the particle graph are either executed at particle spawn, or during their lifetime. We call these execution rates:
- Rate once, those nodes will be executed once when the particle spawns
- Full rate, those nodes will be executed every frame, during the particle’s lifetime
- Render, those nodes are render related, and render particles based on input data
- Nodes that don’t have an execution rate (ie. resources)
Most of the time you do not have to manually specify an execution rate for nodes.
All nodes are in “Auto” rate by default. The PopcornFX runtime will try to figure it out and pick the most appropriate rate, depending on the rate of the node’s inputs. It will try to do the least amount of work possible during evolve, and optimize the overall execution of the graph.
The update rate colors shown in the nodegraph view displays the final resolved rate, as the runtime understands it. In the screenshot above, there are no explicit rates set in the graph. The “physics” template node internally contains a full-rate node to get the simulation timestep, which causes the rest of its computations to run at full rate.
Sometimes you will need to manually specify an explicit rate for a node, for example when using the “effect.age” template, or an attribute node to access one of the effect’s parameters, if you want to read this value every frame instead of once at spawn-time, you will need to switch from “Auto” to “Full”.
This replaces v1.x’s “Spawn script”, “Evolvers”, and “Renderers” sections.
There are some options in the settings that will change those colors to make them more readable for users with color vision deficiencies:
To enable this mode, go to “Edit >Editor preferences > General > Color Vision Deficiency”
Some values and node inputs have special semantics. These semantics give a hint to the nodegraph about the expected usage of that value, and allow the editor to use special widgets when displaying it, or the runtime to treat that values a certain way.
Two good examples are the “Color”, and “3D Coordinate” semantics:
The “Color” semantic can be applied to any 3D or 4D vector value, and will tell popcorn to use interpret it as a color. As a result, the editor will show a color-picker widget instead of the regular 3 or 4 numeric values.
You can still edit the exact values by hand using the color picker dialog.
- Constant node’s semantic is set to “Color”
- Value is now displayed a clickable color picker widget, displaying color’s RGB and Alpha
A 3D vector can be specified as being a “3D coordinate”. This helps creating axis-system independent effects by giving the runtime hints about which quantities are 3D coordinates, and should be flipped when the effect was authored in a Y-up axis system, but was loaded in a Z-up engine.
- Constant node’s semantic is set to “3D_Coordinate”
- Value is now displayed as an axis system independent 3D coordinate: this project is set to be in Y up, changing the target engine’s axis system in our project settings to a Z-UP axis system will change that value to (0, 0, 1).
A good rule of thumb is to always set that semantic on constant nodes when manipulating 3D coordinates (Euler angles, Positions).