[Solved] Bloom Filter applied to near view port does not create transparent background

The problem:

I have many view ports overlapped with different near / far frustums. A bloom filter is applied to each view port. The problem is that since the final image that the bloom creates does not have a black background with a zero alpha, it paints over all the other further view ports. So in the end, I can only see the objects in my closest view port. Is there any way to fix this artifact? I of course tried to just put a bloom filter on the furthest view port but it seems that bloom is not run after all the view ports are rendered so only the farthest view port’s objects are bloomed in that case.

If all your viewports use the same FrameBuffer and simply draw one over another then you should only need one bloom filter on the last viewport rendered. If you need to take advantage of the “glow” feature which renders the scene again with a forced “Glow” technique then you would need to re-render all of the viewports in order using the “Glow” technique and the same assigned FrameBuffer.

Oh yeah, I forgot the filter is just going to assign a different output FrameBuffer to the viewport it’s assigned to so if you assign just one filter to the last viewport then you’ll only see that viewport.

I used the postQueue(RenderQueue rq) method in one of my ScreenController classes to do my glow filter which is applied to multiple cameras:

glowQuad.updateGeometricState();
quad.updateGeometricState();
if (UI.getState() == UI.STATE_GAME && Opts.glow > Opts.GLOW_OFF) {
    renderManager.getRenderer().setBackgroundColor(ColorRGBA.Black);            
    renderManager.getRenderer().setFrameBuffer(glowBuff);
    renderManager.getRenderer().clearBuffers(true, true, true);
    
    UI.getGameCam().getMidCam().resize(glowBuff.getWidth(), glowBuff.getHeight(), true);
    UI.getGameCam().getForeCam().resize(glowBuff.getWidth(), glowBuff.getHeight(), true);
    
    renderManager.setForcedTechnique("Glow");
    renderManager.renderViewPortRaw(UI.getGameCam().getMidPort());
    renderManager.getRenderer().clearBuffers(false, true, true);
    renderManager.renderViewPortRaw(UI.getGameCam().getForePort());
    renderManager.setForcedTechnique(null);
    
    UI.getGameCam().getMidCam().resize(w, h, true);
    UI.getGameCam().getForeCam().resize(w, h, true);
    
    glowQuad.setMaterial(vBlur);
    renderManager.setCamera(glowCam, true);
    renderManager.renderGeometry(glowQuad);
    
    glowQuad.setMaterial(hBlur);
    renderManager.setCamera(glowCam, true);
    renderManager.renderGeometry(glowQuad);
    
    if (Opts.glow == Opts.GLOW_HIGH) {
        glowQuad.setMaterial(vBlur);
        renderManager.setCamera(glowCam, true);
        renderManager.renderGeometry(glowQuad);

        glowQuad.setMaterial(hBlur);
        renderManager.setCamera(glowCam, true);
        renderManager.renderGeometry(glowQuad);
    }
    
    //Set the renderer back to default background color
    renderManager.getRenderer().setBackgroundColor(ColorRGBA.BlackNoAlpha);
    quad.setMaterial(glowMat);
    
    //Set the renderer back to the main frame buffer and render the final quad using a /MatDefs/Common/Misc/Unshaded material with getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Additive) set so it renders adds the glow to the original scene.
    renderManager.getRenderer().setFrameBuffer(defaultBuffer);
    renderManager.setCamera(cam, true);
    renderManager.renderGeometry(quad);
}

I’m using a class that extends NiftyJmeDisplay, because I’m using NiftyGUI, and instantiating that when I create the Nifty display so my constructor looks something like this:

