Gradient Fog Filter

The documentation says yes and the code too.

2 Likes

You also want to consider that filters that write translucent data are kind of corrupting the input data for the later filters.
The end result may vary depending if they write depth or not, but all filters using modifies data after that produce physically wrong results

I refactored the shader, in my case, the edges now look smoother.

GradientFogFilter.java:


/**
 * @author Ali-RS
 */
public class GradientFogFilter extends Filter {

    private Texture fogGradient;
    private float fogIntensity = 1;
    private float fogDistance = 100;

    @Override
    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("FogDistance", fogDistance);
    }

    @Override
    protected Material getMaterial() {
        return material;
    }

    @Override
    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 getFogDistance() {
        return fogDistance;
    }

    public void setFogDistance(float fogDistance) {
        if (material != null) {
            material.setFloat("FogDistance", fogDistance);
        }

        this.fogDistance = fogDistance;
    }
}

GradientFog.j3md:

MaterialDef GradientFog {
    MaterialParameters {
        Int NumSamples
        Int NumSamplesDepth
        Texture2D Texture
        Texture2D DepthTexture
        Texture2D FogGradient
        Float FogIntensity
        Float FogDistance
    }

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

        WorldParameters {

        }

        Defines {
            RESOLVE_MS : NumSamples
            RESOLVE_DEPTH_MS : NumSamplesDepth
        }
    }
}

GradientFog.frag:

#import "Common/ShaderLib/GLSLCompat.glsllib"
#import "Common/ShaderLib/MultiSample.glsllib"

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

uniform sampler2D m_FogGradient;
uniform float m_FogIntensity;
uniform float m_FogDistance;

const float LOG2 = 1.442695;

void main(){
    vec2 frustumNearFar = vec2(1.0, m_FogDistance);
    vec4 color = getColor(m_Texture, texCoord);
    float fogVal = getDepth(m_DepthTexture, texCoord).r;
    float depth = (2.0 * frustumNearFar.x) / (frustumNearFar.y + frustumNearFar.x - fogVal * (frustumNearFar.y - frustumNearFar.x));

    float fogFactor = exp2(-m_FogIntensity * m_FogIntensity * depth *  depth * LOG2);
    fogFactor = 1.0 - clamp(fogFactor, 0.0, 1.0);

    vec4 fogColor = texture(m_FogGradient, vec2(depth, 0.5));
    //vec4 fogColor = texture(m_FogGradient, vec2(fogFactor, 0.5));

    color.rgb = mix(color.rgb, fogColor.rgb, fogFactor);

    gl_FragColor = color;
}
1 Like

Try either of the above codes and see which effect you like :slightly_smiling_face:

Hint, change the fog intensity to notice how they are different.

Edit:
If someone finds a good param name for it, let me know so I can add an option to make it switchable in the material!

2 Likes

Cool. It seems like it works beter with this one. I wonder if it would be possible to provide a gradient between 3 colors to apply rather than a texture?

2 Likes

I am sure it is possible, but it would require a bunch of dirty if/elses I think. A better solution as described below might be to dynamically generate a 1D gradient image from 2 or more colors and use it with the filter.

Looks like the Java AWT package already has a utility class (LinearGradientPaint) for generating a gradient buffered image from multiple colors. I think you can find an example in the above link.

1 Like

From a quick glance at the above code, I think this is doable by adding multiple GradientFogFilter objects each with a different setup to the FilterPostProcessor, but you will have to test it, I may be mistaken.

Okay so I implemented a solution of the GradientFogFilter where you can specify 3 different colors and then also the y/height value of the middle color.

Here is a screenshot of the 2 different effects side by side.
Left is image based and right is color gradient based.

Screenshot

3 Likes

Looks cool :slightly_smiling_face:

1 Like

But something is not right when you look up or down, seems like I need to apply the fog factor somehow on the fogColor.

Here is the code:

#import "Common/ShaderLib/GLSLCompat.glsllib"
#import "Common/ShaderLib/MultiSample.glsllib"

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

