[SOLVED] GrayScale Filter

Hi,



Looking for how to achieve effects using shaders, I came to create a grayscale filter. If anyone wants to use it, feel free and do it!!



GrayScale Filter Off



GrayScale Filter On



Filter: GrayScaleFilter.java

[java]

import com.jme3.asset.AssetManager;

import com.jme3.material.Material;

import com.jme3.post.Filter;

import com.jme3.renderer.RenderManager;

import com.jme3.renderer.ViewPort;



public class GrayScaleFilter extends Filter {



@Override

protected Material getMaterial() {

return this.material;

}



@Override

protected void initFilter(final AssetManager manager, final RenderManager renderManager, final ViewPort vp,

final int w, final int h) {

this.material = new Material(manager, “Common/MatDefs/Post/GrayScale.j3md”);

}



}

[/java]



Material Def: GrayScale.j3md

[java]

MaterialDef GrayScale {



MaterialParameters {

Int NumSamples

Texture2D Texture

}



Technique {

VertexShader GLSL150: Common/MatDefs/Post/Post15.vert

FragmentShader GLSL150: Common/MatDefs/Post/GrayScale.frag



WorldParameters {

WorldViewProjectionMatrix

}

}



Technique {

VertexShader GLSL100: Common/MatDefs/Post/Post.vert

FragmentShader GLSL100: Common/MatDefs/Post/GrayScale.frag



WorldParameters {

WorldViewProjectionMatrix

}

}

}

[/java]



Frag Shader: GrayScale.frag

[java]

uniform sampler2D m_Texture;



varying vec2 texCoord;



void main() {



// Convert to grayscale

vec3 colour = texture2D(m_Texture, texCoord).xyz;

float gray = (colour.x + colour.y + colour.z) / 3.0;

vec3 grayscale = vec3(gray);



gl_FragColor = vec4(grayscale, 1.0);

}

[/java]



Test Case: TestGrayScale.java

[java]

import java.io.File;



import com.jme3.app.SimpleApplication;

import com.jme3.asset.plugins.HttpZipLocator;

import com.jme3.asset.plugins.ZipLocator;

import com.jme3.input.KeyInput;

import com.jme3.input.controls.ActionListener;

import com.jme3.input.controls.KeyTrigger;

import com.jme3.light.DirectionalLight;

import com.jme3.math.ColorRGBA;

import com.jme3.math.Quaternion;

import com.jme3.math.Vector3f;

import com.jme3.post.FilterPostProcessor;

import com.jme3.scene.Node;

import com.jme3.scene.Spatial;

import com.jme3.util.SkyFactory;



public class TestGrayScale extends SimpleApplication {



private FilterPostProcessor fpp;

private boolean enabled = true;

private GrayScaleFilter grayScale;



// set default for applets

private static boolean useHttp = true;



public static void main(final String[] args) {

final File file = new File(“wildhouse.zip”);

if (file.exists()) {

TestGrayScale.useHttp = false;

}

final TestGrayScale app = new TestGrayScale();

app.start();

}



@Override

public void simpleInitApp() {

this.flyCam.setMoveSpeed(10);

final Node mainScene = new Node();

this.cam.setLocation(new Vector3f(-27.0f, 1.0f, 75.0f));

this.cam.setRotation(new Quaternion(0.03f, 0.9f, 0f, 0.4f));



// load sky

mainScene.attachChild(SkyFactory.createSky(this.assetManager, “Textures/Sky/Bright/BrightSky.dds”, false));



// create the geometry and attach it

// load the level from zip or http zip

if (TestGrayScale.useHttp) {

this.assetManager.registerLocator(“http://jmonkeyengine.googlecode.com/files/wildhouse.zip”,

HttpZipLocator.class);

} else {

this.assetManager.registerLocator(“wildhouse.zip”, ZipLocator.class);

}

final Spatial scene = this.assetManager.loadModel(“main.scene”);



final DirectionalLight sun = new DirectionalLight();

final Vector3f lightDir = new Vector3f(-0.37352666f, -0.50444174f, -0.7784704f);

sun.setDirection(lightDir);

sun.setColor(ColorRGBA.White.clone().multLocal(2));

scene.addLight(sun);



mainScene.attachChild(scene);

this.rootNode.attachChild(mainScene);



this.fpp = new FilterPostProcessor(this.assetManager);

this.fpp.setNumSamples(4);

this.grayScale = new GrayScaleFilter();

this.fpp.addFilter(this.grayScale);

this.viewPort.addProcessor(this.fpp);

this.initInputs();

}



private void initInputs() {

this.inputManager.addMapping(“toggle”, new KeyTrigger(KeyInput.KEY_SPACE));



final ActionListener acl = new ActionListener() {



@Override

public void onAction(final String name, final boolean keyPressed, final float tpf) {

if (name.equals(“toggle”) && keyPressed) {

if (TestGrayScale.this.enabled) {

TestGrayScale.this.enabled = false;

TestGrayScale.this.viewPort.removeProcessor(TestGrayScale.this.fpp);

} else {

TestGrayScale.this.enabled = true;

TestGrayScale.this.viewPort.addProcessor(TestGrayScale.this.fpp);

}

}



}

};



this.inputManager.addListener(acl, “toggle”);



}

}