public NiftyDiemDisplay(
    final AssetManager assetManager,
    final InputManager inputManager,
    final AudioRenderer audioRenderer,
    final ViewPort viewport,
    final FrameBuffer defaultBuffer) {
    super(assetManager, inputManager, audioRenderer, viewport);
    
    this.defaultBuffer = defaultBuffer;
    
    cam.resize(w, h, true);
    quad.setWidth(w);
    quad.setHeight(h);
    
    vBlur = new Material(assetManager, "MatDefs/Post/VGaussianBlur.j3md");
    
    hBlur = new Material(assetManager, "MatDefs/Post/HGaussianBlur.j3md");
    
    glowMat = new Material(assetManager,
            "Common/MatDefs/Misc/Unshaded.j3md");
    glowMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Additive);
    
    glowBuff = new FrameBuffer((int)FastMath.ceil(w * 0.5f),
            (int)FastMath.ceil(h * 0.5f), 0);
    glowBuff.setColorBuffer(Image.Format.RGBA8);
    glowBuff.setDepthBuffer(Image.Format.Depth);

    glowTex = new Texture2D(glowBuff.getWidth(),
            glowBuff.getHeight(), Image.Format.RGBA8);
    glowTex.setWrap(Texture.WrapMode.MirroredRepeat);
    glowBuff.setColorTexture(glowTex);

    vBlur.setTexture("Texture", glowTex);
    vBlur.setFloat("Scale", 2f);
    vBlur.setFloat("Size", glowBuff.getHeight());

    hBlur.setTexture("Texture", glowTex);
    hBlur.setFloat("Scale", 2f);
    hBlur.setFloat("Size", glowBuff.getWidth());
    
    glowCam.resize(glowBuff.getWidth(), glowBuff.getHeight(), true);
    glowQuad.setWidth(glowBuff.getWidth());
    glowQuad.setHeight(glowBuff.getHeight());
    
    glowMat.setTexture("ColorMap", glowTex);
}

I rewrote some of that code from what I have because there’s more going on in that class and methods that isn’t relevant to this issue so I may have made a flub here or there in copy pasting :wink:

Edit: Here’s the full unaltered class:

package carpeDiem.UI;

import carpeDiem.Opts;
import com.jme3.asset.AssetManager;
import com.jme3.audio.AudioRenderer;
import com.jme3.input.InputManager;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.niftygui.NiftyJmeDisplay;
import com.jme3.renderer.Camera;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.ui.Picture;

/**
 *
 * @author Adam T. Ryder http://1337atr.weebly.com
 */
public class NiftyDiemDisplay extends NiftyJmeDisplay {
    public final FrameBuffer uiBuff;
    public final Camera cam = new Camera(1, 1);
    public final Picture quad = new Picture("Nifty Quad");
    public final Material frostMat;
    
    public final Texture2D uiTex;
    
    public final FrameBuffer defaultBuffer;
    
    private FrameBuffer glowBuff;
    private Texture2D glowTex;
    private final Picture glowQuad = new Picture("Glow Quad");
    private final Material glowMat;
    private final Material vBlur;
    private final Material hBlur;
    private final Camera glowCam = new Camera(1, 1);
    
    public NiftyDiemDisplay(
        final AssetManager assetManager,
        final InputManager inputManager,
        final AudioRenderer audioRenderer,
        final ViewPort viewport,
        final int atlasWidth,
        final int atlasHeight,
        final FrameBuffer defaultBuffer) {
        super(assetManager, inputManager, audioRenderer, viewport, atlasWidth, atlasHeight);
        
        this.defaultBuffer = defaultBuffer;
        uiBuff = new FrameBuffer(w, h, 0);
        uiBuff.setColorBuffer(Image.Format.RGBA8);
        uiBuff.setDepthBuffer(Image.Format.Depth);
        if (Opts.uifrost > Opts.FROST_LOW) {
            viewport.setOutputFrameBuffer(uiBuff);
        }
        
        uiTex = new Texture2D(w, h, Image.Format.RGBA8);
        uiTex.setWrap(Texture.WrapMode.MirroredRepeat);
        uiBuff.setColorTexture(uiTex);
        
        cam.resize(uiBuff.getWidth(), uiBuff.getHeight(), true);
        quad.setWidth(uiBuff.getWidth());
        quad.setHeight(uiBuff.getHeight());
        
        frostMat = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        frostMat.setTexture("ColorMap", uiTex);
        quad.setMaterial(frostMat);
        
        vBlur = new Material(assetManager, "MatDefs/Post/VGaussianBlur.j3md");
        
        hBlur = new Material(assetManager, "MatDefs/Post/HGaussianBlur.j3md");
        
        glowMat = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        glowMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Additive);
        
