[Solved] Order of Filters

Best Morning jMonkeys,

I’m facing some problems related to Filters and the FilterPostProcessor.
Basically i just added WaterFilter, ShadowFilter, SSAOFilter, FogFilter, BloomFilter, DepthOfFieldFilter, LightScatteringFilter and in case samples is set to 0 also a FXAAFilter (all of them in that order) to a filterpostprocessor after its numsamples is set according to the selected settings. Cannot imagine it makes a difference but I first add the FilterPostProcessor to the viewport once the Filters are added to the FilterPostProcessor.

The problem I’m facing now is that, although I add the FogFilter after the ShadowFilter I can see the shadows being drawn with full strength (depending on shadowIntensity ofc) regardless of how foggy it is in that distance although I’d expect the shadows to fade into the fog just as the rest of the scene does. So to try out some things I quickly added an actionlistener so I can toggle Filters on and off by pressing some buttons ingame and I notices that when I remove the FogFilter from the FilterPostProcessor and re-add it I get the fading shadows I expect. This only happens when actually removing and re-adding the FogFilter, not when using setEnabled(true / false). It would make sence to me if the order of the Filters would matter, but it does not make sence to me that removing and re-adding the FogFilter from/to the FilterPostProcessor makes a difference if it doesnt affect the actual order.
Since it might sound confusing here is a quick example with less filters but I tested it and it appears too:
Order of Filters:
ShadowFilter, SSAOFilter, FogFilter, BloomFilter
I remove and re-add the FogFilter, new order should be:
ShadowFilter, SSAOFilter, BloomFilter, FogFilter
In both cases the FogFilter is behind the ShadowFilter, but only in the last case the fog is actually drawn over the shadows.
(BTW if I then also remove and re-add the ShadowFilter, the shadows are drawn with full strength again, probably because the ShadowFilter is behind the FogFilter now?)
So final question is, why does the order of the Filters make a difference, when it somehow does not make a difference since I’m initially adding them in the actually correct order (i assume)?
Just from reading my own post I thought there was a problem with the FogFilter so I in code added it as the very last Filter so i already when the game starts got the order i usually have after removing and re-adding the FogFilter and still the shadows are drawn independant of the fog until i remove and re-add the FogFilter also in this case

In case code is needed for that I can post some but I guess it’s more of a thing about the FilterPostProcessor and Filters that I just don’t understand.

Thanks a lot in advance for every bit of idea pushing me towards the right direction
Greetings, Samwise

Add the filters, then in the next frame add the fog filter.

Thanks for the quick reply, I guess I’ll do that in the meantime but ultimately I’d still like to understand what causes the difference between the two situations (I’m not yet sure I want to use all the Filters, might be I use less or even toggle them ingame in different situations or something so I’d like to understand whats actually happening)

Order of filters is VERY important.
A filter is some effect that is drawn over the scene image. think of is as a photoshop layer. So if you draw the shadows after the fog… the shadows appear over the fog.

1 Like

Also thanks to you for the quick reply.
I already thought so, soI wonder whats the difference between:

  1. ShadowFilter, SSAOFilter, BloomFilter, FogFilter (that order) right at start of app
    and
  2. ShadowFilter, SSAOFilter, BloomFilter, FogFilter (that order) but then
    -remove FogFilter (-> ShadowFilter, SSAOFilter, BloomFilter (that order)) and
    -add FogFilter again (-> ShadowFilter, SSAOFilter, BloomFilter, FogFilter (that order → same as 1)))

I would assume it’s no difference but scenario 1) gives me strong shadows even in the distance regardless of fog, while scenario 2) gives me correct shadows with fog drawn over it

The 2 cases should give the same result indeed.
We need to see your code.

Allright that means i was wrong thinking i missunderstand Filters, its quite some code so I’ll try to only post what’s related to Filters and attach screenshots of the result i mean, give me a couple of minutes please

Allright so here is the related code from the PostProcessorAppState:

[java]

        filterPostProcessor = new FilterPostProcessor(app.getAssetManager());
        app.getViewPort().addProcessor(filterPostProcessor);

        AppSettings settings = app.getContext().getSettings();
        numSamples = settings.getSamples();
        shadowQuality = (int) settings.getOrDefault(SettingsHandler.GFX_SHADOWS, 0);

        isSSAO = (Boolean) settings.getOrDefault(SettingsHandler.GFX_SSAO, false);
        isShadows = (shadowQuality > 0);
        isFog = (Boolean) settings.getOrDefault(SettingsHandler.GFX_FOG, false);
        boolean depth = (Boolean) settings.getOrDefault(SettingsHandler.GFX_DEPTH_BLUR, false);
        depth = false;
        isBloom = (Boolean) settings.getOrDefault(SettingsHandler.GFX_BLOOM, false);
        isLightScattering = (Boolean) settings.getOrDefault(SettingsHandler.GFX_LIGHT_SCATTERING, false);
        isDOF = (Boolean) settings.getOrDefault(SettingsHandler.GFX_DEPTH_OF_FIELD, false);
        isWater = true;
        isFXAA = numSamples > 0;
        LOGGER.logln(this, Logger.LEVEL_DEBUG, "GFXAppState filters: ssao(" + isSSAO + ") shadow(" + isShadows + ") fog(" + isFog + ") depthblur(" + depth + ") bloom(" + isBloom + ") lightScattering(" + isLightScattering + ") depthOfField(" + isDOF + ") and number of samples: " + numSamples + "(->fxaa(" + isFXAA + "))");

        if (numSamples > 0) {
            filterPostProcessor.setNumSamples(numSamples);
        }

        if (isWater) {
            waterFilter = new WaterFilter();
            if (reflectionScene != null) {
                waterFilter.setReflectionScene(reflectionScene);
            }
            waterFilter.setLightColor(sun.getColor());
            waterFilter.setLightDirection(sun.getDirection());
            waterFilter.setWaterTransparency(0.1f);
            waterFilter.setWaveScale(0.001f);
            waterFilter.setWaterColor(new ColorRGBA(0.6f, 0.6f, 1f, 1.0f));
            waterFilter.setDeepWaterColor(new ColorRGBA(0.4f, 0.4f, 1.0f, 1.0f));
            waterFilter.setWaterHeight(INIT_WATER_HEIGHT);
            waterFilter.setCausticsIntensity(0.0f);
            waterFilter.setFoamHardness(2.0f);
            waterFilter.setUnderWaterFogDistance(8 * PerformanceVariables.get().BLOCK_SIZE);
            waterFilter.setFoamIntensity(1.0f);
            waterFilter.setReflectionDisplace(50);
            waterFilter.setRefractionStrength(0.5f);
            waterFilter.setShoreHardness(0.01f);
            waterFilter.setSpeed(1.5f);
            filterPostProcessor.addFilter(waterFilter);
        }

        if (isShadows) {
            EdgeFilteringMode efm;
            float factor = 1;
            boolean capShadows = true;
            float zExtend = 0.0f, zFade = 0.0f;
            efm = EdgeFilteringMode.Nearest;
            CompareMode cm = CompareMode.Hardware;
            switch (shadowQuality) {
                case 1:
                    efm = EdgeFilteringMode.Nearest;
                    factor = 1;
                    zExtend = PerformanceVariables.get().BLOCK_SIZE * 64f;
                    zFade = PerformanceVariables.get().BLOCK_SIZE * 8f;
                    break;
                case 2:
                    efm = EdgeFilteringMode.Bilinear;
                    factor = 1;
                    zExtend = PerformanceVariables.get().BLOCK_SIZE * 128f;
                    zFade = PerformanceVariables.get().BLOCK_SIZE * 16f;
                    break;
                case 3:
                    efm = EdgeFilteringMode.Bilinear;
                    factor = 2;
                    capShadows = false;
                    break;
                case 4:
                    efm = EdgeFilteringMode.PCFPOISSON;
                    factor = 4;
                    capShadows = false;
                    break;
            }
            int shadowMapSize = (int) (512 * factor * PerformanceVariables.get().BLOCK_SIZE);

            boolean isRenderer = false;
            if (isRenderer) {

                shadowRenderer = new DirectionalLightShadowRenderer(app.getAssetManager(), shadowMapSize, shadowQuality);
                shadowRenderer.setLight(sun);
                shadowRenderer.setEdgeFilteringMode(efm);
                shadowRenderer.setEdgesThickness(2);
                shadowRenderer.setEnabledStabilization(true);
                shadowRenderer.setShadowCompareMode(cm);
                if (capShadows) {
                    shadowRenderer.setShadowZExtend(zExtend);
                    shadowRenderer.setShadowZFadeLength(zFade);
                }
                shadowRenderer.setShadowIntensity(0.5f);
                app.getViewPort().addProcessor(shadowRenderer);
            } else {
                shadowFilter = new DirectionalLightShadowFilter(app.getAssetManager(), shadowMapSize, shadowQuality);
                shadowFilter.setLight(sun);
                shadowFilter.setEdgeFilteringMode(efm);
                shadowFilter.setEdgesThickness(1);
                shadowFilter.setEnabledStabilization(true);
                shadowFilter.setShadowCompareMode(cm);
                if (capShadows) {
                    shadowFilter.setShadowZExtend(zExtend);
                    shadowFilter.setShadowZFadeLength(zFade);
                }
                shadowFilter.setShadowIntensity(0.5f);
                filterPostProcessor.addFilter(shadowFilter);
            }
        }
        if (isSSAO) {
            //ssaoFilter = new SSAOFilter(7.94f, 5.92f, 0.33f, 0.61f);
            ssaoFilter = new SSAOFilter();
            filterPostProcessor.addFilter(ssaoFilter);
        }
        if (isBloom) {
            bloomFilter = new BloomFilter();
            bloomFilter.setBloomIntensity(1);
            bloomFilter.setBlurScale(1.0f);
            bloomFilter.setDownSamplingFactor(1.0f);
            bloomFilter.setExposureCutOff(0.5f);
            bloomFilter.setExposurePower(3.0f);
            filterPostProcessor.addFilter(bloomFilter);
        }
        if (isDOF) {
            depthOfFieldFilter = new DepthOfFieldFilter();
            depthOfFieldFilter.setFocusDistance(0);
            depthOfFieldFilter.setFocusRange(256 * PerformanceVariables.get().BLOCK_SIZE);
            depthOfFieldFilter.setBlurScale(1.0f);
            filterPostProcessor.addFilter(depthOfFieldFilter);
        }
        if (isLightScattering) {
            lightScatteringFilter = new LightScatteringFilter();
            lightScatteringFilter.setBlurStart(0.1f);
            lightScatteringFilter.setBlurWidth(0.9f);
            lightScatteringFilter.setLightDensity(0.8f);
            lightScatteringFilter.setLightPosition(Vector3f.UNIT_X.mult(300));
            lightScatteringFilter.setNbSamples(Math.max(20, Math.min(100, 25 * numSamples)));
            filterPostProcessor.addFilter(lightScatteringFilter);
        }
        if (depth) {
            depthBlurFilter = new DepthBlurFilter();
            depthBlurFilter.setFocusDistance(10 * PerformanceVariables.get().BLOCK_SIZE);
            depthBlurFilter.setFocusRange(20 * PerformanceVariables.get().BLOCK_SIZE);
            depthBlurFilter.setBlurScale(0.8f);
            filterPostProcessor.addFilter(depthBlurFilter);
        }
        if (numSamples < 1) {
            fxaaFilter = new FXAAFilter();
            filterPostProcessor.addFilter(fxaaFilter);
        }
        if (isFog) {
            fogFilter = new FogFilter();
            fogFilter.setFogDensity(1.0f);
            fogFilter.setFogColor(new ColorRGBA(0.45f, 0.45f, 0.45f, 0.4f));
            fogFilter.setFogDistance(512 * PerformanceVariables.get().BLOCK_SIZE);
            filterPostProcessor.addFilter(fogFilter);
        }

