Gradient Fog Filter

To not spam the WIP screenshot thread more, I created a new thread for the gradient fog filter.

This is not my code, I just modified Riccardo’s gradient fog he wrote for his render pipeline to work with the current JME’s filter post-processor.

public class GradientFogFilter extends Filter {

    private Texture fogGradient;
    private float fogIntensity = 1f;
    private float fogDistanceNear = 1f;
    private float fogDistanceFar = 1000f;

    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
        material = new Material(manager, "MatDefs/Post/GradientFog.j3md");
        if (fogGradient == null) {
            fogGradient = manager.loadTexture("Textures/gradient_ramp2.png");
        material.setTexture("FogGradient", fogGradient);
        material.setFloat("FogIntensity", fogIntensity);
        material.setFloat("FogDistanceNear", fogDistanceNear);
        material.setFloat("FogDistanceFar", fogDistanceFar);

    protected Material getMaterial() {
        return material;

    protected boolean isRequiresDepthTexture() {
        return true;

    public Texture getFogGradient() {
        return fogGradient;

    public void setFogGradient(Texture fogGradient) {
        if (material != null) {
            material.setTexture("FogGradient", fogGradient);

        this.fogGradient = fogGradient;

    public float getFogIntensity() {
        return fogIntensity;

    public void setFogIntensity(float fogIntensity) {
        if (material != null) {
            material.setFloat("FogIntensity", fogIntensity);
        this.fogIntensity = fogIntensity;

    public float getFogDistanceNear() {
        return fogDistanceNear;

    public void setFogDistanceNear(float fogDistanceNear) {
        if (material != null) {
            material.setFloat("FogDistanceNear", fogDistanceNear);

        this.fogDistanceNear = fogDistanceNear;

    public float getFogDistanceFar() {
        return fogDistanceFar;

    public void setFogDistanceFar(float fogDistanceFar) {
        if (material != null) {
            material.setFloat("FogDistanceFar", fogDistanceFar);

        this.fogDistanceFar = fogDistanceFar;


MaterialDef GradientFog {
    MaterialParameters {
        Int NumSamples
        Int NumSamplesDepth
        Texture2D Texture
        Texture2D DepthTexture
        Texture2D FogGradient
        Float FogIntensity
        Float FogDistanceNear
        Float FogDistanceFar

    Technique {
        VertexShader GLSL150:   Common/MatDefs/Post/Post.vert
        FragmentShader GLSL150: Shaders/Post/GradientFog.frag

        WorldParameters {

        Defines {
            RESOLVE_MS : NumSamples
            RESOLVE_DEPTH_MS : NumSamplesDepth


#import "Common/ShaderLib/GLSLCompat.glsllib"
#import "Common/ShaderLib/MultiSample.glsllib"
#extension GL_ARB_explicit_attrib_location : enable

uniform COLORTEXTURE m_Texture;
uniform DEPTHTEXTURE m_DepthTexture;
varying vec2 texCoord;

uniform sampler2D m_FogGradient;
uniform float m_FogIntensity;
uniform float m_FogDistanceNear;
uniform float m_FogDistanceFar;

// depth is in [0, 1]
float getDistance(in float depth) {
    float near = 1.0;
    float far = m_FogDistanceFar;
    float d = 2.0 * depth -1.0;
    return (2. * near * far) / (far + near - d * (far - near));

float linear01Depth(in float depth) {
    float distance = getDistance(depth);
    float near = m_FogDistanceNear;
    float far = m_FogDistanceFar;
    distance = clamp(distance, near, far);
    return (distance - near) / (far - near);

vec4 sampleWithFog(in sampler2D sceneTx, in sampler2D depthTx, in sampler2D gradientTx){
    float depth = linear01Depth(texture(depthTx, texCoord).r);
    vec4 fogGradient = texture(gradientTx, vec2(depth, 0));
    vec4 color = texture(sceneTx, texCoord);
    color.rgb = mix(color.rgb, fogGradient.rgb, fogGradient.a * m_FogIntensity);
    return color;

void main(){
    gl_FragColor = sampleWithFog(m_Texture, m_DepthTexture, m_FogGradient);

gradient_ramp.png: (from here)

gradient_ramp2.png: (from here)

gradient_ramp2-1.png: (from here)

gradient_ramp7.png: (I made with Gimp)

defaultGradient.bmp: (It’s from Riccardo’s repo)

Usage example:

        // Setup fog
        gradientFog = new GradientFogFilter();

Read More:

Some screenshots shared by @ndebruyn using this filter in their editor:

Some more (shared by me;)


Looks nice, are you guys willing to add this feature to Jme-3.7?

1 Like

Maybe, once it becomes stable :slightly_smiling_face:

Currently we found an issue with aliasing, reported here:


So, this issue happens when enabling anti-aliasing from jme3 App settings only?

1 Like

I suspect this is another limitation of screen space post-processing fog. Though I haven’t thought enough about it to explain it.

Note: if you already have forked JME shaders then it’s pretty trivial to add a fog gradient. I plan to do this for my Mythruna shaders. If there is interest then I can backport it to at least Lighting.j3md (what I’m based on now).


Thanks, this would be cool :slightly_smiling_face:

In my case, I am only using the FXAA filter. The anti-aliasing settings from AppSettings is off.

If you take a look at the last screenshot (the mountain edges) you can see the aliasing artifact. It goes away when I disable the fog.

Regarding @ndebruyn case, I do not know what anti-aliasing settings he is using though.

1 Like

I am using the same as you @Ali_RS .
I am also enabling the AppSettings one at startup.


I don’t know the underlying maths behind this filter, but in general, I guess exclusion will be helpful, Could you try the anti-aliasing on the AppSettings only and remove the FXAA filter or disable it? (The issue may be on the FXAA filter).

1 Like

Enabling FXAA and also enabling AA in the AppSettings feels like wearing two raincoats.

1 Like

Just with AppSettings anti-aliasing still causes this:

1 Like

Thanks for the test, I guess this might be normal too, I will try to investigate this problem if we are really going to add this filter (because it is cool).

Btw, I found this note from the Gradient Fog Unity’s manual, seems to be a plugin btw, the MSAA stands for Multi-Sampling Anti-Aliasing:


Just for interrest, how in jME can we guarantee the order in which filters are executed?

This is much related to the FilterPostProcessor, so the answer is in the order you add them to the FilterPostProcessor.


You might try to add FXAA after the Gradient Fog and test that behavior.

1 Like

Also, if you take all filters that we have in jME, what would be the optimal logical order of adding them?
For example:


It’s hard to tell so unless there is a behavior that overwrites the other one, as applying the Fog Filter after FXAA, the fog filter is camera-based that is why I guess this is what causes the conflict with the anti-aliased Z-buffer, it seems to be an overwriting behavior.

The GLSL Code might be your answer though.

1 Like

As a general rule, I’d say you’d categorize the filters like:
-rendering effects (shadows, water, SSAO… things that are simulating actual geometry/lighting in the scene)
-atmospheric effects (fog, scattering, etc.)
-lens effects (depth of field)
-‘printing’ effects (is in film printing) so bloom, FXAA, Cartoon, etc…

And within that, sometimes it will be an artistic choice or practical choice. Do you want FXAA antialiasing before or after cartoon edges… bloom after? Artistic choices.


Cool, thanks for your input here.
This makes figuring the order.
Do you know if the FilterPostProcesstor maintains the order in which you add the filters?

1 Like

Yes. That’s the only way to control the order.