        setGlowMode();
    }
    
    public NiftyDiemDisplay(
        final AssetManager assetManager,
        final InputManager inputManager,
        final AudioRenderer audioRenderer,
        final ViewPort viewport,
        final FrameBuffer defaultBuffer) {
        super(assetManager, inputManager, audioRenderer, viewport);
        
        this.defaultBuffer = defaultBuffer;
        uiBuff = new FrameBuffer(w, h, 0);
        uiBuff.setColorBuffer(Image.Format.RGBA8);
        uiBuff.setDepthBuffer(Image.Format.Depth);
        if (Opts.uifrost > Opts.FROST_LOW) {
            viewport.setOutputFrameBuffer(uiBuff);
        }
        
        uiTex = new Texture2D(w, h, Image.Format.RGBA8);
        uiTex.setWrap(Texture.WrapMode.MirroredRepeat);
        uiBuff.setColorTexture(uiTex);
        
        cam.resize(uiBuff.getWidth(), uiBuff.getHeight(), true);
        quad.setWidth(uiBuff.getWidth());
        quad.setHeight(uiBuff.getHeight());
        
        frostMat = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        frostMat.setTexture("ColorMap", uiTex);
        quad.setMaterial(frostMat);
        
        vBlur = new Material(assetManager, "MatDefs/Post/VGaussianBlur.j3md");
        
        hBlur = new Material(assetManager, "MatDefs/Post/HGaussianBlur.j3md");
        
        glowMat = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        glowMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Additive);
        
        setGlowMode();
    }
    
    public void setFrostMode() {
        if (Opts.uifrost < Opts.FROST_MED) {
            vp.setOutputFrameBuffer(defaultBuffer);
        } else {
           vp.setOutputFrameBuffer(uiBuff);
        }
    }
    
    public void setGlowMode() {
        if (Opts.glow == Opts.GLOW_HIGH) {
            glowCam.resize(w, h, true);
            glowQuad.setWidth(w);
            glowQuad.setHeight(h);
            
            glowBuff = new FrameBuffer(w, h, 0);
            glowBuff.setColorBuffer(Image.Format.RGBA8);
            glowBuff.setDepthBuffer(Image.Format.Depth);

            glowTex = new Texture2D(w, h, Image.Format.RGBA8);
            glowTex.setWrap(Texture.WrapMode.MirroredRepeat);
            glowBuff.setColorTexture(glowTex);

            vBlur.setTexture("Texture", glowTex);
            vBlur.setFloat("Scale", 2f);
            vBlur.setFloat("Size", h);

            hBlur.setTexture("Texture", glowTex);
            hBlur.setFloat("Scale", 2f);
            hBlur.setFloat("Size", w);
            
            glowMat.setTexture("ColorMap", glowTex);
        } else if (Opts.glow == Opts.GLOW_LOW) {
            glowBuff = new FrameBuffer((int)FastMath.ceil(w * 0.5f),
                    (int)FastMath.ceil(h * 0.5f), 0);
            glowBuff.setColorBuffer(Image.Format.RGBA8);
            glowBuff.setDepthBuffer(Image.Format.Depth);

            glowTex = new Texture2D(glowBuff.getWidth(),
                    glowBuff.getHeight(), Image.Format.RGBA8);
            glowTex.setWrap(Texture.WrapMode.MirroredRepeat);
            glowBuff.setColorTexture(glowTex);

            vBlur.setTexture("Texture", glowTex);
            vBlur.setFloat("Scale", 2f);
            vBlur.setFloat("Size", glowBuff.getHeight());

            hBlur.setTexture("Texture", glowTex);
            hBlur.setFloat("Scale", 2f);
            hBlur.setFloat("Size", glowBuff.getWidth());
            
            glowCam.resize(glowBuff.getWidth(), glowBuff.getHeight(), true);
            glowQuad.setWidth(glowBuff.getWidth());
            glowQuad.setHeight(glowBuff.getHeight());
            
            glowMat.setTexture("ColorMap", glowTex);
        } else {
            glowBuff = null;
            glowTex = null;
        }
    }
    
    @Override
    public void postQueue(RenderQueue rq) {
        glowQuad.updateGeometricState();
        quad.updateGeometricState();
        if (UI.getState() == UI.STATE_GAME && Opts.glow > Opts.GLOW_OFF) {
            renderManager.getRenderer().setBackgroundColor(ColorRGBA.Black);            
            renderManager.getRenderer().setFrameBuffer(glowBuff);
            renderManager.getRenderer().clearBuffers(true, true, true);
            
            UI.getGameCam().getMidCam().resize(glowBuff.getWidth(), glowBuff.getHeight(), true);
            UI.getGameCam().getForeCam().resize(glowBuff.getWidth(), glowBuff.getHeight(), true);
            
            renderManager.setForcedTechnique("Glow");
            renderManager.renderViewPortRaw(UI.getGameCam().getMidPort());
            renderManager.getRenderer().clearBuffers(false, true, true);
            renderManager.renderViewPortRaw(UI.getGameCam().getForePort());
            renderManager.setForcedTechnique(null);
            
            UI.getGameCam().getMidCam().resize(w, h, true);
            UI.getGameCam().getForeCam().resize(w, h, true);
            
            glowQuad.setMaterial(vBlur);
            renderManager.setCamera(glowCam, true);
            renderManager.renderGeometry(glowQuad);
            
            glowQuad.setMaterial(hBlur);
            renderManager.setCamera(glowCam, true);
            renderManager.renderGeometry(glowQuad);
            
            if (Opts.glow == Opts.GLOW_HIGH) {
                glowQuad.setMaterial(vBlur);
                renderManager.setCamera(glowCam, true);
                renderManager.renderGeometry(glowQuad);

                glowQuad.setMaterial(hBlur);
                renderManager.setCamera(glowCam, true);
                renderManager.renderGeometry(glowQuad);
            }
            
            renderManager.getRenderer().setBackgroundColor(ColorRGBA.BlackNoAlpha);
            quad.setMaterial(glowMat);
            
            renderManager.getRenderer().setFrameBuffer(defaultBuffer);
            renderManager.setCamera(cam, true);
            renderManager.renderGeometry(quad);
            
            if (Opts.uifrost > Opts.FROST_LOW) {
                renderManager.getRenderer().setFrameBuffer(uiBuff);
            }
        }
        
        if (Opts.uifrost > Opts.FROST_OFF) {
            renderManager.getRenderer().copyFrameBuffer(defaultBuffer,
                uiBuff, false);
            quad.setMaterial(frostMat);
        }
        
        super.postQueue(rq);
        
        if (Opts.uifrost > Opts.FROST_LOW) {
            quad.updateGeometricState();

            renderManager.getRenderer().setFrameBuffer(defaultBuffer);
            renderManager.setCamera(cam, true);
            renderManager.renderGeometry(quad);
        }
    }
}

