Affectors

A ParticleAffector applies a per-frame force or transformation to every particle in the enclosing ParticleSystem. Its behaviour is driven by the compute material assigned to its Affector Material slot. Stock affector materials live in ParticlesAssetPackage/Materials/Particles/: import the ones you need and assign them to one or more ParticleAffector nodes.

This page catalogs the stock affectors and documents how they compose.

Order of application

Each frame, particles pass through the affectors in a fixed order before integration:

  1. Each emitter’s own Self Affect Material, if any, runs over that emitter’s particles.

  2. All ParticleAffector children of the ParticleSystem run, in tree order, over every live particle regardless of which emitter spawned it.

  3. Positions and velocities are integrated using the ParticleSystem integration method (Euler or Verlet).

Two consequences:

  • The order of ParticleAffector children matters when one affector reads state another has just written (for example, a VelocityClamp that caps the result of stacked forces must sit below them).

  • An affector added via SelfAffectMaterial is scoped to a single emitter; a separate ParticleAffector child affects every emitter in the system.

Per-affector transform and falloff

Every affector shader can read the affector node’s world transform via getAffectorTransform(). Moving the node repositions its region of influence. Most stock affectors also expose a Radius uniform that controls a spatial falloff centred on that transform:

  • Radius = 0: the affector applies globally, with uniform strength regardless of position.

  • Radius > 0: the affector’s strength fades from full at the centre to zero at the sphere surface.

Affectors whose strength fades across the sphere share one falloff helper, sphereFalloff(distance, radius, falloffExponent) (in Particles/affector_falloff.glsl), so the convention is uniform across the catalog. (VelocityClamp is the exception: its Radius is a binary in/out gate, since a speed clamp either applies or it doesn’t.) The shape of the fade is tunable with a Falloff Exponent uniform on an author-friendly [-1, 1] scale that the helper remaps into the actual power in log space:

  • 0 — linear ramp from full strength at the centre to zero at the edge.

  • negative — drops off rapidly near the centre, then tapers slowly toward the edge.

  • positive — stays near full strength across most of the radius, then falls sharply at the edge. ~1 approximates a hard cutoff at the sphere surface.

Falloff Exponent is ignored when Radius = 0 (the affector is unbounded). Authoring custom affectors that consume sphereFalloff() keeps them composable with the stock set.

Acceleration vs force: addParticleAcceleration vs addParticleForce

Affectors mutate particles through accessor helpers. Two of them (addParticleAcceleration and addParticleForce) choose the physical model:

  • addParticleAcceleration(index, a): adds a directly to the per-particle acceleration accumulator. Mass-independent: every particle gets the same kinematic effect regardless of its mass. Right for body-force effects like gravity or buoyancy.

  • addParticleForce(index, F): adds F / mass. Mass-aware: heavier particles respond less to the same force. Right for aerodynamic / fluid-interaction effects like wind or drag.

Two stock affectors illustrate the two sides:

  • Gravity: acceleration. Calls addParticleAcceleration(index, vec3(0, -9.8, 0)). Every particle falls at -9.8 m/s² regardless of mass, matching real-world gravity.

  • Wind: force. Calls addParticleForce(index, DragFactor * (windVelocity - velocity)). After the helper’s mass-divide, water (mass ~10) drifts sluggishly toward the wind velocity while smoke (mass ~1) snaps to it.

The remaining stock force affectors (Accelerate, Directional, PointAttractor, Buoyancy, CurlNoise) call addParticleAcceleration: their math defines a direct kinematic contribution. Drag calls addParticleForce like Wind, since the deceleration scales with mass.

When authoring a custom affector, pick the helper that matches the physical intent. Particles with mass = 1 (the default) hide the distinction: addParticleForce and addParticleAcceleration produce identical motion when every particle has unit mass.

Force affectors

Gravity

Applies a constant downward acceleration (-9.8 m/s² along world -Y). Ships with no uniforms: copy and modify the shader if you need a different direction or magnitude, or use Accelerate for a configurable equivalent.

Accelerate