uniform sampler2D m_FogGradient;
uniform float m_FogIntensity;
uniform float m_FogDistance;
uniform vec4 m_TopColor;
uniform vec4 m_MiddleColor;
uniform vec4 m_BottomColor;
uniform float m_MiddleColorPosition;

const float LOG2 = 1.442695;

void main(){
    vec2 frustumNearFar = vec2(1.0, m_FogDistance);
    vec4 color = getColor(m_Texture, texCoord);
    float fogVal = getDepth(m_DepthTexture, texCoord).r;
    float depth = (2.0 * frustumNearFar.x) / (frustumNearFar.y + frustumNearFar.x - fogVal * (frustumNearFar.y - frustumNearFar.x));

    float fogFactor = exp2(-m_FogIntensity * m_FogIntensity * depth *  depth * LOG2);
    fogFactor = 1.0 - clamp(fogFactor, 0.0, 1.0);

    //vec4 fogColor = texture(m_FogGradient, vec2(depth, 0.5));
    vec4 fogColor = texture(m_FogGradient, vec2(fogFactor, 0.5));

    //THIS PART IS FOR THE 3 Color effect
    if (texCoord.x > 0.5) {
        fogColor = mix(mix(m_BottomColor, m_MiddleColor, texCoord.y/m_MiddleColorPosition), mix(m_MiddleColor, m_TopColor, (texCoord.y - m_MiddleColorPosition)/(1.0 - m_MiddleColorPosition)), step(m_MiddleColorPosition, texCoord.y));        
        //TODO: Need to implement fogFactor combination
        
    }
    
    color.rgb = mix(color.rgb, fogColor.rgb, fogFactor);    

    gl_FragColor = color;
}
MaterialDef GradientFog {
    MaterialParameters {
        Int NumSamples
        Int NumSamplesDepth
        Texture2D Texture
        Texture2D DepthTexture
        Texture2D FogGradient
        Float FogIntensity
        Float FogDistance
        Float MiddleColorPosition
        Color TopColor (Color)
        Color MiddleColor (Color)
        Color BottomColor (Color)
    }

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

        WorldParameters {

        }

        Defines {
            RESOLVE_MS : NumSamples
            RESOLVE_DEPTH_MS : NumSamplesDepth
        }
    }
}

And the filter:

public class GradientFogFilter extends Filter {