[/java]

according to the settings from the screenshot below (settings stuff works, i tested and the booleans related to the filters are set correctly meaning the correct filters are applied) there is WaterFilter, ShadowFilter, SSAOFilter, BloomFilter and FogFilter added in that order now (i moved adding the filterpostprocessor to the viewport to the beginning in the meantime to see if it made a difference but unsurprisingly it didn’t)

this is only for toggling the filters ingame as i mentioned:
[java]

    ssaoOn = ssaoFilter != null;
    fogOn = fogFilter != null;
    bloomOn = bloomFilter != null;
    lsOn = lightScatteringFilter != null;
    shadowsOn = shadowFilter != null;
    waterOn = waterFilter != null;
    dofOn = depthOfFieldFilter != null;
    app.getInputManager().addMapping("SSAO", new KeyTrigger(KeyInput.KEY_F6));
    app.getInputManager().addMapping("FOG", new KeyTrigger(KeyInput.KEY_F7));
    app.getInputManager().addMapping("BLOOM", new KeyTrigger(KeyInput.KEY_F8));
    app.getInputManager().addMapping("LIGHTSCATTERING", new KeyTrigger(KeyInput.KEY_F9));
    app.getInputManager().addMapping("SHADOWS", new KeyTrigger(KeyInput.KEY_F10));
    app.getInputManager().addMapping("WATER", new KeyTrigger(KeyInput.KEY_F11));
    app.getInputManager().addMapping("DOF", new KeyTrigger(KeyInput.KEY_F12));
    app.getInputManager().addListener(new ActionListener() {
        @Override
        public void onAction(String name, boolean isPressed, float tpf) {
            boolean remove = true;
            if (isPressed) {
                boolean turnedOn = true;
                if ("SSAO".equals(name)) {
                    if (ssaoOn) {
                        ssaoOn = false;
                        if (remove) {
                            filterPostProcessor.removeFilter(ssaoFilter);
                        } else {
                            ssaoFilter.setEnabled(false);
                        }
                        turnedOn = false;
                    } else {
                        if (ssaoFilter != null) {
                            ssaoOn = true;
                            if (remove) {
                                filterPostProcessor.addFilter(ssaoFilter);
                            } else {
                                ssaoFilter.setEnabled(true);
                            }
                        }
                    }
                }
                if ("FOG".equals(name)) {
                    if (fogOn) {
                        fogOn = false;
                        if (remove) {
                            filterPostProcessor.removeFilter(fogFilter);
                        } else {
                            fogFilter.setEnabled(false);
                        }
                        turnedOn = false;
                    } else {
                        if (fogFilter != null) {
                            fogOn = true;
                            if (remove) {
                                filterPostProcessor.addFilter(fogFilter);
                            } else {
                                fogFilter.setEnabled(true);
                            }
                        }
                    }
                }
                if ("BLOOM".equals(name)) {
                    if (bloomOn) {
                        bloomOn = false;
                        if (remove) {
                            filterPostProcessor.removeFilter(bloomFilter);
                        } else {
                            bloomFilter.setEnabled(false);
                        }
                        turnedOn = false;
                    } else {
                        if (bloomFilter != null) {
                            bloomOn = true;
                            if (remove) {
                                filterPostProcessor.addFilter(bloomFilter);
                            } else {
                                bloomFilter.setEnabled(true);
                            }
                        }
                    }
                }
                if ("LIGHTSCATTERING".equals(name)) {
                    if (lsOn) {
                        lsOn = false;
                        if (remove) {
                            filterPostProcessor.removeFilter(lightScatteringFilter);
                        } else {
                            lightScatteringFilter.setEnabled(false);
                        }
                        turnedOn = false;
                    } else {
                        if (lightScatteringFilter != null) {
                            lsOn = true;
                            if (remove) {
                                filterPostProcessor.addFilter(lightScatteringFilter);
                            } else {
                                lightScatteringFilter.setEnabled(true);
                            }
                        }
                    }
                }
                if ("SHADOWS".equals(name)) {
                    if (shadowsOn) {
                        shadowsOn = false;
                        if (remove) {
                            filterPostProcessor.removeFilter(shadowFilter);
                        } else {
                            shadowFilter.setEnabled(false);
                        }
                        turnedOn = false;
                    } else {
                        if (shadowFilter != null) {
                            shadowsOn = true;
                            if (remove) {
                                filterPostProcessor.addFilter(shadowFilter);
                            } else {
                                shadowFilter.setEnabled(true);
                            }
                        }
                    }
                }
                if ("WATER".equals(name)) {
                    if (waterOn) {
                        waterOn = false;
                        if (remove) {
                            filterPostProcessor.removeFilter(waterFilter);
                        } else {
                            waterFilter.setEnabled(false);
                        }
                        turnedOn = false;
                    } else {
                        if (waterFilter != null) {
                            waterOn = true;
                            if (remove) {
                                filterPostProcessor.addFilter(waterFilter);
                            } else {
                                waterFilter.setEnabled(true);
                            }
                        }
                    }
                }
                if ("DOF".equals(name)) {
                    if (dofOn) {
                        dofOn = false;
                        if (remove) {
                            filterPostProcessor.removeFilter(depthOfFieldFilter);
                        } else {
                            depthOfFieldFilter.setEnabled(false);
                        }
                        turnedOn = false;
                    } else {
                        if (depthOfFieldFilter != null) {
                            dofOn = true;
                            if (remove) {
                                filterPostProcessor.addFilter(depthOfFieldFilter);
                            } else {
                                depthOfFieldFilter.setEnabled(true);
                            }
                        }
                    }
                }
                NotifierAppState nas = app.getStateManager().getState(NotifierAppState.class);
                if (nas != null) {
                    nas.addNotification(new Notification("changed: " + name + ": " + turnedOn));
                }
            }
        }

    }, "SSAO", "FOG", "BLOOM", "LIGHTSCATTERING", "SHADOWS", "WATER", "DOF");

