[SOLVED] setting RenderState.BlendMode.Off on ParticleEmitter

Hi,
experimenting with particle effects, I noticed that by changing the default ‘BlendMode’ of the ‘particle material’ from Off to Alpha and then back to Off, something seems to break.

In short, setting the BlendMode.Off after changing it once, the effect no longer works correctly. Could this be a bug?

-

Here is the test case:

package com.test.main;

import com.jme3.app.SimpleApplication;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.system.AppSettings;

/**
 * 
 * @author capdevon
 */
public class Test_BlendModeIssue extends SimpleApplication {

	private ParticleEmitter emit;
	private float angle = 0;
	private float timer;
	private float refreshRate = 1f;
	private int mIndex = 0;
	private BlendMode[] modes = { BlendMode.Off, BlendMode.Alpha };

	/**
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		AppSettings settings = new AppSettings(true);
		settings.setResolution(800, 600);

		Test_BlendModeIssue app = new Test_BlendModeIssue();
		app.setShowSettings(false);
		app.setPauseOnLostFocus(false);
		app.setSettings(settings);
		app.start();
	}

	@Override
	public void simpleInitApp() {
		flyCam.setDragToRotate(true);
		flyCam.setMoveSpeed(20);

		viewPort.setBackgroundColor(ColorRGBA.DarkGray);

/* --not required
		DirectionalLight sun = new DirectionalLight();
		sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal());
		sun.setColor(ColorRGBA.White);
		rootNode.addLight(sun);
*/
		createParticleEmitter();
	}

	@Override
	public void simpleUpdate(float tpf) {
		timer += tpf;
		if (timer > refreshRate) {
			timer = 0;
			mIndex = (mIndex + 1) % modes.length;
			BlendMode bm = modes[mIndex];
			fpsText.setText("BlendMode: " + bm.toString());
			emit.getMaterial().getAdditionalRenderState().setBlendMode(bm);
		}

		angle += tpf;
		angle %= FastMath.TWO_PI;
		float x = FastMath.cos(angle) * 2;
		float y = FastMath.sin(angle) * 2;
		emit.setLocalTranslation(x, 0, y);
	}

	private void createParticleEmitter() {
		emit = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 300);
		Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
		mat.setTexture("Texture", assetManager.loadTexture("Effects/Smoke/Smoke.png"));
		emit.setGravity(0, 0, 0);
		emit.getParticleInfluencer().setVelocityVariation(1);
		emit.getParticleInfluencer().setInitialVelocity(new Vector3f(0, .5f, 0));
		emit.setLowLife(1);
		emit.setHighLife(1);
		emit.setImagesX(15);
		emit.setMaterial(mat);
		rootNode.attachChild(emit);

		System.out.println("Default BlendMode: " + mat.getAdditionalRenderState().getBlendMode());
	}
}

Edit:

Right, directional light does not affect the test.

1 Like

Also happens when setting it directly to Off without setting it to something else first.
The DirectionalLight doesn’t seem involved.

quickly tested with jme 3.3.2 stable, 3.4.1 stable
3.5.0-beta4 had problems finding liblwjgl.so

2 Likes

@capdevon and @1000ml:

Without knowing the root causes of these behaviors, it’s hard to decide whether they are bugs or not.

Since it’s been 3 days and the root causes are still (as far as I know) unknown, please open issues at GitHub for both behaviors:

  1. the effect looking different and
  2. inability to find “liblwjgl.so” (on which platform?)
1 Like

@capdevon does it happen also with other material types like Lighting.j3md or only happens with Particle.j3md?

Hi Stephen, here it is:

1 Like

I have not noticed any malfunctions with Lighiting.j3md

1 Like

I tried to understand more, in the Particle.j3md file, all RenderState{} blocks have BlendMode.AlphaAdditive by default.

The javadoc of the RenderState class suggests using the following modes for particle effects:

  • Additive
  • AlphaAdditive

It is probably correct that particle effects do not work correctly with BlendMode.Off

2 Likes

That makes sense. The additive blending modes are order-independent. For other modes, in order for blending to look good, the fragments need to be sorted back-to-front. Sorting lots of moving particles every frame is costly.

Now since the blending mode is initialized to AlphaAdditive, I think this is expected behavior.

What I find suspicious though, is that mat.getAdditionalRenderState().getBlendMode() doesn’t reflect the initial value. After creating a Material with Particle.j3md it returns BlendMode.Off.

2 Likes

I have some suspicions about it too, but i don’t know if this is a deliberate behavior.

1 Like

Where does the value get changed?

maybe here

2 Likes

I’ve analyzed issue 1724, and I think I know what’s going on. @capdevon please take a look.

3 Likes

Hi Stephen, sorry for the delay, but I’ve had problems with the pc these days. Thank you for the detailed analysis, the behavior is now clearer. Do you suggest closing the issue or leaving it open?

1 Like

Do you suggest closing the issue or leaving it open?