[/java]

5 Likes

It works fine on my laptop (Intel graphic card → old and very slow) but it does not work on my desk pc (sapphire hd 6970 graphic card → new and very fast) :cry:

I see a gray screen (same color everywhere).



Help!!! anyone

I’ve found the issue. The line 66 on TestGrayScale.java is the cause of the issue:

[java]this.fpp.setNumSamples(4);[/java]

If I delete/comment this line, all works fine.



It seems like setting up FilterPostProcessor’s Antialiasing is causing that texture2D(m_Texture, texCoord) does not work properly I think. But on FogFilter is working fine… I don’t know… I’m very new on shader so I’m not sure about that. :slight_smile: I need to read and learn more about shaders

Yeah you have to make your shader support multisampling.

All core filters have 2 versions : classic and a glsl 1.5 version that supports multisampling.



you should have a grayScale15.frag that does it.

this should work.

[java]

#import "Common/ShaderLib/MultiSample.glsllib"



uniform COLORTEXTURE m_Texture;



in vec2 texCoord;

out vec4 fragColor;



void main() {



// Convert to grayscale

vec3 colour = getColor(m_Texture, texCoord).xyz;

float gray = (colour.x + colour.y + colour.z) / 3.0;

vec3 grayscale = vec3(gray);



fragColor = vec4(grayscale, 1.0);

}

[/java]



also your j3md should look like that

[java]

MaterialDef GrayScale {



MaterialParameters {

Int NumSamples

Texture2D Texture

}



Technique {

VertexShader GLSL150: Common/MatDefs/Post/Post15.vert

FragmentShader GLSL150: Common/MatDefs/Post/GrayScale15.frag



WorldParameters {

WorldViewProjectionMatrix

}



Defines {

RESOLVE_MS : NumSamples

}

}



Technique {

VertexShader GLSL100: Common/MatDefs/Post/Post.vert

FragmentShader GLSL100: Common/MatDefs/Post/GrayScale.frag



WorldParameters {

WorldViewProjectionMatrix

}

}

}

[/java]

2 Likes

Thanks a lot @nehon

That works great!

@H , i added your filter to ShaderBlow lib. Thanks a lot!



http://code.google.com/p/jme-glsl-shaders/source/detail?r=0af4d0a639090235d36b16e828dba12a265ecbd0

@nehon , i see not all filters have this line in JME:



[java]



out vec4 outFragColor;

[/java]



When should it be used for frag15?

gl_FragColor is deprecated in glsl 1.5. Now you have to declare output variable and assign them.

We may have missed some gl_FragColor in some 1.5 shaders though.

1 Like

Thank you. Now I understand it.



Should we use the same line in shaders less than glsl1.5 or use “gl_FragColor” for them?