[/java]

Thats the settings (for simplicity only shadows ssao bloom and fog as in the original example):

Thats what it looks like right after i start the actual game
(Remember Filter order was WaterFilter, ShadowFilter, SSAOFilter, BloomFilter, FogFilter)

then i REMOVE (not setEnabled(false)) the FogFilter from the PostProcessor ingame
→ order is now WaterFilter, ShadowFilter, SSAOFilter, BloomFilter

When i add the FogFilter again it looks as i would expect:
→ order is exactly same as initially, with WaterFilter, ShadowFilter, SSAOFilter, BloomFilter, FogFilter, still it gives different result

and for randomness here is 2 more steps, basically confirming the problem is the order of the filters
Now thats 2 steps in one, i removed and added again the shadowFilter (gives wrong shadows again ofc, because order is now WaterFilter, SSAOFilter, BloomFilter, FogFilter, ShadowFilter)

Another two steps, removed and aggain again the fogFilter
(gives correct result (or probably differs slightly from the 3rd image since the shadow is drawn after the SSAO and BloomFilter), with this order: WaterFilter, SSAOFilter, BloomFilter, FogFilter, ShadowFilter)

and have a look at that awesome pumpgun in the quickbar and the right hand (yes, its supposed to be a pumpgun, don’t bother me)

