Live reload of material for shader dev

So following up this conversation http://hub.jmonkeyengine.org/forum/topic/re-compile-shaders-during-runtime/
I added a new appstate to the engine : the MaterialDebugAppState

It handles material hot reload of materials at runtime. Meaning that you don’t have to stop the app and relaunch it every time you make a modification to a shader file.
If your modification results in a shader compilation error, the App state will catch it and display it in the console, but won’t reload the material avoiding the app to crash.

It handles reloading materials for any Spatial or any Filter.
There might be still some issues with filters when you have multiples passes (maybe not, but I didn’t test it thoroughly so I can’t tell).

You can register a binding with a Trigger (most probably a KeyTrigger) and a Spatial. Whenever the trigger is fired (the key is hit), the Spatial’s materials (works also for a node with sub geoms) will be reloaded.
You can do the same with a Filter instead of a Spatial, and all the materials of the Filter will be reloaded

You can also register a binding with a file (that the asset manager can locate, for example in your assets folder) and a Spatial or a Filter. The reload will be fired whenever the file is changed on the hard drive.
That method is pretty cool, because it allows live updates of the shader at runtime. I’ll post a video tomorrow.

here is an example code to use it.

import com.jme3.app.ChaseCameraAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.post.FilterPostProcessor;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import com.jme3.util.MaterialDebugAppState;
 
public class TestMaterialHotReload extends SimpleApplication {
 
    public static void main(String[] args) {
        TestMaterialHotReload app = new TestMaterialHotReload();
 
        //ignore this, it’s just for convenience
        AppSettings settings = new AppSettings(true);
        settings.setFrameRate(30);
        settings.setResolution(640, 480);
        app.setSettings(settings);
        app.setShowSettings(false);
        app.setPauseOnLostFocus(false); 
        app.start();
    }
 
    @Override
    public void simpleInitApp() {
 
        //a box geom that will have the material we want to test
        final Geometry boxGeo = new Geometry("Box", new Box(1f, 1f, 1f));
        rootNode.attachChild(boxGeo);
        //create the material
        final Material mat = createMaterial();
        boxGeo.setMaterial(mat);
 
        //ignore this, it’s just for convenience
        flyCam.setEnabled(false);
        ChaseCameraAppState chaseCam = new ChaseCameraAppState();
        chaseCam.setTarget(boxGeo);
        stateManager.attach(chaseCam);
        
        MaterialDebugAppState matDebug = new MaterialDebugAppState();
        
        // you can do that if you are lazy, it will be a bit slower, but it 
        //reload all the materials of the scenegraph when pressing R
        matDebug.registerBinding(new KeyTrigger(KeyInput.KEY_R), rootNode);  
        //will reload boxgeo's material whenever the frag file is changed
        matDebug.registerBinding("Shaders/distort.frag", boxGeo); 
        //will reload boxgeo's material whenever the vert file is changed
        matDebug.registerBinding("Shaders/distort.vert", boxGeo); 
        
        FilterPostProcessor p = new FilterPostProcessor(assetManager);
        //this is an example filter put your own
        MyColorOverlayFilter f = new MyColorOverlayFilter();
        p.addFilter(f);
        
        viewPort.addProcessor(p);
        //will reload the filter whenever the F key is hit
        matDebug.registerBinding(new KeyTrigger(KeyInput.KEY_F),  f);
        //will reload filter whenever the frag file is changed
        matDebug.registerBinding("MatDefs/Overlay15.frag", f); 
        //will reload filter whenever the frag file is changed
        matDebug.registerBinding("MatDefs/Overlay.frag", f); 
        
        stateManager.attach(matDebug);
    } 

    /**
     * Creates the material, use whatever material that uses the shader you are
     * coding
     *
     * @return
     */
    protected Material createMaterial() {
        //this is an example material. Put your own
        Material mat = new Material(assetManager,"MatDefs/distort.j3md");
        mat.setTexture("ColorMap", assetManager.loadTexture("com/jme3/app/Monkey.png"));
        mat.setBoolean("UseDistort", true);
        return mat;
    }
 
}

have fun

13 Likes