@mifth said:
Should we use the same line in shaders less than glsl1.5 or use "gl_FragColor" for them?

To be exact you can use the in/out qualifier for a variable since glsl1.3.
varying and attribute qualifiers are deprecated, same for gl_FragColor. (i'm probably missing some)

So have to use gl_FragColor for any shader that has to be compliant with glsl version before 1.3.
1 Like
@mifth said:
@H , i added your filter to ShaderBlow lib. Thanks a lot!

http://code.google.com/p/jme-glsl-shaders/source/detail?r=0af4d0a639090235d36b16e828dba12a265ecbd0

Nice! I'm happy to contribute with something!!

I modified the filter and had it run on two passes to get certain spatials to ignore the grayscale. You get a pretty cool noir effect as a result





Slime Noir

2 Likes
@Sploreg said:
I modified the filter and had it run on two passes to get certain spatials to ignore the grayscale. You get a pretty cool noir effect as a result


Slime Noir


oh snap care to share
@Sploreg said:
I modified the filter and had it run on two passes to get certain spatials to ignore the grayscale. You get a pretty cool noir effect as a result

Slime Noir


very nice!!!

I was panning to implement this effect but I don't know how to do it yet ;). Please, can you share the code?

Sure thing.

5 files, and a small change to your existing Lighting material definition.



Your material definition (lighting.j3md)

[java]

MaterialParameters {

…

// other material parameters for your material

…

// if true, it will be affected by the grayscale filter

Boolean NotGrayFiltered : false



…

Technique Gray {



VertexShader GLSL100: Common/MatDefs/Misc/Unshaded.vert

FragmentShader GLSL100: Shaders/Gray.frag



WorldParameters {

WorldViewProjectionMatrix

}



Defines {

IS_NOTGRAY : NotGrayFiltered

}

}

}

[/java]



GrayScaleFilter.j3md

[java]

MaterialDef GrayScale {



MaterialParameters {

Int NumSamples

Texture2D Texture

Texture2D GrayTex

}



Technique {

VertexShader GLSL150: Common/MatDefs/Post/Post15.vert

FragmentShader GLSL150: Shaders/GrayScaleFilter15.frag



WorldParameters {

WorldViewProjectionMatrix

}



Defines {

RESOLVE_MS : NumSamples

}

}



Technique {

VertexShader GLSL100: Common/MatDefs/Post/Post.vert

FragmentShader GLSL100: Shaders/GrayScaleFilter.frag



WorldParameters {

WorldViewProjectionMatrix

}

}

}

[/java]



GrayScaleFilter15.frag

[java]

#import "Common/ShaderLib/MultiSample.glsllib"



uniform COLORTEXTURE m_Texture;

uniform sampler2D m_GrayTex;



in vec2 texCoord;

out vec4 fragColor;



void main() {



vec4 grayT = texture2D(m_GrayTex, texCoord);



// Convert to grayscale

vec3 color = getColor(m_Texture, texCoord).xyz;

float gray = (color.x + color.y + color.z) / 3.0;

vec3 grayscale = vec3(gray);



if (grayT.r > 0.0) {

fragColor = vec4(color, 1.0);

} else {

fragColor = vec4(grayscale, 1.0);

}

}[/java]



GrayScaleFilter.frag

[java]uniform sampler2D m_Texture;



varying vec2 texCoord;

uniform sampler2D m_GrayTex;



void main() {



vec4 grayT = texture2D(m_GrayTex, texCoord);



// Convert to grayscale

vec3 color = texture2D(m_Texture, texCoord).xyz;

float gray = (color.x + color.y + color.z) / 3.0;

vec3 grayscale = vec3(gray);

if (grayT.r > 0.0) {

gl_FragColor = vec4(color, 1.0);

} else {

gl_FragColor = vec4(grayscale, 1.0);

}

}[/java]



Gray.frag

[java]

void main() {



#ifdef IS_NOTGRAY

gl_FragColor = vec4(1.0);

#else

gl_FragColor = vec4(0.0);

#endif

}

[/java]



GrayScaleFilter.java

[java]

package effects;



import com.jme3.asset.AssetManager;

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.renderer.queue.RenderQueue;

import com.jme3.texture.Image.Format;



/**

  • Renders the scene in grayscale, excluding any spatials that set the IS_NOTGRAY param.
  • By default, things will be grayscale.

    *
  • @author bowens

    */

    public class GrayScaleFilter extends Filter {



    private RenderManager renderManager;

    private ViewPort viewPort;

    private Pass preGrayPass;



    @Override

    protected Material getMaterial() {

    return this.material;

    }



    @Override

    protected void initFilter(final AssetManager manager, final RenderManager renderManager, final ViewPort vp,

    final int w, final int h) {



    this.renderManager = renderManager;

    this.viewPort = vp;



    preGrayPass = new Pass();

    preGrayPass.init(renderManager.getRenderer(), w, h, Format.RGBA8, Format.Depth);



    //final material

    this.material = new Material(manager, “Shaders/GrayScaleFilter.j3md”);

    material.setTexture(“GrayTex”, preGrayPass.getRenderedTexture());

    }



    @Override

    protected void postQueue(RenderQueue queue) {

    renderManager.getRenderer().setBackgroundColor(ColorRGBA.BlackNoAlpha);

    renderManager.getRenderer().setFrameBuffer(preGrayPass.getRenderFrameBuffer());

    renderManager.getRenderer().clearBuffers(true, true, true);

    renderManager.setForcedTechnique(“Gray”);

    renderManager.renderViewPortQueues(viewPort, false);

    renderManager.setForcedTechnique(null);

    renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer());

    }

    }

    [/java]



    And the usage:

    [java]

    // add the filter

    FilterPostProcessor fpp = new FilterPostProcessor(this.assetManager);

    fpp.setNumSamples(4);

    GrayScaleFilter grayScale = new GrayScaleFilter();

    fpp.addFilter(grayScale);

    viewPort.addProcessor(fpp);



    // make a spatial ignore the gray pass

    mySpatialMaterial.setBoolean(“NotGrayFiltered”, true);

    [/java]

    yes that parameter is a double negative, but I had to have it default to everything being gray if different materials are in the scene or if they have no param set to say that it should ignore the gray filter.



    To change your current materials you just have to add in the first piece of code above.



    Question for @nehon: with the Gray technique in the material definition, will jme automatically pick the v150 if I specify it with the same “Gray” technique name (ie. if I have the two same-named techniques, can jme pick the correct one)?