    private Texture fogGradient;
    private float fogIntensity = 1.5f;
    private float fogDistance = 1000;
    private float middleColorPosition = 0.5f;
    private ColorRGBA topColor = new ColorRGBA(0.2f, 0.2f, 0.8f, 0.2f);
    private ColorRGBA middleColor = new ColorRGBA(0.8f, 0.2f, 0.2f, 1f);
    private ColorRGBA bottomColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 0.5f);

    @Override
    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/Post/gradient_ramp2.png");
        }

        material.setTexture("FogGradient", fogGradient);
        material.setFloat("FogIntensity", fogIntensity);
        material.setFloat("FogDistance", fogDistance);
        material.setFloat("MiddleColorPosition", middleColorPosition);
        material.setColor("TopColor", topColor);
        material.setColor("MiddleColor", middleColor);
        material.setColor("BottomColor", bottomColor);
    }

    @Override
    protected Material getMaterial() {
        return material;
    }

    @Override
    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 getFogDistance() {
        return fogDistance;
    }

    public void setFogDistance(float fogDistance) {
        if (material != null) {
            material.setFloat("FogDistance", fogDistance);
        }

        this.fogDistance = fogDistance;
    }

    public float getMiddleColorPosition() {
        return middleColorPosition;
    }

    public void setMiddleColorPosition(float middleColorPosition) {
        if (material != null) {
            material.setFloat("MiddleColorPosition", middleColorPosition);
        }
        this.middleColorPosition = middleColorPosition;
    }

    public ColorRGBA getTopColor() {
        return topColor;
    }

    public void setTopColor(ColorRGBA topColor) {
        if (material != null) {
            material.setColor("TopColor", topColor);
        }
        this.topColor = topColor;
    }

    public ColorRGBA getMiddleColor() {
        return middleColor;
    }

    public void setMiddleColor(ColorRGBA middleColor) {
        if (material != null) {
            material.setColor("MiddleColor", middleColor);
        }
        this.middleColor = middleColor;
    }

    public ColorRGBA getBottomColor() {
        return bottomColor;
    }

    public void setBottomColor(ColorRGBA bottomColor) {
        if (material != null) {
            material.setColor("BottomColor", bottomColor);
        }
        this.bottomColor = bottomColor;
    }

    @Override
    public GradientFogFilter clone() {
        GradientFogFilter clone = new GradientFogFilter();
        clone.fogDistance = fogDistance;
        clone.fogIntensity = fogIntensity;
        clone.fogGradient = fogGradient;
        clone.middleColorPosition = middleColorPosition;
        clone.topColor = topColor.clone();
        clone.middleColor = middleColor.clone();
        clone.bottomColor = bottomColor.clone();
        return clone;
    }

    @Override
    public void write(JmeExporter ex) throws IOException {
        super.write(ex);
        OutputCapsule oc = ex.getCapsule(this);
        oc.write(fogDistance, "fogDistance", 1000);
        oc.write(fogIntensity, "fogIntensity", 1.5f);
        oc.write(fogGradient, "fogGradient", null);
        oc.write(middleColorPosition, "middleColorPosition", 0.5f);
        oc.write(topColor, "topColor", null);
        oc.write(middleColor, "middleColor", null);
        oc.write(bottomColor, "bottomColor", null);

    }

    @Override
    public void read(JmeImporter im) throws IOException {
        super.read(im);
        InputCapsule ic = im.getCapsule(this);
        fogDistance = ic.readFloat("fogDistance", 1000);
        fogIntensity = ic.readFloat("fogIntensity", 1.5f);
        fogGradient = (Texture) ic.readSavable("fogGradient", null);
        
        middleColorPosition = ic.readFloat("middleColorPosition", 0.5f);
        
        topColor = (ColorRGBA) ic.readSavable("topColor", null);
        middleColor = (ColorRGBA) ic.readSavable("middleColor", null);
        bottomColor = (ColorRGBA) ic.readSavable("bottomColor", null);

    }

}

Like, in world space?

Going from screen space to world space from a post processing filter is tricky math. My suspicion is the problem is there. Especially if you just thought “What is he talking about?!?”

1 Like

Hehehe, exactly.

I decided to go with the awt option which is to generate the gradiant texture dynamically on color change.
There is now an option of 4 colors in the gradient texture of which the middle two x positions can be adjusted.
Here is it in action:

Thanks @Ali_RS for the help and providing the shader.

3 Likes

Congrats, looks awesome indeed, let us know more about how you implemented it, it will be cool maybe to try and offer this to Jme-3.7.

1 Like

Looks nice

By the way,

I think using the terms top and down is wrong here because the filter does not work in a top-down manner (vertically) but it works in a depth-based manner (unless you have modified the shader) so I think you should call them “near”, “mid near”, “mid far”, and “far” colors.

Edit:
Also curious to know how you convert BufferedImage generated by AWT to JME image. Do you perhaps temporarily save BufferedImage to a png file and load it via asset manager?

1 Like

I thought this was well known information… in the sense that I half-remembered you already point this out to someone (but I admit I read a lot of traffic).

https://javadoc.jmonkeyengine.org/v3.5.2-stable/com/jme3/texture/plugins/AWTLoader.html#load-java.awt.image.BufferedImage-boolean-

1 Like

Hehe, right!

I thought that was a private method but Javadoc says otherwise, I must have re-checked it before I ask :slightly_smiling_face:

I think I will rename it, your names are better.
Will post the code here when finished.

2 Likes

Here is the final code I have for gradiant fog filter which contains 4 colors for the gradients.

