Changing material shader program in realtime

Is it possible to change shaders and techniques on a material during runtime? While I can see how I can change everything else, still Idk how to change shaders. My aim is to be able to load shaders program texts from a database, so I need to know how to give that text to the material definition constructor instead of taking it from a file.

To change what shader is used just change the material.

You could also look at shader nodes…

If you really do need to generate shaders dynamically I image it is possible but I’ve no idea how you would go about it.

An easy workarround would probably be just writing the material and shader files at runtime and then create material from them, then you can sue standard mechanics again. Just need to decide where you want to store them and tell the assetmanager to include that location when looking for the files.

I want to be able to fetch shader programs from an independant database… probably even fetch entire material definitions. Writing to a disk and then reading is kinda a solution but it is very inefficient. Especially when it seems like a matter of just switching an InputStream or like…

Yeah, you shouldn’t need to write them to disk.

Take a look at the shader nodes stuff, you are really digging under the hood here though so I don’t know how well documented from an “end user” perspective this sort of thing is though.

Are you really sure you really really need really every shader to really be different…really?..rather than one shader (or a bunch of shader nodes) with material configuration wiring them up as required?

@noncom said: Is it possible to change shaders and techniques on a material during runtime? While I can see how I can change everything else, still Idk how to change shaders. My aim is to be able to load shaders program texts from a database, so I need to know how to give that text to the material definition constructor instead of taking it from a file.

http://hub.jmonkeyengine.org/javadoc/com/jme3/asset/AssetManager.html#registerLocator(java.lang.String,%20java.lang.Class)

@noncom :
I wish that you can come up with something :stuck_out_tongue:

Here is some basic setup for such loader:
[java]
public class InMemoryLocator implements AssetLocator {

public void setRootPath(String rootPath) {
    System.out.println(" " + rootPath);
}

public AssetInfo locate(AssetManager manager, AssetKey key) {
    //InMemoryAssetKey ikey = (InMemoryAssetKey) key;
    String realName = key.getName();
    realName = realName.replaceFirst("__inmemory::", "");
    realName = realName.replaceFirst(".foo", "");
    //String str = ikey.getData().getMap().get(realName);
    String str = MatPreviewApp.getMap().get(realName);
    if (str != null) {
        final InputStream is = new ByteArrayInputStream(str.getBytes());

        return new AssetInfo(manager, key) {
            @Override
            public InputStream openStream() {
                return is;
            }
        };
    } else {
        throw new RuntimeException("There is no in memory asset this name!" + key.getName());
    }
}

}
[/java]

[java]
public class MatPreviewApp extends SimpleApplication {

private static MatPreviewApp app;
private static AwtPanel panel;

private Material mat;
public static HashMap<String, String> map = new HashMap<String, String>();
String vertexShader = "uniform mat4 g_WorldViewProjectionMatrix;\n"
        + "\n"
        + "attribute vec3 inPosition;\n"
        + "attribute vec2 inTexCoord;\n"
        + "\n"
        + "varying vec2 texCoord;\n"
        + "\n"
        + "void main(){\n"
        + "    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);\n"
        + "    texCoord = inTexCoord;\n"
        + "}";
String fragmentShader = "varying vec2 texCoord;\n"
        + "\n"
        + "uniform sampler2D m_ColorMap;\n"
        + "uniform vec4 m_Color;\n"
        + "\n"
        + "void main(){\n"
        + "    vec4 texColor = texture2D(m_ColorMap, texCoord);\n"
        + "    gl_FragColor = vec4(mix(m_Color.rgb, texColor.rgb, texColor.a), 1.0);\n"
        + "    gl_FragColor = vec4(1,1,1,1);"
        + "}";
String vertLanguage = "GLSL100";
String fragLanguage = "GLSL100";

public MatPreviewApp() {
}
static HashMap<String,String> getMap() {
    return map;
}
public void startApp(final JPanel parent) {
    Logger.getLogger("com.jme3").setLevel(Level.WARNING);
    setShowSettings(false);
    AppSettings settings = new AppSettings(true);
    settings.setCustomRenderer(AwtPanelsContext.class);
    settings.setFrameRate(60);
    setSettings(settings);
    start();

    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            final AwtPanelsContext ctx = (AwtPanelsContext) getContext();
            panel = ctx.createPanel(PaintMode.Accelerated);
            panel.setPreferredSize(new Dimension(120, 120));
            ctx.setInputSource(panel);
            parent.add(panel);
        }
    });
}

