Because of issues with perspective and depth, the 2D particle effects are not suitable for 3D applications. Additionally, the 3d particle effects take full advantage of movement through 3d space, allowing a wide variety of dynamic graphical effects.

3D Particle Effects - 图1

Flame - 3D Particle Editor

Much like their 2D cousins, 3D particle effects can be edited with a GUI editor included in libgdx. The editor is called Flame, and can be downloaded here.

Particle Effect Types

There are 3 different kinds of 3D particle effects:

  • Billboards
  • PointSprites
  • ModelInstance

Billboards are sprites that always face the camera (the Decal class in libGDX is essentially a billboard).

PointSprites draw a sprite to a single 3d point. They are simpler than billboards, but more efficient. More information about point sprites in OpenGL: http://www.informit.com/articles/article.aspx?p=770639&seqNum=7

ModelInstances are familiar to you if you have done any 3D work in libgdx. They are instances of 3D models. Not surprisingly, this is the most taxing type of particle effect in terms of performance.

Due to those differences, each particle effect type has its own dedicated batch renderer: BillboardParticleBatch, PointSpriteParticleBatch, ModelInstanceParticleBatch.


Using 3D Particle Effects

The easiest way to use 3D particle effects is by taking advantage of the ParticleSystem class, abstracting away various details and managing them for you. First we will create the batch of the type(s) we wish to use, then create the ParticleSystem. In this case, we are going to use PointSprites.

For a more in depth look at how to use 3d particles programmatically, take a look at the test class.

IMPORTANT: When you import the ParticleEffect class into your IDE, make sure you do not accidentally import the 2D effect ParticleEffect class. They share the same name, but have different import paths. You are looking for: com.badlogic.gdx.graphics.g3d.particles.ParticleEffect

Step 1: Create Batches and ParticleSystem

  1. ParticleSystem particleSystem = new ParticleSystem();
  2. // Since our particle effects are PointSprites, we create a PointSpriteParticleBatch
  3. PointSpriteParticleBatch pointSpriteBatch = new PointSpriteParticleBatch();
  4. pointSpriteBatch.setCamera(cam);
  5. particleSystem.add(pointSpriteBatch);

Step 2: Load Effects Using AssetManager

Now we need to load our particle effects that we have created using the Flame GUI editor. First, create a ParticleEffectLoadParameter to pass to the asset manager when loading. Then the assets may be loaded.

  1. AssetManager assets = new AssetManager();
  2. ParticleEffectLoader.ParticleEffectLoadParameter loadParam = new ParticleEffectLoader.ParticleEffectLoadParameter(particleSystem.getBatches());
  3. assets.load("particle/effect.pfx", ParticleEffect.class, loadParam);
  4. assets.finishLoading()

Step 3: Add Loaded ParticleEffects to the ParticleSystem

  1. ParticleEffect originalEffect = assets.get("particle/effect.pfx");
  2. // we cannot use the originalEffect, we must make a copy each time we create new particle effect
  3. ParticleEffect effect = originalEffect.copy();
  4. effect.init();
  5. effect.start(); // optional: particle will begin playing immediately
  6. particleSystem.add(effect);

Your game most likely will have many particle effects, either at once or over time during game play. You really don’t want to make a new copy of the particle effect each time you create an object or graphical effect that needs it. Instead, you should pool the effects to avoid new object creation. You can read more about Pooling in this wiki or the libGDX Pool class documentation.

Here is an example of a Pool:

  1. private static class PFXPool extends Pool<ParticleEffect> {
  2. private ParticleEffect sourceEffect;
  3. public PFXPool(ParticleEffect sourceEffect) {
  4. this.sourceEffect = sourceEffect;
  5. }
  6. @Override
  7. public void free(ParticleEffect pfx) {
  8. pfx.reset();
  9. super.free(pfx);
  10. }
  11. @Override
  12. protected ParticleEffect newObject() {
  13. return sourceEffect.copy();
  14. }
  15. }

Note that we reset the particle when it is freed, not during obtain. This avoids a NullPointerException that occurs because of how the ParticleSystem works.

Step 4: Rendering our 3D Particles Using the ParticleSystem

A ParticleSystem must update and draw its own components, then be passed to a ModelBatch instance to be rendered to the scene.

  1. private void renderParticleEffects() {
  2. particleSystem.update(); // technically not necessary for rendering
  3. particleSystem.begin();
  4. particleSystem.draw();
  5. particleSystem.end();
  6. modelBatch.render(particleSystem);
  7. }

You can also translate and rotate the effect. Depending on how your engine works you might want to use a specific matrix that is reset to identity on changes or only add the delta transformation/rotation.

  1. private void renderParticleEffects() {
  2. targetMatrix.idt();
  3. targetMatrix.translate(targetPos);
  4. effect.setTransform(targetMatrix);
  5. particleSystem.update(); // technically not necessary for rendering
  6. particleSystem.begin();
  7. particleSystem.draw();
  8. particleSystem.end();
  9. modelBatch.render(particleSystem);
  10. }

or

  1. private void renderParticleEffects() {
  2. effect.translate(deltaPos);
  3. particleSystem.update(); // technically not necessary for rendering
  4. particleSystem.begin();
  5. particleSystem.draw();
  6. particleSystem.end();
  7. modelBatch.render(particleSystem);
  8. }

Stop New Particle Emission, But Let Existing Particles Finish Playing

It is a little bit more complicated to do this in the 3D Particle System:

  1. Emitter emitter = pfx.getControllers().first().emitter;
  2. if (emitter instanceof RegularEmitter) {
  3. RegularEmitter reg = (RegularEmitter) emitter;
  4. reg.setEmissionMode(RegularEmitter.EmissionMode.EnabledUntilCycleEnd);
  5. }

Simple examples

A simplified example of the above GdxTest.java can be found here.