GLSL Dissolve Shader Documentation

I wrote a dissolve shader to use in mTheroyGame, and shared it with the jME community, this is all the supporting documention I drew up. It’s a bit of an insight into behind the scenes of working on the game. Documentation takes far longer than developing of the shader itself but its just part of development.

The Dissolve Shader uses a simple grey scale image as an animated mask to hide a material. It’s internal workings are deviously simple but immensely powerful.

Test Project output:

Starting at the top left we have :

  • simple linear dissolve
  • organic dissolve
  • pixel desolve

and bottom row :

  • organic growth
  • texture masking
  • organic burn

screenshot of the test and all the texture maps used in the test, layed out to reflect their usage in within the test scene

The best way to get an understanding of what this is and how it works, is to first watch the video while paying close attention to the top left linear dissolve, then look at the linear dissolve map in the graphic above… done that – see what I’m saying ?


Test Project Setup

The test is occolating the dissolve amount between 0 and 1. It demonstrates 6 different uses for the shader, all running at the same speed. The top row are straight forward dissolves. The bottom row shows 3 potential applications:

  • Organic Growth (bottom left) over a mesh, this could work both animating rapidly for a fast grow effect, or set to a fixed value e.g. set to 0.5f is “50% covered in growth”;
  • Texture Masking (bottom middle) , I see this is probably where the most practical applications will come from. The demonstration shows a poorely photoshoped clean street, peices of garbage are then scattered around dependant on the dissolve amount, this would work best with a fixed value eg set to .75 is “75% dirty”. Texture Masking could be also be used for:
    –  paint damage on a car;
    –  lacerations on a character;
    –  the blood shot eye effect that creeps in from the sides of the screen when you’ve taken too much damage in a modern FPS.
  • Organic Burn (bottom right) is comprised of 2 cubes, one blue, one orange, both with the same organic dissolve, however the orange one is slightly offset ahead of the blue so it shows first (ie the dissolve amount is always slight advanced).


The shader requires 2 parameters:

  • a Texture2D texture map to use as the dissolve map; and
  • a Vector2 of internal params params:
    –  the first a float value being the amount of dissolve, a value from 0-1 : 0 being no dissolve, being fully dissolved; and
    –  the second value is an int use as an inversion switch, 1 to invert the dissolve/discard, 0 to leave as is.

How it Works

The shader incrementally clamps off the colour value, dark to light, and uses that for a masking texture to discard pixels. It is currently capped for convenience at 255 frames of animation and is only using one colour channel. In simple terms, in starts by only discarding the darkest parts of the texture map, then the slightly lighter parts, then the slightly lighter again and again until it eventually cant get any lighter (white), at which point the proccess is complete.



Performance is cheakily quicker than the orignal Lighting material, because frags are getting discarded before any additional lighting processing is being applied, but this will fluctuate depending on how much of an object is being discarded which is something to be very weary of, and is actually a bit crap on my behalf – slower reliable performance is far more desirable than fast fluky performance.


Future plans

There are still 3 colour channels in a normal image left to play with, I havent decided how to utilise them yet but they could be used to extend the length of the animtion, allow more complex animation frames, enable alpha fading rather than a binary discard, impose max and min value limits, several animations per image, more comprehensive map data (eg map + inv map + alt map + alt inv map), reverse animation flag…. not really sure yet.





I have included a full download of the source and assets for the test project.

The shader is currently piggy backed on the back of the standard Lighting material shader (until I can figure a more efficient method than one behemoth material to rule them all).

Here is a list of the changes made to the standard lighting material, the source download includes an already customized variant.



        // the env map is a spheremap and not a cube map
        Boolean EnvMapAsSphereMap