MaterialDef GradientFog {
    MaterialParameters {
        Int NumSamples
        Int NumSamplesDepth
        Texture2D Texture
        Texture2D DepthTexture
        Texture2D FogGradient
        Float FogIntensity
        Float FogDistance
    }

    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"

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

uniform sampler2D m_FogGradient;
uniform float m_FogIntensity;
uniform float m_FogDistance;

const float LOG2 = 1.442695;

void main(){
    vec2 frustumNearFar = vec2(1.0, m_FogDistance);
    vec4 color = getColor(m_Texture, texCoord);
    float fogVal = getDepth(m_DepthTexture, texCoord).r;
    float depth = (2.0 * frustumNearFar.x) / (frustumNearFar.y + frustumNearFar.x - fogVal * (frustumNearFar.y - frustumNearFar.x));

    float fogFactor = exp2(-m_FogIntensity * m_FogIntensity * depth *  depth * LOG2);
    fogFactor = 1.0 - clamp(fogFactor, 0.0, 1.0);

    //vec4 fogColor = texture(m_FogGradient, vec2(depth, 0.5));
    vec4 fogColor = texture(m_FogGradient, vec2(fogFactor, 0.5));
    
    color.rgb = mix(color.rgb, fogColor.rgb, fogFactor);    

    gl_FragColor = color;
}

JAVA CODE:

package com.bruynhuis.envision.filters;

import com.bruynhuis.envision.utils.MaterialUtils;
import com.jme3.asset.AssetManager;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.post.Filter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.image.ColorSpace;
import com.jme3.util.BufferUtils;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.ByteBuffer;
import jme3tools.converters.ImageToAwt;

/**
 *
 * @author ndebruyn
 */
public class GradientFogFilter extends Filter {

    private Texture fogGradient;
    private Texture colorGradient;
    private float fogIntensity = 1.5f;
    private float fogDistance = 1000;
    private float midFarPosition = 0.7f;
    private float midNearPosition = 0.3f;
    private ColorRGBA farColor = new ColorRGBA(0.2f, 0.2f, 0.8f, 0.2f);
    private ColorRGBA midFarColor = new ColorRGBA(0.8f, 0.2f, 0.2f, 1f);
    private ColorRGBA midNearColor = new ColorRGBA(0.8f, 0.2f, 0.2f, 1f);
    private ColorRGBA nearColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 0.5f);
    private float margin = 0.05f;

    @Override
    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
        material = new Material(manager, "MatDefs/Post/GradientFog.j3md");

        material.setFloat("FogIntensity", fogIntensity);
        material.setFloat("FogDistance", fogDistance);

        updateGradientTexture();
    }

    @Override
    protected Material getMaterial() {
        return material;
    }

    @Override
    protected boolean isRequiresDepthTexture() {
        return true;
    }

    public Texture getFogGradient() {
        if (fogGradient == null) {
            return colorGradient;
        }
        return fogGradient;
    }

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

        this.fogGradient = fogGradient;
        this.colorGradient = null;
    }

    public float getFogIntensity() {
        return fogIntensity;
    }

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

    public float getFogDistance() {
        return fogDistance;
    }

    public void setFogDistance(float fogDistance) {
        if (material != null) {
            material.setFloat("FogDistance", fogDistance);
        }

        this.fogDistance = fogDistance;
    }

    public ColorRGBA getFarColor() {
        return farColor;
    }

    public void setFarColor(ColorRGBA farColor) {
        this.farColor = farColor;
        this.updateGradientTexture();
    }

    public ColorRGBA getNearColor() {
        return nearColor;
    }

    public void setNearColor(ColorRGBA nearColor) {
        this.nearColor = nearColor;
        this.updateGradientTexture();
    }

    public Texture getColorGradient() {
        return colorGradient;
    }

    public void setColorGradient(Texture colorGradient) {
        if (material != null) {
            material.setTexture("FogGradient", colorGradient);
        }

        this.fogGradient = null;
        this.colorGradient = colorGradient;
    }

    public float getMidFarPosition() {
        return midFarPosition;
    }

    public void setMidFarPosition(float midFarPosition) {
        if (midFarPosition < (midNearPosition + margin)) {
            midFarPosition = midNearPosition + margin;

        }

        this.midFarPosition = midFarPosition;
        this.updateGradientTexture();
    }

    public float getMidNearPosition() {
        return midNearPosition;
    }

    public float getMargin() {
        return margin;
    }

    public void setMargin(float margin) {
        this.margin = margin;
    }

    public void setMidNearPosition(float midNearPosition) {
        if (midNearPosition > (midFarPosition - margin)) {
            midNearPosition = midFarPosition - margin;

        }
        this.midNearPosition = midNearPosition;
        this.updateGradientTexture();
    }

    public ColorRGBA getMidFarColor() {
        return midFarColor;
    }

    public void setMidFarColor(ColorRGBA midFarColor) {
        this.midFarColor = midFarColor;
        this.updateGradientTexture();
    }

    public ColorRGBA getMidNearColor() {
        return midNearColor;
    }

    public void setMidNearColor(ColorRGBA midNearColor) {
        this.midNearColor = midNearColor;
        this.updateGradientTexture();
    }

    private void updateGradientTexture() {

        BufferedImage image = new BufferedImage(100, 10, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = image.createGraphics();

        Point2D start = new Point2D.Float(0, 0);
        Point2D end = new Point2D.Float(100, 0);

        float[] fractions = {0.0f, midNearPosition, midFarPosition, 1.0f};
        Color[] colors = {
            MaterialUtils.convertColor(nearColor),
            MaterialUtils.convertColor(midNearColor),
            MaterialUtils.convertColor(midFarColor),
            MaterialUtils.convertColor(farColor)
        };

        g2.setPaint(new LinearGradientPaint(start, end, fractions, colors));
        g2.fillRect(0, 0, 100, 10);
        g2.dispose();

        ByteBuffer buf = BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * 4);
        ImageToAwt.convert(image, Image.Format.ARGB8, buf);

        Image i = new Image(Image.Format.ARGB8, image.getWidth(), image.getHeight(), buf, ColorSpace.Linear);
        colorGradient = new Texture2D(i);
        setColorGradient(colorGradient);

    }

    @Override
    public GradientFogFilter clone() {
        GradientFogFilter clone = new GradientFogFilter();
        clone.fogDistance = fogDistance;
        clone.fogIntensity = fogIntensity;
        clone.fogGradient = fogGradient;
        clone.midNearPosition = midNearPosition;
        clone.midFarPosition = midFarPosition;
        clone.farColor = farColor.clone();
        clone.midNearColor = midNearColor.clone();
        clone.midFarColor = midFarColor.clone();
        clone.nearColor = nearColor.clone();
        return clone;
    }

    @Override
    public void write(JmeExporter ex) throws IOException {
        super.write(ex);
        OutputCapsule oc = ex.getCapsule(this);
        oc.write(fogDistance, "fogDistance", 1000);
        oc.write(fogIntensity, "fogIntensity", 1.5f);
        oc.write(fogGradient, "fogGradient", null);
        oc.write(midNearPosition, "midNearPosition", 0.7f);
        oc.write(midFarPosition, "midFarPosition", 0.3f);
        oc.write(farColor, "farColor", null);
        oc.write(midNearColor, "midNearColor", null);
        oc.write(midFarColor, "midFarColor", null);
        oc.write(nearColor, "nearColor", null);

    }

    @Override
    public void read(JmeImporter im) throws IOException {
        super.read(im);
        InputCapsule ic = im.getCapsule(this);
        fogDistance = ic.readFloat("fogDistance", 1000);
        fogIntensity = ic.readFloat("fogIntensity", 1.5f);
        fogGradient = (Texture) ic.readSavable("fogGradient", null);

        midNearPosition = ic.readFloat("midNearPosition", 0.7f);
        midFarPosition = ic.readFloat("midFarPosition", 0.3f);

        farColor = (ColorRGBA) ic.readSavable("farColor", null);
        midNearColor = (ColorRGBA) ic.readSavable("midNearColor", null);
        midFarColor = (ColorRGBA) ic.readSavable("midFarColor", null);
        nearColor = (ColorRGBA) ic.readSavable("nearColor", null);

        this.updateGradientTexture();

    }

}

3 Likes

Cool,

By the way, for this to work on Android I think you need to add a dependency

implementation 'ro.andob.androidawt:androidawt:1.0.4'
2 Likes