Ver código fonte

Adds a new java API for shader nodes

Nehon 7 anos atrás
pai
commit
b57ecf35ea

+ 12 - 8
jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java

@@ -275,13 +275,17 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
                     // one mapping for this variable, directly append the
                     // other variable from the mapping to the function call
                     VariableMapping m = maps.get(0);
-                    ShaderNodeVariable v2 = m.getRightVariable();
-                    b.append(getAppendableNameSpace(v2))
-                            .append(v2.getPrefix())
-                            .append(v2.getName());
-                    if (m.getRightSwizzling().length() > 0) {
-                        b.append(".");
-                        b.append(m.getRightSwizzling());
+                    if(m.getRightExpression()!=null) {
+                        b.append(m.getRightExpression());
+                    } else {
+                        ShaderNodeVariable v2 = m.getRightVariable();
+                        b.append(getAppendableNameSpace(v2))
+                                .append(v2.getPrefix())
+                                .append(v2.getName());
+                        if (m.getRightSwizzling().length() > 0) {
+                            b.append(".");
+                            b.append(m.getRightSwizzling());
+                        }
                     }
                 } else {
                     // 2 possible cases here
@@ -652,7 +656,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
     }
 
     @Override
-    protected String getLanguageAndVersion(ShaderType type) {
+    protected String getLanguageAndVersion() {
         return "GLSL100";
     }
 

+ 1 - 1
jme3-core/src/main/java/com/jme3/shader/Glsl150ShaderGenerator.java

@@ -55,7 +55,7 @@ public class Glsl150ShaderGenerator extends Glsl100ShaderGenerator {
     }
 
     @Override
-    protected String getLanguageAndVersion(ShaderType type) {
+    protected String getLanguageAndVersion() {
         return "GLSL150";
     }
 

+ 23 - 21
jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java

@@ -114,7 +114,7 @@ public abstract class ShaderGenerator {
         Shader shader = new Shader();
         for (ShaderType type : ShaderType.values()) {
             String extension = type.getExtension();
-            String language = getLanguageAndVersion(type);
+            String language = getLanguageAndVersion();
             String shaderSourceCode = buildShader(techniqueDef.getShaderNodes(), info, type);
             
             if (shaderSourceCode != null) {
@@ -203,32 +203,36 @@ public abstract class ShaderGenerator {
             if (info.getUnusedNodes().contains(shaderNode.getName())) {
                 continue;
             }
-
-            if (shaderNode.getDefinition().getType() == type) {
-                int index = findShaderIndexFromVersion(shaderNode, type);
-                String shaderPath = shaderNode.getDefinition().getShadersPath().get(index);
-                Map<String, String> sources = (Map<String, String>) assetManager.loadAsset(new ShaderAssetKey(shaderPath, false));
-                String loadedSource = sources.get("[main]");
-                for (String name : sources.keySet()) {
-                    if (!name.equals("[main]") && !imports.contains(name)) {
-                        imports.add(name);
-                        // append the imported file in place if it hasn't been imported already.
-                        sourceDeclaration.append(sources.get(name));
+            ShaderNodeDefinition def = shaderNode.getDefinition();
+            if (def.getType() == type) {
+                String loadedSource = def.getInlinedCode();
+                String shaderPath = def.getName() + "_Inlined";
+                if(loadedSource == null) {
+                    int index = findShaderIndexFromVersion(shaderNode);
+                    shaderPath = def.getShadersPath().get(index);
+                    Map<String, String> sources = (Map<String, String>) assetManager.loadAsset(new ShaderAssetKey(shaderPath, false));
+                    loadedSource = sources.get("[main]");
+                    for (String name : sources.keySet()) {
+                        if (!name.equals("[main]") && !imports.contains(name)) {
+                            imports.add(name);
+                            // append the imported file in place if it hasn't been imported already.
+                            sourceDeclaration.append(sources.get(name));
+                        }
                     }
                 }
                 // Nodes are functions added to the declaration part of the shader
                 // Multiple nodes may use the same definition and we don't want to declare it several times.
                 // Also nodes can have #ifdef conditions so we need to properly merge this conditions to declare the Node function.
                 NodeDeclaration nd = declaredNodes.get(shaderPath);
-                if(nd == null){
-                    if(!shaderNode.getDefinition().getPath().equals(shaderPath)) {
+                if (nd == null) {
+                    if (def.getInlinedCode() == null && !def.getPath().equals(shaderPath)) {
                         // old style shader node definition
                         loadedSource = functionize(loadedSource, shaderNode.getDefinition());
                     }
-                    nd = new NodeDeclaration(shaderNode.getCondition(),  loadedSource);
+                    nd = new NodeDeclaration(shaderNode.getCondition(), loadedSource);
                     declaredNodes.put(shaderPath, nd);
                 } else {
-                    nd.condition = ConditionParser.mergeConditions(nd.condition, shaderNode.getCondition(), "||");
+                    nd.condition = ShaderUtils.mergeConditions(nd.condition, shaderNode.getCondition(), "||");
                 }
 
                 generateNodeMainSection(source, shaderNode, loadedSource, info);
@@ -293,11 +297,10 @@ public abstract class ShaderGenerator {
      * returns the language + version of the shader should be something like
      * "GLSL100" for glsl 1.0 "GLSL150" for glsl 1.5.
      *
-     * @param type the shader type for which the version should be returned.
      *
      * @return the shaderLanguage and version.
      */
-    protected abstract String getLanguageAndVersion(Shader.ShaderType type);
+    protected abstract String getLanguageAndVersion();
 
     /**
      * generates the uniforms declaration for a shader of the given type.
@@ -379,15 +382,14 @@ public abstract class ShaderGenerator {
      * can handle
      *
      * @param shaderNode the shaderNode being processed
-     * @param type the shaderType
      * @return the index of the shader path in ShaderNodeDefinition shadersPath
      * list
      * @throws NumberFormatException
      */
-    protected int findShaderIndexFromVersion(ShaderNode shaderNode, ShaderType type) throws NumberFormatException {
+    protected int findShaderIndexFromVersion(ShaderNode shaderNode) throws NumberFormatException {
         int index = 0;
         List<String> lang = shaderNode.getDefinition().getShadersLanguage();
-        int genVersion = Integer.parseInt(getLanguageAndVersion(type).substring(4));
+        int genVersion = Integer.parseInt(getLanguageAndVersion().substring(4));
         int curVersion = 0;
         for (int i = 0; i < lang.size(); i++) {
             int version = Integer.parseInt(lang.get(i).substring(4));

+ 9 - 2
jme3-core/src/main/java/com/jme3/shader/ShaderNodeDefinition.java

@@ -59,6 +59,7 @@ public class ShaderNodeDefinition implements Savable {
     private List<ShaderNodeVariable> outputs = new ArrayList<ShaderNodeVariable>();
     private List<ShaderNodeVariable> params = new ArrayList<>();
     private String path = null;
+    private String inlinedCode;
     private boolean noOutput = false;
     private String returnType;
 
@@ -237,8 +238,14 @@ public class ShaderNodeDefinition implements Savable {
         this.noOutput = noOutput;
     }
 
-    
-    
+    public String getInlinedCode() {
+        return inlinedCode;
+    }
+
+    public void setInlinedCode(String inlinedCode) {
+        this.inlinedCode = inlinedCode;
+    }
+
     /**
      * jme serialization (not used)
      *

+ 6 - 2
jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java

@@ -337,7 +337,11 @@ public class ShaderNodeVariable implements Savable, Cloneable {
     }
 
     @Override
-    public ShaderNodeVariable clone() throws CloneNotSupportedException {
-        return (ShaderNodeVariable) super.clone();
+    public ShaderNodeVariable clone() {
+        try {
+            return (ShaderNodeVariable) super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError();
+        }
     }
 }

+ 316 - 31
jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java

@@ -32,9 +32,11 @@
 package com.jme3.shader;
 
 import com.jme3.asset.AssetManager;
+import com.jme3.material.*;
+import com.jme3.material.plugins.ConditionParser;
+import com.jme3.scene.VertexBuffer;
 import com.jme3.shader.plugins.ShaderAssetKey;
 
-import java.io.StringReader;
 import java.text.ParseException;
 import java.util.*;
 import java.util.regex.Matcher;
@@ -42,18 +44,14 @@ import java.util.regex.Pattern;
 
 public class ShaderUtils {
 
-    public static String convertToGLSL130(String input, boolean isFrag) {
-        StringBuilder sb = new StringBuilder();
-        sb.append("#version 130\n");
-        if (isFrag) {
-            input = input.replaceAll("varying", "in");
-        } else {
-            input = input.replaceAll("attribute", "in");
-            input = input.replaceAll("varying", "out");
-        }
-        sb.append(input);
-        return sb.toString();
-    }
+    private static ConditionParser parser = new ConditionParser();
+
+    // matches "defaults (<defaultParam1>, <defaultParam2>, ...)"
+    private final static Pattern defaultsPattern = Pattern.compile("defaults\\s*\\(\\s*(.*)\\s*\\)");
+    // matches "<type> <functionName>("
+    private final static Pattern typeNamePattern = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(");
+    // matches "const? <in/out> <type> <parmaName>,"
+    private final static Pattern paramsPattern = Pattern.compile("((const)?\\s*(\\w+)\\s+(\\w+)\\s+(\\w+)\\s*[,\\)])");
 
     /**
      * Check if a mapping is valid by checking the types and swizzle of both of
@@ -80,8 +78,8 @@ public class ShaderUtils {
 
         return false;
     }
-    
-     /**
+
+    /**
      * Check if a mapping is valid by checking the multiplicity of both of
      * the variables if they are arrays
      *
@@ -91,16 +89,16 @@ public class ShaderUtils {
     public static boolean multiplicityMatch(VariableMapping mapping) {
         String leftMult = mapping.getLeftVariable().getMultiplicity();
         String rightMult = mapping.getRightVariable().getMultiplicity();
-        
-        if(leftMult == null){
-            if(rightMult != null){
+
+        if (leftMult == null) {
+            if (rightMult != null) {
                 return false;
             }
-        }else{
-            if(rightMult == null){
+        } else {
+            if (rightMult == null) {
                 return false;
-            }else{
-                if(!leftMult.equalsIgnoreCase(rightMult)){
+            } else {
+                if (!leftMult.equalsIgnoreCase(rightMult)) {
                     return false;
                 }
             }
@@ -113,7 +111,7 @@ public class ShaderUtils {
      * is 4 float cardinality is 1 vec4.xyz cardinality is 3. sampler2D
      * cardinality is 0
      *
-     * @param type the glsl type
+     * @param type      the glsl type
      * @param swizzling the swizzling of a variable
      * @return the cardinality
      */
@@ -143,15 +141,9 @@ public class ShaderUtils {
      * @return true if a variable of the given type can have a swizzle
      */
     public static boolean isSwizzlable(String type) {
-        return type.indexOf("vec4")>-1 || type.indexOf("vec3")>-1 || type.indexOf("vec2")>-1 || type.equals("float");
+        return type.indexOf("vec4") > -1 || type.indexOf("vec3") > -1 || type.indexOf("vec2") > -1 || type.equals("float");
     }
 
-    private final static Pattern defaultsPattern = Pattern.compile("defaults\\s*\\(\\s*(.*)\\s*\\)");
-    // matches "<type> <functionName>("
-    private final static Pattern typeNamePattern = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(");
-    // matches "const? <in/out> <type> <parmaName>,"
-    private final static Pattern paramsPattern = Pattern.compile("((const)?\\s*(\\w+)\\s+(\\w+)\\s+(\\w+)\\s*[,\\)])");
-
     public static List<ShaderNodeDefinition> parseDefinitions(String glsl) throws ParseException {
         List<ShaderNodeDefinition> defs = new ArrayList<>();
         String nodesCode[] = glsl.split("#pragma ShaderNode");
@@ -244,7 +236,7 @@ public class ShaderUtils {
     }
 
     public static List<ShaderNodeDefinition> loadSahderNodeDefinition(AssetManager assetManager, String definitionPath) throws ParseException {
-        Map<String, String> sources =  (Map<String, String>)  assetManager.loadAsset(new ShaderAssetKey(definitionPath, false));
+        Map<String, String> sources = (Map<String, String>) assetManager.loadAsset(new ShaderAssetKey(definitionPath, false));
         String glsl = sources.get("[main]");
         List<ShaderNodeDefinition> defs = ShaderUtils.parseDefinitions(glsl);
         Shader.ShaderType type = ShaderUtils.getShaderType(definitionPath);
@@ -257,4 +249,297 @@ public class ShaderUtils {
 
         return defs;
     }
+
+    public static String getDefaultAttributeType(VertexBuffer.Type type) {
+        switch (type) {
+
+            case BoneWeight:
+            case BindPoseNormal:
+            case Binormal:
+            case Normal:
+                return "vec3";
+            case Size:
+                return "float";
+            case Position:
+            case BindPosePosition:
+            case BindPoseTangent:
+            case Tangent:
+            case Color:
+                return "vec4";
+            case InterleavedData:
+                return "int";
+            case Index:
+                return "uint";
+            case BoneIndex:
+                return "uvec4";
+            case TexCoord:
+            case TexCoord2:
+            case TexCoord3:
+            case TexCoord4:
+            case TexCoord5:
+            case TexCoord6:
+            case TexCoord7:
+            case TexCoord8:
+                return "vec2";
+            default:
+                return "float";
+        }
+    }
+
+    /**
+     * TODO put this in core but this should be changed to handle all kinds of shader not just Vertex and Fragments.
+     * TODO Also getShaderGenerationInfo ArrayLists should be sets really, to avoid duplicated and not have to avoid them ourselves.
+     * This method could be in core actually and be used after loading a techniqueDef.
+     * It computes all the information needed to generate the shader faster, from the ShaderNodes.
+     *
+     * @param technique
+     */
+    public static void computeShaderNodeGenerationInfo(TechniqueDef technique, MaterialDef matDef) {
+
+
+        List<ShaderNodeVariable> attributes = technique.getShaderGenerationInfo().getAttributes();
+        List<ShaderNodeVariable> fragmentGlobals = technique.getShaderGenerationInfo().getFragmentGlobals();
+        List<ShaderNodeVariable> fragmentUniforms = technique.getShaderGenerationInfo().getFragmentUniforms();
+        List<ShaderNodeVariable> vertexUniforms = technique.getShaderGenerationInfo().getVertexUniforms();
+        List<ShaderNodeVariable> varyings = technique.getShaderGenerationInfo().getVaryings();
+        List<String> unusedNodes = technique.getShaderGenerationInfo().getUnusedNodes();
+        attributes.clear();
+        fragmentGlobals.clear();
+        fragmentUniforms.clear();
+        vertexUniforms.clear();
+        varyings.clear();
+        unusedNodes.clear();
+
+        //considering that none of the nodes are used, we'll remove them from the list when we have proof they are actually used.
+        for (ShaderNode shaderNode : technique.getShaderNodes()) {
+            unusedNodes.add(shaderNode.getName());
+        }
+        for (ShaderNode sn : technique.getShaderNodes()) {
+            checkDefineFromCondition(sn.getCondition(), matDef, technique);
+            ShaderNodeDefinition def = sn.getDefinition();
+            List<VariableMapping> in = sn.getInputMapping();
+            if (in != null) {
+                for (VariableMapping map : in) {
+                    checkDefineFromCondition(map.getCondition(), matDef, technique);
+                    if (map.getRightExpression() != null) {
+                        continue;
+                    }
+                    ShaderNodeVariable var = map.getRightVariable();
+                    if (var.getNameSpace().equals("Global")) {
+                        computeGlobals(technique, fragmentGlobals, def, var);
+                    } else if (var.getNameSpace().equals("Attr")) {
+                        addUnique(attributes, var);
+                    } else if (var.getNameSpace().equals("MatParam") || var.getNameSpace().equals("WorldParam")) {
+                        checkMultiplicity(technique, matDef, map, var);
+                        if (def.getType() == Shader.ShaderType.Fragment) {
+                            addUnique(fragmentUniforms, var);
+                        } else {
+                            addUnique(vertexUniforms, var);
+                        }
+                    } else {
+                        //the nameSpace is the name of another node, if it comes from a different type of node the var is a varying
+                        ShaderNode otherNode = null;
+                        otherNode = findShaderNodeByName(technique, var.getNameSpace());
+                        if (otherNode == null) {
+                            //we have a problem this should not happen...but let's not crash...
+                            //TODO Maybe we could have an error list and report in it, then present the errors to the user.
+                            continue;
+                        }
+                        if (otherNode.getDefinition().getType() != def.getType()) {
+                            addUnique(varyings, var);
+                            var.setShaderOutput(true);
+                            for (VariableMapping variableMapping : otherNode.getInputMapping()) {
+                                if (variableMapping.getLeftVariable().getName().equals(var.getName())) {
+                                    variableMapping.getLeftVariable().setShaderOutput(true);
+                                }
+                            }
+                        }
+                        //and this other node is apparently used so we remove it from the unusedNodes list
+                        unusedNodes.remove(otherNode.getName());
+                    }
+                }
+
+            }
+            List<VariableMapping> out = sn.getOutputMapping();
+            if (out != null && !out.isEmpty()) {
+                for (VariableMapping map : out) {
+                    checkDefineFromCondition(map.getCondition(), matDef, technique);
+                    ShaderNodeVariable var = map.getLeftVariable();
+                    if (var.getNameSpace().equals("Global")) {
+                        computeGlobals(technique, fragmentGlobals, def, var);
+                    }
+                }
+                //shader has an output it's used in the shader code.
+                unusedNodes.remove(sn.getName());
+            } else {
+                //some nodes has no output by design ans their def specifies so.
+                if (sn.getDefinition().isNoOutput()) {
+                    unusedNodes.remove(sn.getName());
+                }
+            }
+        }
+    }
+
+    private static void checkDefineFromCondition(String condition, MaterialDef matDef, TechniqueDef techniqueDef) {
+        if (condition == null) {
+            return;
+        }
+        List<String> defines = parser.extractDefines(condition);
+        for (String define : defines) {
+            MatParam param = findMatParam(define, matDef);
+            if (param != null) {
+                addDefine(techniqueDef, param.getName(), param.getVarType());
+            }
+        }
+    }
+
+    public static void checkMultiplicity(TechniqueDef technique, MaterialDef matDef, VariableMapping map, ShaderNodeVariable var) {
+        if (map.getLeftVariable().getMultiplicity() != null) {
+            MatParam param = findMatParam(map.getRightVariable().getName(), matDef);
+            if (!param.getVarType().name().endsWith("Array")) {
+                throw new IllegalArgumentException(param.getName() + " is not of Array type");
+            }
+            String multiplicity = map.getLeftVariable().getMultiplicity();
+            try {
+                Integer.parseInt(multiplicity);
+            } catch (NumberFormatException nfe) {
+                //multiplicity is not an int attempting to find for a material parameter.
+                MatParam mp = findMatParam(multiplicity, matDef);
+                if (mp != null) {
+                    //It's tied to a material param, let's create a define and use this as the multiplicity
+                    addDefine(technique, multiplicity, VarType.Int);
+                    multiplicity = multiplicity.toUpperCase();
+                    map.getLeftVariable().setMultiplicity(multiplicity);
+                    //only declare the variable if the define is defined.
+                    map.getLeftVariable().setCondition(mergeConditions(map.getLeftVariable().getCondition(), "defined(" + multiplicity + ")", "||"));
+                } else {
+                    throw new IllegalArgumentException("Wrong multiplicity for variable" + map.getLeftVariable().getName() + ". " + multiplicity + " should be an int or a declared material parameter.");
+                }
+            }
+            //the right variable must have the same multiplicity and the same condition.
+            var.setMultiplicity(multiplicity);
+            var.setCondition(map.getLeftVariable().getCondition());
+        }
+    }
+
+    /**
+     * merges 2 condition with the given operator
+     *
+     * @param condition1 the first condition
+     * @param condition2 the second condition
+     * @param operator   the operator ("&&" or "||&)
+     * @return the merged condition
+     */
+    public static String mergeConditions(String condition1, String condition2, String operator) {
+        if (condition1 != null) {
+            if (condition1.equals(condition2)) {
+                return condition1;
+            }
+            if (condition2 == null) {
+                return condition1;
+            } else {
+                String mergedCondition = "(" + condition1 + ") " + operator + " (" + condition2 + ")";
+                return mergedCondition;
+            }
+        } else {
+            return condition2;
+        }
+    }
+
+    public static void addDefine(TechniqueDef techniqueDef, String paramName, VarType paramType) {
+        if (techniqueDef.getShaderParamDefine(paramName) == null) {
+            techniqueDef.addShaderParamDefine(paramName, paramType, paramName.toUpperCase());
+        }
+    }
+
+
+    public static MatParam findMatParam(String varName, MaterialDef matDef) {
+        for (MatParam matParam : matDef.getMaterialParams()) {
+            if (varName.toLowerCase().equals(matParam.getName().toLowerCase())) {
+                return matParam;
+            }
+        }
+        return null;
+    }
+
+    private static void addUnique(List<ShaderNodeVariable> variables, ShaderNodeVariable var) {
+        for (ShaderNodeVariable variable : variables) {
+            if (var.equals(variable)) {
+                return;
+            }
+        }
+        variables.add(var);
+    }
+
+    /**
+     * Retrieve a shader node by name
+     *
+     * @param technique
+     * @param name
+     * @return
+     */
+    private static ShaderNode findShaderNodeByName(TechniqueDef technique, String name) {
+        for (ShaderNode shaderNode : technique.getShaderNodes()) {
+            if (shaderNode.getName().equals(name)) {
+                return shaderNode;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Some parameters may not be used, or not used as an input, but as a flag to command a define.
+     * We didn't get them when looking into shader nodes mappings so let's do that now.
+     *
+     * @param uniforms
+     */
+    public static void getAllUniforms(TechniqueDef technique, MaterialDef matDef, List<ShaderNodeVariable> uniforms) {
+        uniforms.clear();
+        uniforms.addAll(technique.getShaderGenerationInfo().getFragmentUniforms());
+        uniforms.addAll(technique.getShaderGenerationInfo().getVertexUniforms());
+
+        for (UniformBinding worldParam : technique.getWorldBindings()) {
+            ShaderNodeVariable var = new ShaderNodeVariable(worldParam.getGlslType(), "WorldParam", worldParam.name());
+            if (!contains(uniforms, var)) {
+                uniforms.add(var);
+            }
+        }
+
+        for (MatParam matParam : matDef.getMaterialParams()) {
+            ShaderNodeVariable var = new ShaderNodeVariable(matParam.getVarType().getGlslType(), "MatParam", matParam.getName());
+            if (!contains(uniforms, var)) {
+                uniforms.add(var);
+            }
+        }
+    }
+
+    private static void computeGlobals(TechniqueDef technique, List<ShaderNodeVariable> fragmentGlobals, ShaderNodeDefinition def, ShaderNodeVariable var) {
+        var.setShaderOutput(true);
+        if (def.getType() == Shader.ShaderType.Vertex) {
+            if (technique.getShaderGenerationInfo().getVertexGlobal() == null) {
+                technique.getShaderGenerationInfo().setVertexGlobal(var);
+            }
+        } else {
+            if (!contains(fragmentGlobals, var)) {
+                fragmentGlobals.add(var);
+            }
+        }
+    }
+
+    /**
+     * returns true if a ShaderNode variable is already contained in a list of variables.
+     * TODO This could be handled with a Collection.contains, if ShaderNodeVariable had a proper equals and hashcode
+     *
+     * @param vars
+     * @param var
+     * @return
+     */
+    public static boolean contains(List<ShaderNodeVariable> vars, ShaderNodeVariable var) {
+        for (ShaderNodeVariable shaderNodeVariable : vars) {
+            if (shaderNodeVariable.getName().equals(var.getName()) && shaderNodeVariable.getNameSpace().equals(var.getNameSpace())) {
+                return true;
+            }
+        }
+        return false;
+    }
 }

+ 105 - 0
jme3-core/src/main/java/com/jme3/shader/builder/InlineShaderNodeBuilder.java

@@ -0,0 +1,105 @@
+package com.jme3.shader.builder;
+
+import com.jme3.material.TechniqueDef;
+import com.jme3.shader.*;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class InlineShaderNodeBuilder extends ShaderNodeBuilder {
+
+
+    private TechniqueDef technique;
+    private Pattern varPattern = Pattern.compile("%(\\w+)");
+    private String[] outputTypes;
+
+    public InlineShaderNodeBuilder(String name, ShaderNodeDefinition def, String code, TechniqueDef technique) {
+        super(name, def);
+        this.technique = technique;
+        Matcher m = varPattern.matcher(code);
+        while (m.find()) {
+            // type will be inferred with mapping
+            ShaderNodeVariable v = new ShaderNodeVariable(null, m.group(1));
+            def.getParams().add(v);
+            def.getInputs().add(v);
+        }
+        def.setInlinedCode(code.replaceAll("%", ""));
+        def.getOutputs().add(new ShaderNodeVariable(def.getReturnType(), "result"));
+    }
+
+    @Override
+    public InlineShaderNodeBuilder inputs(VariableMappingBuilder... inputs) {
+        ShaderNodeDefinition def = getNode().getDefinition();
+        for (VariableMappingBuilder map : inputs) {
+            ShaderNodeVariable v = findVariable(map.getName(), def.getInputs());
+
+            def.getInputs().add(v);
+            ShaderNodeVariable right = map.getVariable();
+            if (right.getDefaultValue() != null) {
+                throw new IllegalArgumentException("Inlined expression for input " + v.getName()
+                        + " is not supported with inline node " + getNode().getName()
+                        + ". Please inline the expression in the node code.");
+            }
+            // infer type
+            int idx = right.getType().indexOf("|");
+            if (idx > 0) {
+                // texture type, taking the first available type
+                String type = right.getType().substring(0, right.getType().indexOf("|"));
+                right.setType(type);
+            }
+
+            v.setType(right.getType());
+
+        }
+        super.inputs(inputs);
+        return this;
+    }
+
+    @Override
+    public InlineShaderNodeBuilder outputs(VariableMappingBuilder... outputs) {
+        if (outputs.length > 1 || !outputs[0].getName().equals("result")) {
+            throw new IllegalArgumentException("Only the 'result' output can be mapped for an inlined node");
+        }
+        super.outputs(outputs);
+        return this;
+    }
+
+    @Override
+    public void build() {
+        ShaderNodeDefinition def = getNode().getDefinition();
+        //generate the code
+        StringBuilder sb = new StringBuilder();
+        sb.append(def.getReturnType()).append(" ").append(def.getName()).append("(");
+        boolean isFirst = true;
+        int outTypeIndex = 0;
+        for (ShaderNodeVariable v : def.getParams()) {
+            if (!isFirst) {
+                sb.append(", ");
+            }
+            sb.append("const in ");
+            if (def.getInputs().contains(v)) {
+
+            } else {
+                sb.append("out ");
+                if (!def.getOutputs().contains(v)) {
+                    // the variable is not in the output list
+                    def.getOutputs().add(v);
+                }
+                if (v.getType() == null && outTypeIndex < outputTypes.length) {
+                    v.setType(outputTypes[outTypeIndex]);
+                } else {
+                    throw new IllegalArgumentException("Output variable " + v.getName() + " has no type in node " + getNode().getName() + ". Make sure you properly declare it");
+                }
+            }
+            if (v.getType() == null) {
+                throw new IllegalArgumentException("Unable to infer type for input variable " + v.getName() + " in node " + getNode().getName());
+            }
+            sb.append(v.getType()).append(" ");
+            sb.append(v.getName());
+            isFirst = false;
+        }
+        sb.append("){\n\treturn ").append(def.getInlinedCode()).append(";\n}");
+        def.setInlinedCode(sb.toString());
+        super.build();
+    }
+}

+ 146 - 0
jme3-core/src/main/java/com/jme3/shader/builder/MaterialBuilder.java

@@ -0,0 +1,146 @@
+package com.jme3.shader.builder;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.*;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.shader.*;
+import com.jme3.texture.image.ColorSpace;
+
+import java.util.*;
+
+public class MaterialBuilder {
+
+    private MaterialDef matDef;
+    private Map<String, TechniqueBuilder> techBuilders = new HashMap<>();
+    private AssetManager assetManager;
+    private TechniqueBuilder currentTechnique;
+
+    public MaterialBuilder(AssetManager assetManager) {
+        matDef = new MaterialDef(assetManager, "MatDef");
+        this.assetManager = assetManager;
+    }
+
+    public MaterialBuilder(AssetManager assetManager, MaterialDef matDef) {
+        this.matDef = matDef;
+        this.assetManager = assetManager;
+    }
+
+    public MaterialBuilder(AssetManager assetManager, String matDefName) {
+        this.matDef = (MaterialDef) assetManager.loadAsset(matDefName);
+        this.assetManager = assetManager;
+    }
+
+    public TechniqueBuilder technique(String name) {
+        TechniqueBuilder tb = techBuilders.get(name);
+        if (tb == null) {
+            List<TechniqueDef> defs = matDef.getTechniqueDefs(name);
+            if (defs == null || defs.isEmpty()) {
+                String techniqueUniqueName = matDef.getAssetName() + "@" + name;
+                tb = new TechniqueBuilder(assetManager, name, techniqueUniqueName.hashCode());
+                matDef.addTechniqueDef(tb.getTechniqueDef());
+
+            } else {
+                tb = new TechniqueBuilder(assetManager, defs.get(0));
+            }
+            techBuilders.put(name, tb);
+        }
+        currentTechnique = tb;
+        return tb;
+    }
+
+    public TechniqueBuilder technique() {
+        return this.technique("Default");
+    }
+
+    public Material build() {
+
+        for (Map.Entry<String, TechniqueBuilder> entry : techBuilders.entrySet()) {
+            TechniqueBuilder tb = entry.getValue();
+            tb.build();
+            ShaderUtils.computeShaderNodeGenerationInfo(tb.getTechniqueDef(), matDef);
+        }
+        return new Material(matDef);
+    }
+
+    public ShaderNodeVariable var(String expression) {
+        String[] names = expression.split("\\.");
+        if (names.length != 2) {
+            // we might have an inlined expression
+            ShaderNodeVariable tmp = new ShaderNodeVariable(null, null);
+            tmp.setDefaultValue(expression);
+            return tmp;
+        }
+        if (names[0].equals("MatParam")) {
+            MatParam param = matDef.getMaterialParam(names[1]);
+            if (param == null) {
+                throw new IllegalArgumentException("Couldn't find material parameter named " + names[1]);
+            }
+            return new ShaderNodeVariable(param.getVarType().getGlslType(), names[0], names[1], null, "m_");
+        }
+        if (names[0].equals("WorldParam")) {
+            UniformBinding worldParam = UniformBinding.valueOf(names[1]);
+            currentTechnique.addWorldParam(worldParam.name());
+            return new ShaderNodeVariable(worldParam.getGlslType(), "WorldParam", worldParam.name(), null, "g_");
+        }
+        if (names[0].equals("Attr")) {
+            String n = names[1].substring(2);
+            VertexBuffer.Type attribute = VertexBuffer.Type.valueOf(n);
+            return new ShaderNodeVariable(ShaderUtils.getDefaultAttributeType(attribute), names[0], names[1]);
+        }
+
+        if (names[0].equals("Global")) {
+            if(!names[1].equals("position") && !names[1].startsWith("color")){
+                throw new IllegalArgumentException("Global output must be outPosition or outColor, got " + names[1]);
+            }
+
+            return new ShaderNodeVariable("vec4", names[0], names[1]);
+        }
+
+        ShaderNodeBuilder nb = currentTechnique.node(names[0]);
+        if (nb == null) {
+            throw new IllegalArgumentException("Couldn't find node named " + names[0]);
+        }
+
+        ShaderNodeVariable v = nb.variable(names[1]);
+        if (v == null) {
+            throw new IllegalArgumentException("Couldn't find variable named " + names[1] + " in node " + names[0]);
+        }
+
+        return v;
+    }
+
+    public VariableMappingBuilder map(String param, String expression) {
+        return new VariableMappingBuilder(param, var(expression));
+    }
+
+    public VariableMappingBuilder map(String param, ShaderNodeVariable variable) {
+        return new VariableMappingBuilder(param, variable);
+    }
+
+    public VariableMappingBuilder map(String param, VertexBuffer.Type attribute) {
+        ShaderNodeVariable variable = new ShaderNodeVariable(ShaderUtils.getDefaultAttributeType(attribute), "Attr", "in" + attribute.name());
+        return new VariableMappingBuilder(param, variable);
+    }
+
+    public VariableMappingBuilder map(String param, UniformBinding worldParam) {
+        ShaderNodeVariable variable = new ShaderNodeVariable(worldParam.getGlslType(), "WorldParam", worldParam.name(), null, "g_");
+        currentTechnique.addWorldParam(worldParam.name());
+        return new VariableMappingBuilder(param, variable);
+    }
+
+    public void addMatParam(VarType type, String name){
+        if(type.isTextureType()){
+            matDef.addMaterialParamTexture(type, name, ColorSpace.sRGB);
+        } else {
+            matDef.addMaterialParam(type, name, null);
+        }
+    }
+
+    public void addMatParamTexture(VarType type, String name, ColorSpace colorSpace){
+        if(!type.isTextureType()){
+            throw new IllegalArgumentException(type + "is not a texture type ");
+        }
+        matDef.addMaterialParamTexture(type, name, colorSpace);
+    }
+
+}

+ 113 - 0
jme3-core/src/main/java/com/jme3/shader/builder/ShaderNodeBuilder.java

@@ -0,0 +1,113 @@
+package com.jme3.shader.builder;
+
+import com.jme3.shader.*;
+
+import java.util.List;
+
+public class ShaderNodeBuilder {
+
+    private ShaderNode node;
+
+    protected ShaderNodeBuilder(String name, ShaderNodeDefinition def) {
+        node = new ShaderNode(name, def, null);
+    }
+
+    protected ShaderNodeBuilder(ShaderNode node) {
+        this.node = node;
+    }
+
+    protected ShaderNode getNode() {
+        return node;
+    }
+
+    protected ShaderNodeVariable variable(String name){
+        ShaderNodeDefinition def = node.getDefinition();
+        for (ShaderNodeVariable variable : def.getParams()) {
+            if(variable.getName().equals(name)){
+                ShaderNodeVariable var = variable.clone();
+                var.setNameSpace(node.getName());
+                return var;
+            }
+        }
+
+        for (ShaderNodeVariable variable : def.getOutputs()) {
+            if(variable.getName().equals(name)){
+                ShaderNodeVariable var = variable.clone();
+                var.setNameSpace(node.getName());
+                return var;
+            }
+        }
+        return null;
+    }
+
+    public ShaderNodeBuilder inputs(VariableMappingBuilder... inputs){
+        List<VariableMapping> mappings = node.getInputMapping();
+        mappings.clear();
+        for (VariableMappingBuilder mb : inputs) {
+            ShaderNodeVariable left = findVariable(mb.getName(), node.getDefinition().getInputs() );
+            if(left == null){
+                throw new IllegalArgumentException("Couldn't find input " + mb.getName() + " in node definition " + node.getDefinition().getName());
+            }
+            left = left.clone();
+            left.setNameSpace(node.getName());
+            ShaderNodeVariable right = mb.getVariable();
+            VariableMapping m = map(left, right);
+            mappings.add(m);
+        }
+        return this;
+    }
+
+
+    public ShaderNodeBuilder outputs(VariableMappingBuilder... outputs){
+        List<VariableMapping> mappings = node.getOutputMapping();
+        mappings.clear();
+        for (VariableMappingBuilder mb : outputs) {
+            ShaderNodeVariable right = findVariable(mb.getName(), node.getDefinition().getOutputs() );
+            if(right == null){
+                throw new IllegalArgumentException("Couldn't find input " + mb.getName() + " in node definition " + node.getDefinition().getName());
+            }
+            right = right.clone();
+            right.setNameSpace(node.getName());
+            ShaderNodeVariable left = mb.getVariable();
+            VariableMapping m = map(left, right);
+            mappings.add(m);
+        }
+        return this;
+    }
+
+
+    private VariableMapping map(ShaderNodeVariable left, ShaderNodeVariable right) {
+        if(right.getType() == null){
+            // tmp variable, with default value
+            VariableMapping m = new VariableMapping();
+            m.setLeftVariable(left);
+            m.setRightExpression(right.getDefaultValue());
+            return m;
+        }
+        int leftCard = ShaderUtils.getCardinality(left.getType(), "");
+        int rightCard = ShaderUtils.getCardinality(right.getType(), "");
+        String swizzle = "xyzw";
+        String rightVarSwizzle = "";
+        String leftVarSwizzle ="";
+        if (rightCard > leftCard) {
+            rightVarSwizzle = swizzle.substring(0, leftCard);
+        } else if (rightCard > rightCard) {
+            leftVarSwizzle = swizzle.substring(0, rightCard);
+        }
+
+        return new VariableMapping(left, leftVarSwizzle, right, rightVarSwizzle, null);
+    }
+
+    protected ShaderNodeVariable findVariable(String name, List<ShaderNodeVariable> list){
+        for (ShaderNodeVariable variable : list) {
+            if(variable.getName().equals(name)){
+                return variable;
+            }
+        }
+        return null;
+    }
+
+    public void build(){
+
+    }
+}

+ 145 - 0
jme3-core/src/main/java/com/jme3/shader/builder/TechniqueBuilder.java

@@ -0,0 +1,145 @@
+package com.jme3.shader.builder;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.ShaderNodeDefinitionKey;
+import com.jme3.material.ShaderGenerationInfo;
+import com.jme3.material.TechniqueDef;
+import com.jme3.material.logic.*;
+import com.jme3.shader.*;
+
+import java.text.ParseException;
+import java.util.*;
+
+public class TechniqueBuilder {
+
+    private TechniqueDef techniqueDef;
+    private AssetManager assetManager;
+    private Map<String, ShaderNodeBuilder> nodeBuilders = new HashMap<>();
+
+    protected TechniqueBuilder(AssetManager assetManager, String name, int sortId) {
+        techniqueDef = new TechniqueDef(name, sortId);
+        this.assetManager = assetManager;
+    }
+
+    protected TechniqueBuilder(AssetManager assetManager, TechniqueDef techniqueDef) {
+        this.techniqueDef = techniqueDef;
+        this.assetManager = assetManager;
+    }
+
+    protected TechniqueDef getTechniqueDef() {
+        return techniqueDef;
+    }
+
+    public ShaderNodeBuilder node(String name) {
+        ShaderNodeBuilder b = nodeBuilders.get(name);
+        if (b == null){
+            ShaderNode n = findShaderNode(name);
+            if(n == null){
+                throw new IllegalArgumentException("Can't find node with name " + name + " in technique definition " + techniqueDef.getName());
+            }
+            b = new ShaderNodeBuilder(n);
+            nodeBuilders.put(name, b);
+        }
+        return b;
+    }
+
+    public ShaderNodeBuilder addNode(String name, String defName, String shaderNodeDefPath) {
+        List<ShaderNodeDefinition> defs;
+        if(shaderNodeDefPath.endsWith(".j3sn")){
+            defs = assetManager.loadAsset(new ShaderNodeDefinitionKey(shaderNodeDefPath));
+        } else {
+            try {
+                defs = ShaderUtils.loadSahderNodeDefinition(assetManager, shaderNodeDefPath);
+            } catch (ParseException e) {
+                throw new IllegalArgumentException("Couldn't parse definition " + shaderNodeDefPath, e);
+            }
+        }
+        ShaderNodeDefinition definition = findDefinition(defName, defs);
+        if(definition == null){
+            throw new IllegalArgumentException("Couldn't find definition " + defName + " in " + shaderNodeDefPath);
+        }
+
+        ShaderNodeBuilder b = new ShaderNodeBuilder(name,definition);
+        if(techniqueDef.getShaderNodes() ==  null){
+            techniqueDef.setShaderNodes(new ArrayList<ShaderNode>());
+        }
+        techniqueDef.setShaderFile(techniqueDef.hashCode() + "", techniqueDef.hashCode() + "", "GLSL100", "GLSL100");
+        techniqueDef.getShaderNodes().add(b.getNode());
+        techniqueDef.setShaderGenerationInfo(new ShaderGenerationInfo());
+        techniqueDef.setLogic(new DefaultTechniqueDefLogic(techniqueDef));
+        techniqueDef.setShaderPrologue("");
+        nodeBuilders.put(name, b);
+        return b;
+    }
+
+
+    public InlineShaderNodeBuilder inlineVertexNode(String type, String name, String code){
+        return inlineNode(type, name, code, Shader.ShaderType.Vertex);
+    }
+
+    public InlineShaderNodeBuilder inlineFragmentNode(String type, String name, String code){
+        return inlineNode(type, name, code, Shader.ShaderType.Fragment);
+    }
+
+    public InlineShaderNodeBuilder inlineNode(String returnType, String name, String code, Shader.ShaderType type){
+        ShaderNodeDefinition def = new ShaderNodeDefinition();
+        def.setName(name);
+        def.setType(type);
+        def.setReturnType(returnType);
+        InlineShaderNodeBuilder sb = new InlineShaderNodeBuilder(name, def, code, techniqueDef);
+        nodeBuilders.put(name, sb);
+        techniqueDef.getShaderNodes().add(sb.getNode());
+        return sb;
+    }
+
+    public boolean addWorldParam(String name){
+        return techniqueDef.addWorldParam(name);
+    }
+
+    private void setLightMode(TechniqueDef.LightMode mode){
+        switch (techniqueDef.getLightMode()) {
+            case Disable:
+                techniqueDef.setLogic(new DefaultTechniqueDefLogic(techniqueDef));
+                break;
+            case MultiPass:
+                techniqueDef.setLogic(new MultiPassLightingLogic(techniqueDef));
+                break;
+            case SinglePass:
+                techniqueDef.setLogic(new SinglePassLightingLogic(techniqueDef));
+                break;
+            case StaticPass:
+                techniqueDef.setLogic(new StaticPassLightingLogic(techniqueDef));
+                break;
+            case SinglePassAndImageBased:
+                techniqueDef.setLogic(new SinglePassAndImageBasedLightingLogic(techniqueDef));
+                break;
+            default:
+                throw new UnsupportedOperationException();
+        }
+    }
+
+    private ShaderNodeDefinition findDefinition(String defName, List<ShaderNodeDefinition> defs) {
+        for (ShaderNodeDefinition def : defs) {
+            if(def.getName().equals(defName)){
+                return def;
+            }
+        }
+        return null;
+    }
+
+    private ShaderNode findShaderNode(String name){
+        for (ShaderNode shaderNode : techniqueDef.getShaderNodes()) {
+            if(shaderNode.getName().equals(name)){
+                return shaderNode;
+            }
+        }
+        return null;
+    }
+
+    protected void build(){
+        for (Map.Entry<String, ShaderNodeBuilder> entry : nodeBuilders.entrySet()) {
+            ShaderNodeBuilder nb = entry.getValue();
+            nb.build();
+        }
+    }
+}

+ 23 - 0
jme3-core/src/main/java/com/jme3/shader/builder/VariableMappingBuilder.java

@@ -0,0 +1,23 @@
+package com.jme3.shader.builder;
+
+import com.jme3.shader.ShaderNodeVariable;
+
+public class VariableMappingBuilder {
+
+    private String name;
+    private ShaderNodeVariable variable;
+
+    protected VariableMappingBuilder(String name, ShaderNodeVariable variable) {
+        this.name = name;
+        this.variable = variable;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public ShaderNodeVariable getVariable() {
+        return variable;
+    }
+
+}

+ 0 - 22
jme3-core/src/plugins/java/com/jme3/material/plugins/ConditionParser.java

@@ -104,28 +104,6 @@ public class ConditionParser {
         return defines;
     }
 
-    /**
-     * merges 2 condition with the given operator
-     *
-     * @param condition1 the first condition
-     * @param condition2 the second condition
-     * @param operator the operator ("&&" or "||&)
-     * @return the merged condition
-     */
-    public static String mergeConditions(String condition1, String condition2, String operator) {
-        if (condition1 != null) {
-            if (condition2 == null) {
-                return condition1;
-            } else {
-                String mergedCondition = "(" + condition1 + ") " + operator + " (" + condition2 + ")";
-                return mergedCondition;
-            }
-        } else {
-            return condition2;
-        }
-    }
-
-
     /**
      *
      * @return the formatted expression previously updated by extractDefines

+ 2 - 3
jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java

@@ -660,9 +660,8 @@ public class J3MLoader implements AssetLoader {
         if(isUseNodes){
             //used for caching later, the shader here is not a file.
             
-            // KIRILL 9/19/2015
-            // Not sure if this is needed anymore, since shader caching
-            // is now done by TechniqueDef.
+            // REMY 16/06/2018
+            // this is still needed in order for the weight of the technique to be computed.
             technique.setShaderFile(technique.hashCode() + "", technique.hashCode() + "", "GLSL100", "GLSL100");
             techniqueDefs.add(technique);
         }else if (shaderNames.containsKey(Shader.ShaderType.Vertex) && shaderNames.containsKey(Shader.ShaderType.Fragment)) {

+ 1 - 1
jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java

@@ -582,7 +582,7 @@ public class ShaderNodeLoaderDelegate {
                         multiplicity = multiplicity.toUpperCase();
                         left.setMultiplicity(multiplicity);
                         // only declare the variable if the define is defined.
-                        left.setCondition(ConditionParser.mergeConditions(left.getCondition(), "defined(" + multiplicity + ")", "||"));
+                        left.setCondition(ShaderUtils.mergeConditions(left.getCondition(), "defined(" + multiplicity + ")", "||"));
                     } else {
                         throw new MatParseException("Wrong multiplicity for variable" + left.getName() + ". " +
                                 multiplicity + " should be an int or a declared material parameter.", statement);

+ 86 - 0
jme3-examples/src/main/java/jme3test/material/TestShaderNodesApi.java

@@ -0,0 +1,86 @@
+package jme3test.material;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.material.Technique;
+import com.jme3.material.TechniqueDef;
+import com.jme3.math.*;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.shape.Box;
+import com.jme3.shader.*;
+import com.jme3.shader.builder.MaterialBuilder;
+import com.jme3.texture.Texture;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class TestShaderNodesApi extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestShaderNodesApi app = new TestShaderNodesApi();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        flyCam.setMoveSpeed(20);
+        Logger.getLogger("com.jme3").setLevel(Level.WARNING);
+        Box boxshape1 = new Box(1f, 1f, 1f);
+        Geometry cube = new Geometry("A Box", boxshape1);
+        Texture tex = assetManager.loadTexture("Interface/Logo/Monkey.jpg");
+
+        MaterialBuilder mb = new MaterialBuilder(assetManager);
+        mb.addMatParam(VarType.Vector4, "Color");
+        mb.addMatParam(VarType.Texture2D, "Texture");
+
+        mb.technique().addNode("CommonVert", "CommonVert", "jme3test/matdefs/CommonVert.vert")
+                .inputs(
+                        mb.map("worldViewProjectionMatrix", UniformBinding.WorldViewProjectionMatrix),
+                        mb.map("modelPosition", VertexBuffer.Type.Position))
+                .outputs(
+                        mb.map("result", "Global.position")
+                );
+
+        mb.technique().inlineVertexNode("vec2","TexCoord", "%texIn")
+                .inputs(
+                        mb.map("texIn", VertexBuffer.Type.TexCoord)
+                );
+
+        mb.technique().addNode("ColorMult", "ColorMult", "jme3test/matdefs/ColorMult.frag")
+                .inputs(
+                        mb.map("color1", "vec4(0.1, 0.1, 0.1, 1.0)"),
+                        mb.map("color2", "MatParam.Color"))
+                .outputs(
+                        mb.map("result", "Global.color")
+                );
+
+        mb.technique().inlineFragmentNode("vec4","InlineNode","%color1 * texture2D(%tex, %texCoord)")
+                .inputs(
+                        mb.map("color1", "ColorMult.result"),
+                        mb.map("tex", "MatParam.Texture"),
+                        mb.map("texCoord", "TexCoord.result")
+                ).outputs(
+                        mb.map("result", "Global.color")
+                );
+
+        Material mat = mb.build();
+
+        //Material mat = new Material(assetManager, "jme3test/matdefs/test2.j3md");
+
+        mat.selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager);
+        Technique t = mat.getActiveTechnique();
+
+        for (Shader.ShaderSource shaderSource : t.getDef().getShader(assetManager, renderer.getCaps(), t.getDynamicDefines()).getSources()) {
+            System.out.println(shaderSource.getSource());
+        }
+
+        mat.setColor("Color", ColorRGBA.Yellow);
+        mat.setTexture("Texture", tex);
+        cube.setMaterial(mat);
+        cube.move(0, 0, 0);
+        rootNode.attachChild(cube);
+
+
+    }
+}