Applies a constant acceleration along a user-supplied direction, optionally bounded by a sphere of influence. The configurable, mass-independent generalisation of Gravity: a non-world-down gravity, a steady wind-like push, or a thruster boost.

The strength is encoded as a Direction vector plus a signed scalar Magnitude rather than a single vec3. Magnitude is a scalar, so it animates and binds cleanly, and animating it through zero sweeps smoothly between acceleration and deceleration. Direction is authored in the affector node’s local frame and transformed into particle-system space, so rotating the node steers the effect; a zero vector is treated as the node’s local +Y. Like Gravity, the acceleration is mass-independent (it calls addParticleAcceleration).

Key properties: Direction, Magnitude (signed), Radius, Falloff Exponent.

Wind

Drags particles along the affector’s local +Z at a configurable speed, with smooth height-based fade-in, optional turbulence, and optional gusts.

Key properties: Wind Speed (km/h), Drag Factor (medium density), Ground Level / Full Wind Height (height ramp), Turbulence Strength, Turbulence Frequency, Gust Amplitude, Gust Frequency.

Directional

Applies a uniform force along the affector node’s local +Z, with Radius falloff. Use when you want a constant-direction push without the full Wind simulation.

Key properties: Directional Strength, Radius, Falloff Exponent.

PointAttractor

Pulls particles toward the affector node’s origin with a force that scales with Strength and falls off across Radius.

Key properties: Strength, Radius, Falloff Exponent.

Buoyancy

Applies an upward force that decays with height, combined with turbulence to produce smoke/lift-style motion. Canonical affector for smoke plumes.

Key properties: Max Buoyancy, Buoyancy Falloff, Turbulence Strength, Turbulence Frequency.

CurlNoise

Adds a divergence-free curl-noise velocity field: produces swirling, eddy-like motion that keeps particles from bunching up.

Key properties: Strength, Frequency (spatial), Time Scale, Radius, Falloff Exponent.

Damping and clamping

Drag

Subtracts a fraction of each particle’s velocity every frame, simulating resistance proportional to Drag Coefficient.

Key properties: Drag Coefficient, Radius, Falloff Exponent.

VelocityClamp

Caps particle speed at Max Speed. Useful as the last affector in a stack to prevent runaway velocities from compounding forces.

Key properties: Max Speed, Radius.

TimeDilation

Scales per-particle simulation time within Radius. The dt scale blends from unchanged at the sphere surface to Dilation Factor at the centre, following the falloff curve (dt *= mix(1, DilationFactor, sphereFalloff(...))). Dilation Factor below 1 slows particles down (0 freezes them); above 1 speeds them up. Use for slow-motion or accelerated regions. Set Falloff Exponent to ~1 for a hard-edged region.

Key properties: Dilation Factor, Radius, Falloff Exponent.

Conditional affectors

KillZone

Kills particles inside a spherical region with a per-frame probability that follows the falloff curve: certain death at the centre, tapering to a graceful chance of survival near the edge. Combine with a PointAttractor to make an absorbing target, or place at map boundaries to retire escaped particles. Set Falloff Exponent to ~1 for an abrupt, hard-edged kill region.

Key properties: Radius, Falloff Exponent.

Stacking affectors

Multiple ParticleAffector children on a single ParticleSystem compose additively each frame. A typical outdoor effect stack:

ParticleSystem
├── ParticleEmitter
├── ParticleAffector        AffectorMaterial = Gravity
├── ParticleAffector        AffectorMaterial = Wind
├── ParticleAffector        AffectorMaterial = CurlNoise        (Strength = 1, Radius = 0)
└── ParticleAffector        AffectorMaterial = VelocityClamp    (MaxSpeed = 12)

Rules of thumb:

  • Put global forces (Gravity, Wind) first.

  • Put additive noise (CurlNoise, Buoyancy) after the primary forces so it perturbs the integrated motion rather than competing with it.

  • Put clamping/damping (VelocityClamp, Drag) last so the limits apply to the total.

  • Use Radius to localise an affector without duplicating the particle system.

Use SelfAffectMaterial on an individual emitter when only that emitter’s particles must receive a specific effect (e.g. a self-attraction term that must not be applied to a sibling subemitter’s particles).

See also