@Override
public void simpleInitApp() {
    flyCam.setDragToRotate(true);
    flyCam.setEnabled(false);

    Box b = new Box(Vector3f.ZERO, 1, 1, 1);
    Geometry geom = new Geometry("Box", b);
    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    //mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
    testShaderLoad();
    mat = makeNewMatDef();
    mat.selectTechnique("DemoTech", renderManager);
    geom.setMaterial(mat);
    rootNode.attachChild(geom);

    if (panel != null) {
        panel.attachTo(true, viewPort);
    }
}

public static void main(String[] args) {
    Logger.getLogger("com.jme3").setLevel(Level.WARNING);
    MatPreviewApp app = new MatPreviewApp();
    app.setShowSettings(false);
    AppSettings settings = new AppSettings(true);
    settings.setFrameRate(60);
    app.setSettings(settings);
    app.start();
}

public Material makeNewMatDef() {

    mat = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
    TechniqueDef technique = new TechniqueDef("DemoTech");
    technique.setShaderFile("__inmemory::vs.foo", "__inmemory::fs.foo", vertLanguage, fragLanguage);

    mat.getMaterialDef().addTechniqueDef(technique);

    return mat;

}

public void makeNewMat() {
}

public void testShaderLoad() {

    map.put("vs", vertexShader);
    map.put("fs", fragmentShader);

    assetManager.registerLocator("__inmemory::", InMemoryLocator.class);
    assetManager.registerLoader(TextLoader.class, "foo");

    //String newStr = (String) assetManager.loadAsset("__inmemory::vs.foo");

    //System.out.println(" " + newStr);
}

public void testShaderInjection() {
}

}
[/java]

[java]
public class TextLoader implements AssetLoader {
public Object load(AssetInfo assetInfo) throws IOException {
Scanner scan = new Scanner(assetInfo.openStream());
StringBuilder sb = new StringBuilder();
try {
while (scan.hasNextLine()) {
sb.append(scan.nextLine()).append(’\n’);
}
} finally {
scan.close();
}
return sb.toString();
}
}
[/java]

[java]
public class InMemoryAssetKey<T> extends AssetKey<T> {
private final InMemoryData data;

public InMemoryAssetKey(InMemoryData data) {
    this.data = data;
}

public InMemoryData getData() {
    return this.data;
}

}
[/java]

[java]
public interface InMemoryData{
public HashMap<String,String> getMap();
}
[/java]

InMemoryData and InMemoryKey should be replaced with your implementation, in this example I used Map, but for a real system may be other solutions need (your imagination) .Let me know if you come up with something useful with a Shader system, in fact I got mine but it’s a WIP and also it’s more like a general code generation then a specific GLSL one.

Cheers,

…or if you are just reading from a database like you say then just have an asset locator that does the DB query and returns an AssetInfo that has an input stream for the data retrieved. Similar to the InMemoryLocator but querying the database instead.

@pspeed: Yeah, indeed!

<cite>@pspeed said:</cite> ...or if you are just reading from a database like you say then just have an asset locator that does the DB query and returns an AssetInfo that has an input stream for the data retrieved. Similar to the InMemoryLocator but querying the database instead.

In his situation AssetInfo is the best “spot” to change in order to build such system. My example is somehow more generic, in which you can tweak almost every part of the loader mechaism, let say prepare other resources and assets right before the shader load etc… Even if it’s not a shader, in my case something like a Dialogue script attached with Audio and facial notations, I do it like that.

But glad to hear any better solutions and usecases from you guys.

@atomix & @pspeed : thank you for the suggestions! They are very useful and give me a good hint. I am soon getting to implement this functionality in my app! I will let you know how it comes out then!