Viro's particle system enables developers to create and configure quad emitters for building complex and intricate particle effects. Some examples are smoke, rain, confetti, and fireworks. This development guide will go through several examples of effects that can be created.
ParticleEmitter is a particle factory that creates, contains, and recycles a pool of particles. Groups of particle emitters can be used in conjunction with one another to create a composite effect. For example, a campfire may include a flame emitter, a smoke emitter, and an ember or sparks emitter.
Below is a code snippet of a basic ParticleEmitter, one that creates a simple 'Fountain' like effect.
Material snowMaterial = new Material(); snowMaterial.setTexture(snowTexture); Surface particle = new Surface(0.1f, 0.1f); particle.setMaterials(Arrays.asList(snowMaterial)); ParticleEmitter emitter = new ParticleEmitter(context, particle); emitter.setDuration(2000); emitter.run(); Node node = new Node(); node.setPosition(new Vector(0, 4.5f, 0)); node.setParticleEmitter(emitter);
As shown above, there are three steps to creating a particle effect:
- Provide a Surface representing the particles to emit.
- Configure a ParticleEmitter.
- Attach the ParticleEmitter to a Node and start the emitter.
There are several ways to configure a ParticleEmitter. First, shown below are basic properties of the emitter itself. These include what image to emit, when to emit, and how long to emit for.
Passed in through the constructor. The Surface is the visual representation of a single quad particle. You can configure this Surface with any Material and Texture.
delay / duration / loop / run
These parameters control how long to wait before starting an emission cycle, how long the emission cycle should run for, and if the cycle should be repeated continually.
The second set of configuration attributes have have to do with spawn behavior: the spawn rate, spawn volume, and burst intervals, if any.
How fast an emitter produces particles is determined by either emissionRatePerSecond or emissionRatePerMeter (in which the particle output is proportional to how fast the emitter itself is moving). Depending on the effect you wish to create, you may decide to one or both properties. For example, in a situation where steam particles are emitted from a train upon movement, emissionRatePerMeter is the sensible choice. This is also true if you, say, have a spaceship that emits more smoke as it accelerates.
How long a particle lives for (or how fast a particle dies) is defined by particleLifetime, in milliseconds. Specifying particleLifeTime and particleEmissionRate determines an eventual steady state. In this steady state, there will be a roughly constant number of existing, spawned particles for the emitter.
A quick "jet of particles" that die quickly, possibly resulting in emitter starvation.
Particles are produced slowly, but are long-lived, eventually resulting in emitter starvation.
A quick "jet of particles" that are long-lived. Quickly results in emitter starvation.
Particles are produced quickly and die quickly.
Particles are produced slowly, and live for a long time. (Possible performance cost)
Lots of particles, produced quickly, that live for along time. (High performance cost)
To prevent an unbounded number of particles, the ViroParticleEmitter also provides a
maxParticles = emittedParticlesPerSecond + emittedParticlesPerMeter + emissionBurst
This number caps the number of live particles per emitter at any point in time. It is important to note that the more particles your scene has, the higher the performance cost. It is recommended to keep
maxParticles low when possible.
In addition to emitting particles at a constant rate, certain particle effects may require the ability to instantaneously spawn n number of particles all at once in a single burst. Fireworks, sparks, and explosions are some examples. To specify a burst use the
setEmissionBursts(List<EmissionBurst>) method. Here you can specify a time (or distance traveled) at which to burst a number of particles, in repetition if needed. These bursts are done in conjunction with emission rates, and are also subjected to the same
By default, an emitter spawns particles at the location of the emitter. This is useful for effects tied to a single source, like smoke rising from a chimney. However, other effects may require more complex spawn volumes; for example, spawning snow or rain over an area of land.
setSpawnVolume(SpawnVolume, boolean) method, can specify the shape within which to spawn particles. Supported shapes are SpawnVolumeBox with width, height, and length, SpawnVolumeSphere with radius, SpawnVolumeEllipsoid with an x,y,z ellipsoid length, and SpawnVolumePoint. All particles will spawn in a uniformly distributed pattern within the shape.
Finally, there may be effects that require particles to spawn on the surface of a shape, rather than within it. For example, fireworks require particles to be spawned on the surface of a sphere. To achieve this effect, set the
spawnOnSurface boolean to true. Particles will be spawned in a uniformly distributed fashion on the surface of the specified shape.
The visual traits of particles may change over time. For example, firework particles may change in color, while smoke particles may grow in size before disappearing into the sky. These visual behaviors can be created through various particle visual properties.
There are four types of particle visual properties:
setRotationModifier, and setColorModifier`. Each of these methods takes a ParticleModifier as its sole argument. To configure a ParticleModifier:
Provide an initial range of values. For each particle a value will be chosen from a uniform distribution across this range. If the range consists of two identical values, there will be no randomization. The initial range is set in the constructor of the ParticleModifier.
If the property is dynamic (meaning it changes over the course of the particle's lifetime), then it needs a factor against which to interpolate the change. This can be either
ParticleEmitter.Factor.TIME(e.g. making a particle scale down over time) or
ParticleEmitter.Factor.DISTANCE(e.g. making a particle scale down as it moves away).
Finally, again only if the property is dynamic, provide a list of interpolation data to describe the change over time. Each item in the interpolation list consists of the endValue you wish to interpolate toward, and the interval within which to interpolate. Interval values can start and end at any point in the particle's lifecycle, as long as they do not overlap each other.
An interpolation example is provided below:
Material snowMaterial = new Material(); snowMaterial.setTexture(snowTexture); Surface particle = new Surface(0.1f, 0.1f); particle.setMaterials(Arrays.asList(snowMaterial)); ParticleEmitter emitter = new ParticleEmitter(context, particle); emitter.setDuration(2000); emitter.run(); // Initial range [1.0, 1.0], so all particles will have opacity 1.0 initially ParticleModifierFloat opacityModifier = new ParticleModifierFloat(1.0f, 1.0f); opacityModifier.setFactor(ParticleEmitter.Factor.TIME); opacityModifier.addInterval(500, 0.8f); // Drop opacity to 0.8 after 500 ms opacityModifier.addInterval(1000, 0.0f); // Drop opacity to 0.0 after 1 second emitter.setOpacityModifier(opacityModifier);
In the snippet above, we are manipulating the opacity of our particles by slowly fading them out to 0.8 over the first 500 milliseconds, and then we fade them completely to 0.0 at 1000 milliseconds.
By default, particle emitters radiate particles in a fountain-like fashion. This movement behavior is also configurable. Whether it be falling snow, rising smoke, or exploding fireworks, these physics-specific attributes describe how a particle moves over the course of its lifetime.
The first two properties --
setAccelerationModifier -- are straightforward: they define each particle's initial velocity or constantly applied acceleration. Setting these values will override the emitter's "fountain-like" default behavior. As before, you can provide a range of two identical values to eliminate randomization. Or you can provide a lower and upper bound.
Falling snow with velocity and acceleration
A variety of affects can be produced with these two properties. For example, falling, swaying snow can be achieved with a fixed acceleration of -9.81 and a randomized initial horizontal velocity. A similar configuration can be used to make steam particles emanate from a kettle. Note also that these physics properties can be used in conjunction with the animation system. For example, to make a tornado, radiate particles upward in a fountain-like fashion with fixed velocity, then rotate the node clockwise about the Y axis with an animation.
When moving particles, the
fixedToEmitter property controls the reference point to use when computing the particle's appearance and movement. When true, Viro uses the emitter's current position; when false, Viro uses each particle's individual spawn location. Under
fixedToEmitter = false, particles are not "locked" to the emitter; they can float away. For example, smoke particles from a moving train would continue floating upward from the location they were spawned. Under
fixedToEmitter = true, the smoke particles would be "locked" to the train's emitter: they would always move with the train in reference to the emitter's location.
A moving "train" with smoke particles (fixedToEmitter: false)
Impulsive forces, to create effects like explosions, are also possible. For example, fireworks require an initial impulse on every particle, each in a different direction from the detonation point. This behavior can be achieved with the
setExplosiveImpulse(float impulse, Vector position, float decelerationPeriod) method. This impulse is specified in newton seconds. The position parameter (relative to the particle emitter) indicates the center of the detonation: the closer particles are to the detonation point, the greater their explosive force.
Fireworks in NYC!
Finally, the behavior of an explosion may also be tuned with the
decelerationPeriod parameter. This effectively enables developers to apply a "dampening deceleration effect" against the explosive impulse force, in order to slow down the explosion. The
decelerationPeriod defines the timeframe within which the particles will decelerate from their initial velocity to 0.0 m/s. This is particularly useful for fireworks, which explode outward then slow down / are dampened after a specific length of time or spherical size. An interesting side effect of this property is that the deceleration dampener effect is still applied even after the
decelerationPeriod, if the particle is still alive. That is to say, the particle would continue to decelerate in the opposite direction of the explosion, creating a "gravitational attraction" effect.
It is also important to note that invoking
setExplosiveImpulse automatically invalidates any initial velocity setting, as an explosion directly manipulates an object's velocity. Likewise, the
decelerationPeriod will override any acceleration setting.