I setup NiftyGUI as follows:

niftyDisplay = new NiftyDiemDisplay(assetManager, inputManager,
        audioRenderer, guiViewPort, guiViewPort.getOutputFrameBuffer());
nifty = niftyDisplay.getNifty();
guiViewPort.addProcessor(niftyDisplay);
3 Likes

That is too cool! Thank you! As usual, when I have a question you guys are right there with the answer. I was also wondering how to best achieve a glowing HUD on the GUI node and you inadvertently answered that question too lol

You are very welcome. The FilterPostProcessor in jME is highly useful, but it also tries to be sort of a jack of all trades so some trade offs are made in favor of versatility. That doesn’t mean certain things cannot be done, one just has to put in a little extra leg work if they want something a bit more specific to their application.

In my opinion one of jMEs strongest advantages over other engines is the fact that it’s open source. I find when I’m having trouble accomplishing something if I root around in the source code to see what’s being done and where then I can figure out how to do what I want to do.

So my suggestion to anyone using jME is to familiarize yourself with the source code and I think you’ll find that you can accomplish pretty much anything that isn’t explicitly supported out of the box.

And of course, as stated, the community here is always helpful too :slight_smile:

2 Likes

Hm…
I once had the idea to stack multiple frustums onto each other too.
Since I want to render such a scene with long Z distance.
It would be super awesome to have a standard solution for this in jME.
Currently all I’m looking for is a little source code example.
I think this is basic stuff and should become a ‘TestStackedFrustums.java’ demo.
:chimpanzee_smile:

I can post my multi frustum code here later on tonight. It’s pretty easy to do actually. I am still fixing the bloom filter code though. My frustums cover a total near/far range from 0.1f to 1e7f.

1 Like

Nice,

I could write the TestStackedFrustums.java and try to contribute it.
Your code would of course be acknowledged via the author tag.

It would also be a good opportunity for me to learn something about the jME contribution process.
Maybe I could write a little wiki page called “how to contribute things to the engine or SDK”.
:chimpanzee_smile:

1 Like

That wiki page can help teach me how to contribute my InstancedLinkedNode contribution as well :smile:

Okay, let’s make it so.
I need a little change from the font / text / unicode stuff I’m working on right now.
Seems like a perfect opportunity. :chimpanzee_smile:

1 Like

@Ogli All done. I am posting the MultiViewPortAppState code below. I have created access methods for each view port’s camera, viewport and attached FilterPostProcessor as well.

package com.massivefrontier.appstate;

import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppState;
import com.jme3.math.ColorRGBA;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Camera;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;

/**

  • This AppState creates 4 overlapping view ports in order to prevent z-order

  • fighting when you are rendering a very large scene. Four different {@link ViewPort}s

  • and {@link Camera}s are created with a separate {@link FilterPostProcessor} on each

  • {@link ViewPort}. The {@link Camera}s are all synchronized on the update loop.

  • @author Zissis Trabaris
    */
    public class MultiViewportAppState extends AbstractAppState {

    private final FilterPostProcessor veryNearFilter;
    private final FilterPostProcessor nearFilter;
    private final FilterPostProcessor farFilter;
    private final FilterPostProcessor veryFarFilter;

    private final Spatial scene;

    private final ViewPort veryNearViewPort;
    private final ViewPort nearViewPort;
    private final ViewPort farViewPort;
    private final ViewPort veryFarViewPort;

    private final Camera veryNearCam;
    private final Camera nearCam;
    private final Camera farCam;
    private final Camera veryFarCam;

    public Camera getVeryFarCam() {
    return veryFarCam;
    }

    public FilterPostProcessor getVeryFarFilter() {
    return veryFarFilter;
    }

    public ViewPort getVeryFarViewPort() {
    return veryFarViewPort;
    }

    public Camera getVeryNearCam() {
    return veryNearCam;
    }

    public FilterPostProcessor getVeryNearFilter() {
    return veryNearFilter;
    }

    public ViewPort getVeryNearViewPort() {
    return veryNearViewPort;
    }

    public FilterPostProcessor getFarFilter() {
    return farFilter;
    }

    public ViewPort getFarViewPort() {
    return farViewPort;
    }

    public FilterPostProcessor getNearFilter() {
    return nearFilter;
    }

    public ViewPort getNearViewPort() {
    return nearViewPort;
    }

    public Spatial getScene() {
    return scene;
    }

    public Camera getNearCam() {
    return nearCam;
    }

    public Camera getFarCam() {
    return farCam;
    }

    /**

    • Create a multi view {@link AppState} for the passed in
    • {@link SimpleApplication} and scene {@link Spatial}.
    • @param app
    •        the {@link SimpleApplication}
      
    • @param scene
    •        the {@link Spatial} that will be attached as a scene for all
      
    •        the view ports.
      

    */
    public MultiViewportAppState(SimpleApplication app, Spatial scene) {
    super();
    this.scene = scene;

     // The very far viewport and camera are the default viewport and camera
     // of the application
     veryFarViewPort = app.getViewPort();
     veryFarCam = app.getCamera();
     veryFarCam.setFrustumPerspective(40f, (float) veryFarCam.getWidth() / veryFarCam.getHeight(), 2000f, 1e7f);
    
     // Attach a filter post processor to the very far viewport
     veryFarFilter = new FilterPostProcessor();
     veryFarViewPort.addProcessor(veryFarFilter);
    
     // Set up the far viewport and camera
     farCam = this.veryFarCam.clone();
     farCam.setFrustumPerspective(40f, (float) veryFarCam.getWidth() / veryFarCam.getHeight(), 1000f, 2010f);
     farCam.setViewPort(0f, 1f, 0.0f, 1.0f);
     farViewPort = app.getRenderManager().createMainView("FarView", farCam);
     farViewPort.setBackgroundColor(ColorRGBA.BlackNoAlpha);
     farViewPort.setClearFlags(false, true, true);
     farViewPort.attachScene(scene);
    
     // Attach a filter post processor to the far viewport
     farFilter = new FilterPostProcessor();
     farViewPort.addProcessor(veryFarFilter);
    
     // Set up the near viewport and camera
     nearCam = this.veryFarCam.clone();
     nearCam.setFrustumPerspective(40f, (float) veryFarCam.getWidth() / veryFarCam.getHeight(), 400f, 1010f);
     nearCam.setViewPort(0f, 1f, 0.0f, 1.0f);
     nearViewPort = app.getRenderManager().createMainView("NearView", nearCam);
     nearViewPort.setBackgroundColor(ColorRGBA.BlackNoAlpha);
     nearViewPort.setClearFlags(false, true, true);
     nearViewPort.attachScene(scene);
    
     // Attach a filter post processor to the near viewport
     nearFilter = new FilterPostProcessor();
     nearViewPort.addProcessor(nearFilter);
    
     // Set up the very near viewport and camera
     veryNearCam = this.veryFarCam.clone();
     veryNearCam.setFrustumPerspective(40f, (float) veryFarCam.getWidth() / veryFarCam.getHeight(), 0.1f, 410f);
     veryNearCam.setViewPort(0f, 1f, 0.0f, 1.0f);
     veryNearViewPort = app.getRenderManager().createMainView("VeryNearView", veryNearCam);
     veryNearViewPort.setBackgroundColor(ColorRGBA.BlackNoAlpha);
     veryNearViewPort.setClearFlags(false, true, true);
     veryNearViewPort.attachScene(scene);
    
     // Attach a filter post processor to the very near viewport
     veryNearFilter = new FilterPostProcessor();
     veryNearViewPort.addProcessor(veryNearFilter);
    

    }

    @Override
    public void update(float tpf) {

     // Synchronize the cameras to the very far camera
     farCam.setLocation(veryFarCam.getLocation());
     farCam.setRotation(veryFarCam.getRotation());
     nearCam.setLocation(veryFarCam.getLocation());
     nearCam.setRotation(veryFarCam.getRotation());
     veryNearCam.setLocation(veryFarCam.getLocation());
     veryNearCam.setRotation(veryFarCam.getRotation());
    

    }

}

