Dissolve Shader

Hi guys, I apologies if the video isn’t embedding properly, it didn’t work yesterday so I tried to hack around the forum bugs, if you can’t see the video, please watch it on my site before reading the rest of the post, thanks -James :slight_smile:

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:

(This is where the video should be, the YouTube id is ry0r_qwFQLQ if you cant see it and need to hunt it out)

Starting at the top left we have :
- simple linear dissolve
- organic dissolve
- pixel dissolve

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:

1) 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";

2) 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.

3) 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).

Wordpress has raped the layout of the code sorry, making it difficult to read... it may be best just to just look at the source.

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

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

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

[patch] Technique {

SPHERE_MAP : SphereMap

+ DISSOLVE_MAP : DissolveMap
+ DISSOLVE_PARAMS : DissolveParams

[patch] uniform ENVMAP m_EnvMap;

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

float tangDot(in vec3 v1, in vec3 v2){ [/patch]

newTexCoord = texCoord;

+ if (texture2D(m_DissolveMap, newTexCoord).r < m_DissolveOptions.x && m_DissolveOptions.y == 0.0) {
+ discard;
+ }
+ if (texture2D(m_DissolveMap, newTexCoord).r > m_DissolveOptions.x && m_DissolveOptions.y == 1.0) {
+ discard;
+ }
+ #endif

vec4 diffuseColor = texture2D(m_DiffuseMap, newTexCoord);[/patch]

package dissolvetest;

import com.jme3.app.SimpleApplication;
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


nehon said:
shader injection


Looks very cool, thanks!

Wow, amazing work! :slight_smile: Will look into it tomorrow, thanks a lot for sharing.

thanks for the detailed explanation!

thetoucher said:
newTexCoord = texCoord;

+ if (texture2D(m_DissolveMap, newTexCoord).r &lt; m_DissolveOptions.x &amp;&amp; m_DissolveOptions.y == 0) {
+ discard;
+ }
+ if (texture2D(m_DissolveMap, newTexCoord).r &gt; m_DissolveOptions.x &amp;&amp; m_DissolveOptions.y == 1) {
+ discard;
+ }
+ #endif

I almost want to say something about the two texture look-ups where one would do... but really I want to point out that the 0 and the 1 above should be 0.0 and 1.0 or this will fail on many platforms.

Thanks @pspeed, I believe I had already fixed the ‘.0’ issue in the download, forgot to fix it here. I think its mostly (or all) ATI cards that crack it if you leave off the ‘.0’.

When I reauthored all the code into the test project for sharing, I was too focused on keeping the code simple and understandable, totally overlooked the double lookup, Cheers.

The .0 thing will bite you an all Macs I think, also. OSX is stricter in general.

thanks for sharing, maybe later i will get to use it :slight_smile:

Really nice work!

Thank you for sharing this.

@Momoko_Fan This and rim lighting would be nice additions to lighting and unshaded shaders, also they are more effects than shading, and adding them would bloat the shaders once again…We should really all (the core team) seat around the table jaconda will do) and talk about this shader injection idea of yours and finally get it into the core.

1 Like

Kirill…was Einstein afraid when he invented the atomic bomb? … ok he should have…bad example…

Was Zuckerberg afraid when he invented FaceBook???NO (he wasn’t, i’ve seen the movie) !! and now he’s a billionaire!! take your chance dude!!

later when i got time

@thetoucher , thanks for your shader. I have added it to my library.


@pspeed , could you paste your changes you did above to pastebin.com? The forum code is not readable.

I don’t think I posted any code above that wasn’t just a quote of someone else.

Sorry, I get you…