+        // Dissolve Map
+        Texture2D DissolveMap
+        Vector2 DissolveParams;

    Technique {


            USE_REFLECTION : EnvMap
            SPHERE_MAP : SphereMap

+            DISSOLVE_MAP : DissolveMap
+            DISSOLVE_PARAMS : DissolveParams



    uniform ENVMAP m_EnvMap;

+    uniform sampler2D m_DissolveMap;
+        uniform vec2 m_DissolveParams;
+    #endif

float tangDot(in vec3 v1, in vec3 v2){


    newTexCoord = texCoord;

+    #ifdef DISSOLVE_MAP
+        if (texture2D(m_DissolveMap, newTexCoord).r < m_DissolveOptions.x && m_DissolveOptions.y == 0) {
+            discard;
+        }
+        if (texture2D(m_DissolveMap, newTexCoord).r > m_DissolveOptions.x && m_DissolveOptions.y == 1) {
+            discard;
+        }
+    #endif

   #ifdef DIFFUSEMAP
      vec4 diffuseColor = texture2D(m_DiffuseMap, newTexCoord);

package dissolvetest;

import com.jme3.material.Material;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.*;
import com.jme3.light.*;
import com.jme3.math.*;
import com.jme3.texture.*;

 * @author thetoucher

public class Main extends SimpleApplication {

    // speed of animation
    private float speed = .125f;

    private float count = 0;
    private int dir = 1;

    private Vector2f DSParams, DSParamsInv, DSParamsBurn;

    public static void main(String[] args) {
        Main app = new Main();

    public void simpleInitApp() {

        Texture t;
        Material mat;

        cam.setLocation(new Vector3f(0,1.5f,10f));

        // reusable params
        DSParams = new Vector2f(0,0); // standard
        DSParamsInv = new Vector2f(0,1); // inverted
        DSParamsBurn = new Vector2f(0,0); // used for offset organic burn map

        // linear dissolve
        addTestCube(-3f,3f,assetManager.loadTexture("Textures/linear.png"), DSParams);

        // organic dissolve
        addTestCube(0,3f,assetManager.loadTexture("Textures/burnMap.png"), DSParamsInv);

        // pixel dissolve
        t = assetManager.loadTexture("Textures/pixelMap.png");
        t.setMagFilter(Texture.MagFilter.Nearest); // this is needed to retain the crisp pixelated look
        addTestCube(3f, 3f, t, DSParams);        

        // organic growth
        mat = addTestCube(-3f,0,assetManager.loadTexture("Textures/growMap.png"), DSParamsInv).getMaterial();
        mat.setColor("Ambient", ColorRGBA.Green);
        mat.setTexture("DiffuseMap", assetManager.loadTexture("Textures/growMap.png"));

        addTestCube(-3f,0,assetManager.loadTexture("Textures/growMap.png"), DSParams);

        // texture mask
        mat = addTestCube(0,0,assetManager.loadTexture("Textures/streetBurn.png"), DSParams).getMaterial();
        mat.setTexture("DiffuseMap", assetManager.loadTexture("Textures/streetClean.png"));
        mat.setColor("Ambient",  ColorRGBA.White);

        mat = addTestCube(0f,0f,assetManager.loadTexture("Textures/streetBurn.png"), DSParamsInv).getMaterial();
        mat.setTexture("DiffuseMap", assetManager.loadTexture("Textures/street.png"));
        mat.setColor("Ambient",  ColorRGBA.White);

        // organic burn
        addTestCube(3f, 0, assetManager.loadTexture("Textures/burnMap.png"), DSParamsBurn).getMaterial().setColor("Ambient",  ColorRGBA.Red);
        addTestCube(3f, 0, assetManager.loadTexture("Textures/burnMap.png"), DSParams);

        AmbientLight a = new AmbientLight();


    private Geometry addTestCube(float xPos, float yPos, Texture map, Vector2f DSParams) {
        Box b = new Box(Vector3f.ZERO, 1, 1, 1);
        Geometry geom = new Geometry("Box", b);
        geom.setLocalTranslation(new Vector3f(xPos,yPos,0));

        Material mat = new Material(assetManager, "Materials/Lighting.j3md");
        mat.setColor("Ambient",  ColorRGBA.Blue);
        mat.setColor("Diffuse",  ColorRGBA.White);
        mat.setColor("Specular", ColorRGBA.Black);
        mat.setBoolean("UseMaterialColors", true);
        mat.setTexture("DissolveMap", map);
        mat.setVector2("DissolveParams", DSParams);


        return geom;

    public void simpleUpdate(float tpf) {


       // animation ossolation
       if (count > 1f) {
           dir = -1;
       } else if (count < 0) {
           dir = 1;

       // update the dissolve amounts