Maybe @Tryder can add his multi pass custom bloom with verying bloom intensities and then we can have a complete app state :slight_smile:

Okay, I will now start to write the TestXYZ.java to demonstrate the test case.
I already had a good idea how to demonstrate this yesterday. :chimpanzee_smile:

Just out of curiosity: Why is there always a 10f more (overlap) between the frustums?

The overlap is my cheesy way to prevent a line wrapping around a spatial when 1/2 the spatial is in one viewport and the other half is in another vierwport. You get a line artifact due to rounding errors if the frustums did not slightly overlap.

By the way, I thought of a cool test case. Create a spatial, drop in in the closest viewport. In the spatial’s update code, it can check to see what viewport it is in and report it on the display. Then let the user use the FlyByCam to move around the scene.

My idea is also simple:
have the main viewport split in two viewports: left half and right half.
left half uses the standard (1 frustum for the whole Z range)
right half uses your solution (N frustums over the whole Z range)

On the ground there will be a blue plane (= “water”) and some green spheres (=“islands”)
In the air there will be some spheres which are close to each other.
These geometries are intended to provoke Z-fights for the more distant Spatials.

Your idea is also good and will be added like that:
The spheres in the air will be rendered in different colors (red, orange, yellow, green, …).
This way the user will see what sub-frustum they are in…

1 Like

Here ya go I added in Bloom/Glow to your MultiViewportAppState class: MultiViewportAppState.java - Google Drive

I had to post it on Google Drive because the text was too long to post here :smirk:

2 Likes

@Tryder thanks a million!! @Ogli can you please use Tryder’s version and include test cases for his bloom code so that we can contribute a complete solution back to the community?

Great! Yes, I finally have time today. And will now implement it.
First trying to make it with jME 3.0 and then with jME 3.1 too.

1 Like

So here is the latest update:

The TestStackedFrustums.java is almost complete. I added both versions of your 2 AppState implementations (the simple version without glow is very short which is nice). The content of the Test application makes good progress and I created several cases where Z fighting plays a role. Also it has some fancy features like a sun light which moves according to the real sun position when seen from Earth.

For the contribution process I chose a different thing:
I modified one of the Test apps that is already there (TestPointSprite.java) and will try to commit the changes. So we get a first impression of how the contribution process is in that case.
Later I will of course add a description how the contribution of something new (like TestStackedFrustums.java) actually happens - so we cover that “contribute something new” also.

Here is the modified TestPointSprite.java which I will commit first:

/*
 * Copyright (c) 2009-2012 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package jme3test.effect;

import com.jme3.app.SimpleApplication;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.effect.shapes.EmitterBoxShape;
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.math.ColorRGBA;
import com.jme3.math.Vector3f;

public class TestPointSprite extends SimpleApplication {

    protected int particleAsset = 0;
    protected int numParticleAssets = 7;
    protected String textureName;
    protected int imagesX, imagesY;
    
    public static void main(String[] args){
        TestPointSprite app = new TestPointSprite();
        app.start();
    }
    
    public void selectParticleAsset(int particleAsset)
    {
        switch(particleAsset)
        {
            case 0: imagesX = 15; imagesY = 1; textureName = "Effects/Smoke/Smoke.png"; break;
            case 1: imagesX = 2; imagesY = 2; textureName = "Effects/Explosion/flame.png"; break;
            case 2: imagesX = 2; imagesY = 2; textureName = "Effects/Explosion/flash.png"; break;
            case 3: imagesX = 3; imagesY = 3; textureName = "Effects/Explosion/Debris.png"; break;
            case 4: imagesX = 1; imagesY = 1; textureName = "Effects/Explosion/roundspark.png"; break;
            case 5: imagesX = 1; imagesY = 1; textureName = "Effects/Explosion/shockwave.png"; break;
            case 6: imagesX = 1; imagesY = 3; textureName = "Effects/Explosion/smoketrail.png"; break;
            default: break; //ignore
        }
    }
    
    @Override
    public void simpleInitApp() {
        
        flyCam.setMoveSpeed(100f);
        
        final ParticleEmitter emit = new ParticleEmitter("Emitter", Type.Point, 10000);
        emit.setShape(new EmitterBoxShape(new Vector3f(-1.8f, -1.8f, -1.8f),
                                          new Vector3f(1.8f, 1.8f, 1.8f)));
        
        particleAsset = 0;
        numParticleAssets = 7;
        
        selectParticleAsset(particleAsset);
        
        emit.setGravity(0, 0, 0);
        emit.setLowLife(60);
        emit.setHighLife(60);
        emit.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, 0));
        emit.setImagesX(imagesX);
        emit.setImagesY(imagesY);
        emit.setStartSize(0.05f);
        emit.setEndSize(0.05f);
        emit.setStartColor(ColorRGBA.White);
        emit.setEndColor(ColorRGBA.White);
        emit.setSelectRandomImage(true);
        emit.emitAllParticles();
        
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
        mat.setBoolean("PointSprite", true);
        //mat.setBoolean("PointSprite", false);
        mat.setTexture("Texture", assetManager.loadTexture(textureName));
        //mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png"));
        emit.setMaterial(mat);

        rootNode.attachChild(emit);
        inputManager.addListener(new ActionListener() {
            
            public void onAction(String name, boolean isPressed, float tpf) {
                if ("setNum".equals(name) && isPressed) {
                    emit.setNumParticles(5000);
                    emit.emitAllParticles();
                }
                else
                if ("toggle".equals(name) && isPressed) {
                    
                    //select particle texture
                    particleAsset = (particleAsset+1)%numParticleAssets;
                    selectParticleAsset(particleAsset);

                    //update material
                    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
                    mat.setBoolean("PointSprite", true);
                    mat.setTexture("Texture", assetManager.loadTexture(textureName));
                    
                    //update emitter
                    emit.setImagesX(imagesX);
                    emit.setImagesY(imagesY);
                    emit.setMaterial(mat);
                    //emit.updateGeometricState();
                    //emit.emitAllParticles();
                    
                    //debug info
                    System.out.println("particleAsset #"+particleAsset+" selected!");
                }
            }
        }, "setNum", "toggle");
        
        inputManager.addMapping("setNum", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_TAB));
    }

}

I think this is a beautiful example and actually provides something new to the community too.
:chimpanzee_smile:

1 Like

Cool! Contributing back to the comunity is something I have been looking forward to since I have gotten so much help from the comunity for my project. This will be a cool adition. I have a ton more usefull code for some very complex opened world space game problems that I had to solve from scratch that I want to contribute in the future as well. It seems, from reading the forums that most that started to create an opened world space mmo have dropped their projects as soon as they faced the realy complex bits. I aim to change that by contributing out of the box solutions back into jme.

1 Like