2 Likes

Thank @Sploreg

Hi @Sploreg



You have to share “FragmentShader GLSL100: Shaders/Gray.frag” file too in order to get all working fine I think :wink:

Whoops, that one is definitely needed :). Added it in my previous post.

Hi!
When I’m using the GrayScaleFilter, the gray SkyBox is not being reflected in its gray colors by the WaterFilter.
Image:

Test-Case:
[java]public class Start extends SimpleApplication {
public static void main(String[] args) {
Start app = new Start();
app.start();
}
@Override
public void simpleInitApp() {
cam.setLocation(new Vector3f(0, 100, 0));
rootNode.attachChild(SkyFactory.createSky(
assetManager,
assetManager.loadTexture(“Textures/sky_west.jpg”),
assetManager.loadTexture(“Textures/sky_east.jpg”),
assetManager.loadTexture(“Textures/sky_north.jpg”),
assetManager.loadTexture(“Textures/sky_south.jpg”),
assetManager.loadTexture(“Textures/sky_top.jpg”),
assetManager.loadTexture(“Textures/sky_bottom.jpg”)));
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
fpp.addFilter(new GrayScaleFilter());
fpp.addFilter(new WaterFilter(rootNode, Vector3f.ZERO));
viewPort.addProcessor(fpp);
}
}[/java]
Really not a big issue though, the filter itself is really cool :slight_smile:

BTW the problem is solved, if the WaterFilter is added to the FilterPostProcessor before the GrayScaleFilter.