That depends what your needs are. As pointed out in the issue discussion, there’s no mechanism (short of reflection) to reset a RenderState back to its original state. So if you’ve got an application that needs that functionality, we might follow up with an enhancement PR.

On the other hand, if it’s sufficient to return the emitter back to its original appearance, the following should suffice

   emit.getMaterial().getAdditionalRenderState().setBlendMode(BlendMode.AlphaAdditive);

and we can close both the issue and this topic.

I’d like your input.

I have written a test case which shows a lot more information.
If I understand correctly, the additional RenderState allows you to permanently overwrite the BlendMode defined in the j3md file. Until this happens, the BlendMode value and the one defined in the Technique block of the j3md file may differ at runtime. (see example)
Honestly, I can’t think of a case where it might be necessary to restore the initial state. My goal was to tweak the BlendMode at runtime to see the visual effect of each mode. There are no bugs in this and everything works fine. Maybe we can think about it in the future. What do you think?

New test case:

package com.test.main;

import com.jme3.app.SimpleApplication;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.material.MaterialDef;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.material.Technique;
import com.jme3.material.TechniqueDef;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.system.AppSettings;

/**
 *
 * @author capdevon
 */
public class Test_BlendModeIssue extends SimpleApplication implements ActionListener {

    private ParticleEmitter emit;
    private float angle = 0;
    private int mIndex = 0;
    private BlendMode[] modes = BlendMode.values();
    private BitmapText hud;

    /**
     *
     * @param args
     */
    public static void main(String[] args) {
        AppSettings settings = new AppSettings(true);
        settings.setResolution(800, 600);

        Test_BlendModeIssue app = new Test_BlendModeIssue();
        app.setShowSettings(false);
        app.setPauseOnLostFocus(false);
        app.setSettings(settings);
        app.start();
    }

    @Override
    public void simpleInitApp() {
        flyCam.setDragToRotate(true);
        flyCam.setMoveSpeed(20);
        viewPort.setBackgroundColor(ColorRGBA.DarkGray);

        hud = createTextUI(ColorRGBA.Blue, 20, 15);

        createParticleEmitter();

        inputManager.addMapping("NextBlendMode", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addListener(this, "NextBlendMode");
    }

    private void createParticleEmitter() {
        emit = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 300);
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
        mat.setTexture("Texture", assetManager.loadTexture("Effects/Smoke/Smoke.png"));
        emit.setGravity(0, 0, 0);
        emit.getParticleInfluencer().setVelocityVariation(1);
        emit.getParticleInfluencer().setInitialVelocity(new Vector3f(0, .5f, 0));
        emit.setLowLife(1);
        emit.setHighLife(1);
        emit.setImagesX(15);
        emit.setMaterial(mat);
        rootNode.attachChild(emit);
    }

    @Override
    public void simpleUpdate(float tpf) {

        BlendMode blendMode = emit.getMaterial().getAdditionalRenderState().getBlendMode();
        fpsText.setText("AdditionalRenderState.BlendMode: " + blendMode);

        printTechniqueDefs();

        angle += tpf;
        angle %= FastMath.TWO_PI;
        float x = FastMath.cos(angle) * 2;
        float y = FastMath.sin(angle) * 2;
        emit.setLocalTranslation(x, 0, y);
    }

   private void printTechniqueDefs() {
        
        StringBuilder sb = new StringBuilder();
        MaterialDef matDef = emit.getMaterial().getMaterialDef();
        
        for (String name : matDef.getTechniqueDefsNames()) {
            for (TechniqueDef technique : matDef.getTechniqueDefs(name)) {

                BlendMode bm = null;
                if (technique.getRenderState() != null) {
                    bm = technique.getRenderState().getBlendMode();
                }

                sb.append("TechniqueDef=");
                sb.append(name);
                sb.append(", BlendMode=");
                sb.append(bm);
                sb.append("\n");
            }
        }

        Technique tech = emit.getMaterial().getActiveTechnique();
        String activeTech = "ActiveTechnique: ";
        if (tech != null) {
            activeTech += tech.getDef().getName();
        }

        hud.setText(sb.toString() + activeTech);
    }

    private BitmapText createTextUI(ColorRGBA color, float xPos, float yPos) {
        BitmapFont font = assetManager.loadFont("Interface/Fonts/Console.fnt");
        BitmapText bmp = new BitmapText(font);
        bmp.setSize(font.getCharSet().getRenderedSize() * 1.2f);
        bmp.setLocalTranslation(xPos, settings.getHeight() - yPos, 0);
        bmp.setColor(color);
        guiNode.attachChild(bmp);
        return bmp;
    }

    @Override
    public void onAction(String name, boolean isPressed, float tpf) {
        if (name.equals("NextBlendMode") && isPressed) {
            mIndex = (mIndex + 1) % modes.length;
            BlendMode blendMode = modes[mIndex];
            emit.getMaterial().getAdditionalRenderState().setBlendMode(blendMode);
        }
    }
}

1 Like

What do you think?

I think we can close issue 1724. Thanks.

1 Like

I agree with you. Thanks for the support. :wink:

1 Like