[edit] i wish i could make them all the same view direction but it kept jumping around when unfocussing for screenshots but if you compare the 2nd image to the 4th you can see the difference of the shadow [/edit]

[edit2] i noticed that the IsoSurfaceDemo does not make use of the FogFilter, while using i guess all the other filters and since my problem might be FogFilter related too instead of only filter-order related, is the FogFilter maybe bugged?[/edit2]

Nehon understands filters far more than me, but just so you know, the fog filter in my eyes should be deprecated in favour of a few lines of extra code in your material shader. It makes a lot more sense in this particular case. I never did get on with the filter very well, but it’s great for a out of the box effect.

A slightly bit more context would be nice. What method is this in? simpleInit()? Something else? Is it maybe called more than once?

One thing you can do to prove the order is what expect would be to System.out.println() the filter list just to make 100% sure the order is what you think it is.

Assuming the order really is as you expect… from there, it should be fairly simple for you to put together a simple single-class test case that illustrates the issue.

1 Like

I basically got an abstract class BasicAppState, that has an abstract method “void enable(boolean isInit)” thats called with parameter true in the initialize method and called with parameter false in the setEnabled(true) method. I guess it basically works like the BaseAppState or how its called that i found posted somewhere but i had that BasicAppState thing going by then already so i just sticked with it.
The above posted extract is in the enable(boolean isInit) method of the PostProcessorAppState, only called if the parameter was true, so it’s only called when this AppState is initialized, which works, I’ve tested it with a simple System.out.println().

The thing about printing out the filter’s list was actually surprising me when i tested it. what i found was the following:
when i print the filters list right in that enable(boolean isInit) method that i posted above i get the following output:
Filters:
Filter 0: com.jme3.water.WaterFilter@78772f80
Filter 1: com.jme3.shadow.DirectionalLightShadowFilter@1b3092ad
Filter 2: com.jme3.post.ssao.SSAOFilter@5f459495
Filter 3: com.jme3.post.filters.BloomFilter@5f584c77
Filter 4: com.jme3.post.filters.DepthOfFieldFilter@7296653e
Filter 5: com.jme3.post.filters.LightScatteringFilter@734e4b40
Filter 6: com.jme3.post.filters.FogFilter@194c51d9
(with all filters enabled, and its the order i would expect)
so i then added the following lines to the PostProcessorAppStates update(float tpf) method:

time += tpf;
if (filtersPosted == 0 || (filtersPosted == 1 && time > 10)) {
System.out.println(“FILTER in update”);
filtersPosted++;
List l = filterPostProcessor.getFilterList();
for (int i = 0; i < l.size(); i++) {
System.out.println("FILTER: " + i + ": " + l.get(i));
}
}
and i get the exact same output as above in the first frame, however when its executed again after 10 seconds i get another output:
FILTER in update
FILTER: 0: com.jme3.water.WaterFilter@78772f80
FILTER: 1: com.jme3.post.ssao.SSAOFilter@5f459495
FILTER: 2: com.jme3.post.filters.DepthOfFieldFilter@7296653e
FILTER: 3: com.jme3.post.filters.LightScatteringFilter@734e4b40
FILTER: 4: com.jme3.post.filters.FogFilter@194c51d9
FILTER: 5: com.jme3.post.filters.BloomFilter@5f584c77
FILTER: 6: com.jme3.shadow.DirectionalLightShadowFilter@1b3092ad
and just when writing this the embarrassing moment kicks in when i realize i check for the cameras position to see if its considered underwater and if so i remove some filters, and when its not underwater anymore i add them again, resulting in another filter order (i didnt have a dive before i took the example pictures though, but i guess its checked in the first frame before the cameras position is set to the players position meaning the filters order would always be changed in the first frame). So i quickly uncommented it and realized i get the result from the 4th image right at the start of the app, bug found, thanks a holy moly lot for ending my chapter of accidents!

If you knew how many times @pspeed solved an issue here just by asking the original poster to make a single class test case…
I’m glad you solved your issue :wink:

1 Like

Step one of debugging is to verify assumptions.

…now, the tricky part is figuring out what those assumptions are. :slight_smile: In this case, the assumption was that the order was what you thought it was just because you set it that way.

Those assumptions can be tricky. Which is why you will often find my code littered with log statements.