Compare commits

...

4 Commits

  1. 197
      jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java
  2. 2
      jme3-core/src/main/java/com/jme3/shader/Glsl150ShaderGenerator.java
  3. 9
      jme3-core/src/main/java/com/jme3/shader/Shader.java
  4. 164
      jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java
  5. 14
      jme3-core/src/main/java/com/jme3/shader/ShaderNode.java
  6. 38
      jme3-core/src/main/java/com/jme3/shader/ShaderNodeDefinition.java
  7. 8
      jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java
  8. 452
      jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java
  9. 105
      jme3-core/src/main/java/com/jme3/shader/builder/InlineShaderNodeBuilder.java
  10. 146
      jme3-core/src/main/java/com/jme3/shader/builder/MaterialBuilder.java
  11. 113
      jme3-core/src/main/java/com/jme3/shader/builder/ShaderNodeBuilder.java
  12. 145
      jme3-core/src/main/java/com/jme3/shader/builder/TechniqueBuilder.java
  13. 23
      jme3-core/src/main/java/com/jme3/shader/builder/VariableMappingBuilder.java
  14. 12
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/HardwareSkinning/basicGpuSkinning.vert
  15. 16
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/HardwareSkinning/fullGpuSkinning.vert
  16. 5
      jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
  17. 32
      jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java
  18. 26
      jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java
  19. 86
      jme3-examples/src/main/java/jme3test/material/TestShaderNodesApi.java
  20. 84
      jme3-examples/src/main/java/jme3test/material/TestShaderNodesModifApi.java
  21. 5
      jme3-examples/src/main/resources/jme3test/matdefs/ColorMult.frag
  22. 9
      jme3-examples/src/main/resources/jme3test/matdefs/CommonVert.vert
  23. 45
      jme3-examples/src/main/resources/jme3test/matdefs/test.j3md

@ -38,6 +38,8 @@ import com.jme3.shader.Shader.ShaderType;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This shader Generator can generate Vertex and Fragment shaders from
@ -51,6 +53,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
* the indentation characters tabulation characters
*/
private final static String INDENTCHAR = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
private final static Logger log = Logger.getLogger(Glsl100ShaderGenerator.class.getName());
protected ShaderNodeVariable inPosTmp;
@ -116,21 +119,16 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
}
}
/**
* {@inheritDoc}
*
* if the declaration contains no code nothing is done, else it's appended
*/
@Override
protected void generateDeclarativeSection(StringBuilder source, ShaderNode shaderNode, String nodeSource, ShaderGenerationInfo info) {
if (nodeSource.replaceAll("\\n", "").trim().length() > 0) {
nodeSource = updateDefinesName(nodeSource, shaderNode);
protected void generateDeclarationSection(StringBuilder source) {
for (String defName : declaredNodes.keySet()) {
NodeDeclaration nd = declaredNodes.get(defName);
source.append("\n");
unIndent();
startCondition(shaderNode.getCondition(), source);
source.append(nodeSource);
startCondition(nd.condition, source);
source.append(nd.source);
source.append("\n");
endCondition(shaderNode.getCondition(), source);
endCondition(nd.condition, source);
indent();
}
}
@ -234,82 +232,130 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
*/
@Override
protected void generateNodeMainSection(StringBuilder source, ShaderNode shaderNode, String nodeSource, ShaderGenerationInfo info) {
nodeSource = updateDefinesName(nodeSource, shaderNode);
source.append("\n");
comment(source, shaderNode, "Begin");
comment(source, shaderNode, "");
startCondition(shaderNode.getCondition(), source);
final List<String> declaredInputs = new ArrayList<>();
// Decalring variables with default values first
final ShaderNodeDefinition definition = shaderNode.getDefinition();
for (final ShaderNodeVariable var : definition.getInputs()) {
if (var.getType().startsWith("sampler")) {
continue;
StringBuilder b = new StringBuilder();
appendIndent(b);
b.append(definition.getName()).append("(");
boolean isFirst = true;
List<VariableMapping> maps = new ArrayList<>();
for (ShaderNodeVariable v : definition.getParams()) {
if (!isFirst) {
b.append(", ");
}
if (definition.getInputs().contains(v)) {
final String fullName = shaderNode.getName() + "_" + var.getName();
shaderNode.getInputMapping(v.getName(), maps);
final ShaderNodeVariable variable = new ShaderNodeVariable(var.getType(), shaderNode.getName(),
var.getName(), var.getMultiplicity());
boolean declared = false;
for (VariableMapping m : maps) {
// map varyings to their inputs, as the code may not do the mapping.
if (isVarying(info, m.getLeftVariable())) {
map(m, source, false);
declared = true;
}
}
if (!isVarying(info, variable)) {
declareVariable(source, variable, var.getDefaultValue(), true, null);
if (maps.isEmpty()) {
//no mapping found
if (v.getDefaultValue() != null) {
// if there is a default value append it to the function call
b.append(v.getDefaultValue());
} else {
// no default value, construct a variable with the proper type and dummy value and raise a warning
b.append("/*UNMAPPED_").append(v.getName()).append("*/ ");
b.append(getConstructor(v.getType()));
log.log(Level.WARNING, "No input defined for variable " + v.getName() + " on shader node " + shaderNode.getName());
}
} else if (maps.size() == 1 && !declared) {
// one mapping for this variable, directly append the
// other variable from the mapping to the function call
VariableMapping m = maps.get(0);
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
// the variable is a varrying: we can append it directly
// or
// several mappings with different conditions: we have to declare the variable and
// map it properly before appending the variable in the function call
for (VariableMapping mapping : maps) {
map(mapping, source, true);
}
appendVariable(shaderNode.getName(), b, v);
}
} else {
// outputs
declareOutput(source, shaderNode.getName(), info, v);
// append the variable to the function call
appendVariable(shaderNode.getName(), b, v);
}
nodeSource = replaceVariableName(nodeSource, variable);
declaredInputs.add(fullName);
isFirst = false;
}
for (VariableMapping mapping : shaderNode.getInputMapping()) {
b.append(");\n");
final ShaderNodeVariable rightVariable = mapping.getRightVariable();
final ShaderNodeVariable leftVariable = mapping.getLeftVariable();
if(!definition.getReturnType().equals("void")){
// non void return type, the first output is the result
ShaderNodeVariable v = definition.getOutputs().get(0);
declareOutput(source, shaderNode.getName(), info, v);
appendIndent(source);
appendVariable(shaderNode.getName(), source, v);
source.append(" =");
}
String newName = shaderNode.getName() + "_" + leftVariable.getName();
boolean isDeclared = declaredInputs.contains(newName);
//Variables fed with a sampler matparam or world param are replaced by the matparam itself
//It avoids issue with samplers that have to be uniforms.
if (rightVariable != null && isWorldOrMaterialParam(rightVariable) && rightVariable.getType().startsWith("sampler")) {
nodeSource = replace(nodeSource, leftVariable, rightVariable.getPrefix() + rightVariable.getName());
} else {
// Map any output to global output.
for (VariableMapping mapping : shaderNode.getOutputMapping()) {
map(mapping, b, false);
}
source.append(b);
if (leftVariable.getType().startsWith("sampler")) {
throw new IllegalArgumentException("a Sampler must be a uniform");
}
map(mapping, source, !isDeclared);
}
endCondition(shaderNode.getCondition(), source);
}
if (!isDeclared) {
nodeSource = replace(nodeSource, leftVariable, newName);
declaredInputs.add(newName);
}
private void declareOutput(StringBuilder source, String nameSpace, ShaderGenerationInfo info, ShaderNodeVariable v) {
String name = nameSpace + "_" + v.getName();
// if the output is not a varying (already declared) we declare it)
if (!isVarying(info, name)) {
appendIndent(source);
source.append(v.getType()).append(" ").append(name).append(";\n");
}
}
private void appendVariable(String nameSpace, StringBuilder b, ShaderNodeVariable v) {
b.append(nameSpace).append("_").append(v.getName());
}
for (ShaderNodeVariable var : definition.getOutputs()) {
ShaderNodeVariable v = new ShaderNodeVariable(var.getType(), shaderNode.getName(), var.getName(), var.getMultiplicity());
if (!declaredInputs.contains(shaderNode.getName() + "_" + var.getName())) {
if (!isVarying(info, v)) {
declareVariable(source, v);
}
nodeSource = replaceVariableName(nodeSource, v);
}
/**
* Returns a proper constructor call for a given type
* @param type
* @return
*/
private String getConstructor(String type) {
if (type.startsWith("i") || type.startsWith("u")) {
return type + "(0)";
}
source.append(nodeSource);
for (VariableMapping mapping : shaderNode.getOutputMapping()) {
map(mapping, source, true);
if (type.equals("boolean") || type.startsWith("u")) {
return "false";
}
endCondition(shaderNode.getCondition(), source);
comment(source, shaderNode, "End");
return type + "(0.0)";
}
/**
* declares a variable, embed in a conditional block if needed
* @param source the StringBuilder to use
@ -510,6 +556,17 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
return isVarying;
}
protected boolean isVarying(ShaderGenerationInfo info, String variableName) {
for (ShaderNodeVariable shaderNodeVariable : info.getVaryings()) {
String name = shaderNodeVariable.getNameSpace() + "_" + shaderNodeVariable.getName();
if (name.equals(variableName)) {
return true;
}
}
return false;
}
/**
* Appends a comment to the generated code
* @param source the StringBuilder to use
@ -554,10 +611,10 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
String[] lines = nodeSource.split("\\n");
ConditionParser parser = new ConditionParser();
for (String line : lines) {
if (line.trim().startsWith("#if")) {
List<String> params = parser.extractDefines(line.trim());
String l = line.trim().replaceAll("defined", "").replaceAll("#if ", "").replaceAll("#ifdef", "");//parser.getFormattedExpression();
line = line.trim();
if (line.startsWith("#if")) {
List<String> params = parser.extractDefines(line);
String l = line.replaceAll("defined", "").replaceAll("#if ", "").replaceAll("#ifdef", "");
boolean match = false;
for (String param : params) {
for (VariableMapping map : shaderNode.getInputMapping()) {
@ -599,7 +656,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
}
@Override
protected String getLanguageAndVersion(ShaderType type) {
protected String getLanguageAndVersion() {
return "GLSL100";
}

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

@ -103,6 +103,15 @@ public final class Shader extends NativeObject {
public String getExtension() {
return extension;
}
public static ShaderType fromExtention(String ext){
for (ShaderType shaderType : values()) {
if(shaderType.extension.equals(ext)){
return shaderType;
}
}
return null;
}
private ShaderType(String extension) {
this.extension = extension;

@ -34,6 +34,7 @@ package com.jme3.shader;
import com.jme3.asset.AssetManager;
import com.jme3.material.ShaderGenerationInfo;
import com.jme3.material.TechniqueDef;
import com.jme3.material.plugins.ConditionParser;
import com.jme3.shader.Shader.ShaderType;
import com.jme3.shader.plugins.ShaderAssetKey;
@ -72,7 +73,15 @@ public abstract class ShaderGenerator {
*/
Pattern extensions = Pattern.compile("(#extension.*\\s+)");
private Map<String, String> imports = new LinkedHashMap<>();
/**
* a set of imports to append to the shader source
*/
private Set<String> imports = new HashSet<>();
/**
* The nodes function and their condition to be declared in the shader
*/
protected Map<String, NodeDeclaration> declaredNodes = new LinkedHashMap<>();
/**
* Build a shaderGenerator
@ -105,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) {
@ -155,12 +164,6 @@ public abstract class ShaderGenerator {
generateEndOfMainSection(source, info, type);
//insert imports backward
int insertIndex = sourceDeclaration.length();
for (String importSource : imports.values()) {
sourceDeclaration.insert(insertIndex, importSource);
}
sourceDeclaration.append(source);
return moveExtensionsUp(sourceDeclaration);
@ -195,67 +198,109 @@ public abstract class ShaderGenerator {
* @param type the Shader type
*/
protected void generateDeclarationAndMainBody(List<ShaderNode> shaderNodes, StringBuilder sourceDeclaration, StringBuilder source, ShaderGenerationInfo info, Shader.ShaderType type) {
declaredNodes.clear();
for (ShaderNode shaderNode : shaderNodes) {
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.put(name, 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 (def.getInlinedCode() == null && !def.getPath().equals(shaderPath)) {
// old style shader node definition
loadedSource = functionize(loadedSource, shaderNode.getDefinition());
}
nd = new NodeDeclaration(shaderNode.getCondition(), loadedSource);
declaredNodes.put(shaderPath, nd);
} else {
nd.condition = ShaderUtils.mergeConditions(nd.condition, shaderNode.getCondition(), "||");
}
appendNodeDeclarationAndMain(loadedSource, sourceDeclaration, source, shaderNode, info, shaderPath);
generateNodeMainSection(source, shaderNode, loadedSource, info);
}
}
generateDeclarationSection(sourceDeclaration);
}
/**
* Appends declaration and main part of a node to the shader declaration and
* main part. the loadedSource is split by "void main(){" to split
* declaration from main part of the node source code.The trailing "}" is
* removed from the main part. Each part is then respectively passed to
* generateDeclarativeSection and generateNodeMainSection.
*
* @see ShaderGenerator#generateDeclarativeSection
* @see ShaderGenerator#generateNodeMainSection
*
* @param loadedSource the actual source code loaded for this node.
* @param shaderPath path to the shader file
* @param sourceDeclaration the Shader declaration part string builder.
* @param source the Shader main part StringBuilder.
* @param shaderNode the shader node.
* @param info the ShaderGenerationInfo.
* Turns old style shader node code into a proper function so that it can be appended to the declarative section.
* Note that this only needed for nodes coming from a j3sn file.
* @param source
* @param def
* @return
*/
protected void appendNodeDeclarationAndMain(String loadedSource, StringBuilder sourceDeclaration, StringBuilder source, ShaderNode shaderNode, ShaderGenerationInfo info, String shaderPath) {
if (loadedSource.length() > 1) {
loadedSource = loadedSource.substring(0, loadedSource.lastIndexOf("}"));
String[] sourceParts = loadedSource.split("\\s*void\\s*main\\s*\\(\\s*\\)\\s*\\{");
if(sourceParts.length<2){
throw new IllegalArgumentException("Syntax error in "+ shaderPath +". Cannot find 'void main(){' in \n"+ loadedSource);
public static String functionize(String source, ShaderNodeDefinition def){
StringBuffer signature = new StringBuffer();
def.setReturnType("void");
signature.append("void ").append(def.getName()).append("(");
boolean addParam = false;
if(def.getParams().isEmpty()){
addParam = true;
}
boolean isFirst = true;
for (ShaderNodeVariable v : def.getInputs()) {
if(!isFirst){
signature.append(", ");
}
String qualifier;
qualifier = "const in";
signature.append(qualifier).append(" ").append(v.getType()).append(" ").append(v.getName());
isFirst = false;
if(addParam) {
def.getParams().add(v);
}
generateDeclarativeSection(sourceDeclaration, shaderNode, sourceParts[0], info);
generateNodeMainSection(source, shaderNode, sourceParts[1], info);
} else {
//if source is empty, we still call generateNodeMainSection so that mappings can be done.
generateNodeMainSection(source, shaderNode, loadedSource, info);
}
for (ShaderNodeVariable v : def.getOutputs()) {
if(def.getInputs().contains(v)){
continue;
}
if(!isFirst){
signature.append(", ");
}
signature.append("out ").append(v.getType()).append(" ").append(v.getName());
isFirst = false;
if(addParam) {
def.getParams().add(v);
}
}
signature.append("){");
source = source.replaceAll("\\s*void\\s*main\\s*\\(\\s*\\)\\s*\\{", signature.toString());
return source;
}
/**
* 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.
@ -287,19 +332,12 @@ public abstract class ShaderGenerator {
protected abstract void generateVaryings(StringBuilder source, ShaderGenerationInfo info, ShaderType type);
/**
* Appends the given shaderNode declarative part to the shader declarative
* part. If needed the shader type can be determined by fetching the
* shaderNode's definition type.
*
* @see ShaderNode#getDefinition()
* @see ShaderNodeDefinition#getType()
* Appends the shaderNodes function to the shader declarative
* part.
*
* @param nodeDecalarationSource the declaration part of the node
* @param source the StringBuilder to append generated code.
* @param shaderNode the shaderNode.
* @param info the ShaderGenerationInfo.
*/
protected abstract void generateDeclarativeSection(StringBuilder source, ShaderNode shaderNode, String nodeDecalarationSource, ShaderGenerationInfo info);
protected abstract void generateDeclarationSection(StringBuilder source);
/**
* generates the start of the shader main section. this method is
@ -344,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));
@ -363,4 +400,15 @@ public abstract class ShaderGenerator {
}
return index;
}
protected class NodeDeclaration{
String condition;
String source;
public NodeDeclaration(String condition, String source) {
this.condition = condition;
this.source = source;
}
}
}

@ -143,6 +143,20 @@ public class ShaderNode implements Savable, Cloneable {
return inputMapping;
}
/**
* Returns a list of variable mapping for the given node input.
*
* @return the input mappings.
*/
public void getInputMapping(String varName, List<VariableMapping> list) {
list.clear();
for (VariableMapping v : inputMapping) {
if (v.getLeftVariable().getName().equals(varName)){
list.add(v);
}
}
}
/**
* Sets the input mappings.
*

@ -57,8 +57,11 @@ public class ShaderNodeDefinition implements Savable {
private String documentation;
private List<ShaderNodeVariable> inputs = new ArrayList<ShaderNodeVariable>();
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;
/**
* creates a ShaderNodeDefinition
@ -183,8 +186,22 @@ public class ShaderNodeDefinition implements Savable {
public void setPath(String path) {
this.path = path;
}
public String getReturnType() {
return returnType;
}
public void setReturnType(String returnType) {
this.returnType = returnType;
}
public List<ShaderNodeVariable> getParams() {
return params;
}
public void setParams(List<ShaderNodeVariable> params) {
this.params = params;
}
/**
* jme serialization (not used)
@ -194,14 +211,15 @@ public class ShaderNodeDefinition implements Savable {
*/
@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = (OutputCapsule) ex.getCapsule(this);
OutputCapsule oc = ex.getCapsule(this);
oc.write(name, "name", "");
String[] str = new String[shadersLanguage.size()];
oc.write(shadersLanguage.toArray(str), "shadersLanguage", null);
oc.write(shadersPath.toArray(str), "shadersPath", null);
oc.write(type, "type", null);
oc.writeSavableArrayList((ArrayList) inputs, "inputs", new ArrayList<ShaderNodeVariable>());
oc.writeSavableArrayList((ArrayList) outputs, "inputs", new ArrayList<ShaderNodeVariable>());
oc.writeSavableArrayList((ArrayList) outputs, "outputs", new ArrayList<ShaderNodeVariable>());
oc.writeSavableArrayList((ArrayList) params, "params", new ArrayList<ShaderNodeVariable>());
}
public List<String> getShadersLanguage() {
@ -220,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)
*
@ -250,6 +274,8 @@ public class ShaderNodeDefinition implements Savable {
type = ic.readEnum("type", Shader.ShaderType.class, null);
inputs = (List<ShaderNodeVariable>) ic.readSavableArrayList("inputs", new ArrayList<ShaderNodeVariable>());
outputs = (List<ShaderNodeVariable>) ic.readSavableArrayList("outputs", new ArrayList<ShaderNodeVariable>());
params = (List<ShaderNodeVariable>) ic.readSavableArrayList("params", new ArrayList<ShaderNodeVariable>());
im.getAssetManager();
}
/**

@ -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();
}
}
}

@ -31,20 +31,27 @@
*/
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.text.ParseException;
import java.util.*;
import java.util.regex.Matcher;
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
@ -71,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
*
@ -82,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;
}
}
@ -104,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
*/
@ -134,6 +141,405 @@ 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");
}
public static List<ShaderNodeDefinition> parseDefinitions(String glsl) throws ParseException {
List<ShaderNodeDefinition> defs = new ArrayList<>();
String nodesCode[] = glsl.split("#pragma ShaderNode");
for (String code : nodesCode) {
if (code.trim().length() == 0) {
continue;
}
int firstCr = code.indexOf("\n");
int firstBracket = code.indexOf("{");
String pragma = code.substring(0, firstCr);
Matcher m1 = defaultsPattern.matcher(pragma);
String[] defaults = null;
if (m1.find()) {
defaults = m1.group(1).split(",");
}
code = code.substring(firstCr + 1, firstBracket);
Matcher m = typeNamePattern.matcher(code);
String returnType = null;
String functionName = null;
while (m.find()) {
returnType = m.group(1);
functionName = m.group(2);
}
if (returnType == null || functionName == null) {
throw new ParseException("Unmatched return type or function name in \n" + code, firstCr + 1);
}
ShaderNodeDefinition def = new ShaderNodeDefinition();
def.setName(functionName);
def.setReturnType(returnType);
m.reset();
m.usePattern(paramsPattern);
List<ShaderNodeVariable> inputs = new ArrayList<>();
List<ShaderNodeVariable> outputs = new ArrayList<>();
List<ShaderNodeVariable> params = new ArrayList<>();
if (!returnType.equals("void")) {
ShaderNodeVariable result = new ShaderNodeVariable(returnType, "result");
outputs.add(result);
}
int cpt = 0;
while (m.find()) {
String dir = m.group(3);
String type = m.group(4);
String varName = m.group(5);
ShaderNodeVariable v = new ShaderNodeVariable(type, varName);
params.add(v);
String defVal = null;
if (defaults != null && defaults.length > cpt) {
defVal = defaults[cpt].trim();
defVal = defVal.isEmpty() ? null : defVal;
}
v.setDefaultValue(defVal);
switch (dir) {
case "in":
inputs.add(v);
break;
case "out":
outputs.add(v);
break;
default:
throw new ParseException("Missing in or out keyword for variable " + varName + " in function " + functionName, m.start());
}
cpt++;
}
def.setParams(params);
def.setInputs(inputs);
if (outputs.isEmpty()) {
def.setNoOutput(true);
} else {
def.setOutputs(outputs);
}
defs.add(def);
}
return defs;
}
public static Shader.ShaderType getShaderType(String shaderPath) {
String ext = shaderPath.substring(shaderPath.lastIndexOf(".") + 1);
return Shader.ShaderType.fromExtention(ext);
}
public static List<ShaderNodeDefinition> loadSahderNodeDefinition(AssetManager assetManager, String definitionPath) throws ParseException {
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);
for (ShaderNodeDefinition d : defs) {
d.setType(type);
d.getShadersLanguage().add("GLSL100");
d.setPath(definitionPath);
d.getShadersPath().add(definitionPath);
}
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;
}
}

@ -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();
}
}

@ -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);
}
}

@ -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(){
}
}

@ -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();
}
}
}

@ -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;
}
}

@ -1,8 +1,8 @@
#import "Common/ShaderLib/Skinning.glsllib"
void main(){
modModelPosition = (mat4(0.0) +
boneMatrices[int(boneIndex.x)] * boneWeight.x +
boneMatrices[int(boneIndex.y)] * boneWeight.y +
boneMatrices[int(boneIndex.z)] * boneWeight.z +
boneMatrices[int(boneIndex.w)] * boneWeight.w) * vec4(modelPosition.xyz,1.0);
void main(){
#ifdef NUM_BONES
modModelPosition = modelPosition;
Skinning_Compute(modModelPosition);
#endif
}

@ -1,12 +1,10 @@
#import "Common/ShaderLib/Skinning.glsllib"
void main(){
modModelPosition = (mat4(0.0) +
boneMatrices[int(boneIndex.x)] * boneWeight.x +
boneMatrices[int(boneIndex.y)] * boneWeight.y +
boneMatrices[int(boneIndex.z)] * boneWeight.z +
boneMatrices[int(boneIndex.w)] * boneWeight.w) * modelPosition;
mat3 rotMat = mat3(mat[0].xyz, mat[1].xyz, mat[2].xyz);
modModelTangent = rotMat * modelTangent;
modModelNormal = rotMat * modelNormal;
#ifdef NUM_BONES
modModelPosition = modelPosition;
modModelNormal = modelNormal;
modModelTangents = modelTangents;
Skinning_Compute(modModelPosition, modModelNormal, modModelTangents);
#endif
}

@ -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)) {

@ -40,9 +40,11 @@ import com.jme3.material.ShaderGenerationInfo;
import com.jme3.material.TechniqueDef;
import com.jme3.shader.Shader.ShaderType;
import com.jme3.shader.*;
import com.jme3.shader.plugins.ShaderAssetKey;
import com.jme3.util.blockparser.Statement;
import java.io.IOException;
import java.text.ParseException;
import java.util.*;
/**
@ -580,7 +582,7 @@ public class ShaderNodeLoaderDelegate {
multiplicity = multiplicity.toUpperCase();
left.setMultiplicity(multiplicity);
// only declare the variable if the define is defined.
left.setCondition(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);
@ -996,9 +998,15 @@ public class ShaderNodeLoaderDelegate {
List<ShaderNodeDefinition> defs;
try {
defs = assetManager.loadAsset(new ShaderNodeDefinitionKey(definitionPath));
if(definitionPath.endsWith(".j3sn")) {
defs = assetManager.loadAsset(new ShaderNodeDefinitionKey(definitionPath));
} else {
defs = ShaderUtils.loadSahderNodeDefinition(assetManager, definitionPath);
}
} catch (final AssetNotFoundException e) {
throw new MatParseException("Couldn't find " + definitionPath, statement, e);
} catch (ParseException e) {
throw new MatParseException("Error while loading shader node definition" + definitionPath, statement, e);
}
for (final ShaderNodeDefinition definition : defs) {
@ -1057,26 +1065,6 @@ public class ShaderNodeLoaderDelegate {
}
}
/**
* 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 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;
}
}
/**
* Searches a variable in a list from its name and merges the conditions of the

@ -4,7 +4,7 @@ import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.material.Technique;
import com.jme3.material.TechniqueDef;
import com.jme3.math.ColorRGBA;
import com.jme3.math.*;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.shader.Shader;
@ -24,7 +24,7 @@ public class TestShaderNodes extends SimpleApplication {
flyCam.setMoveSpeed(20);
Logger.getLogger("com.jme3").setLevel(Level.WARNING);
Box boxshape1 = new Box(1f, 1f, 1f);
Geometry cube_tex = new Geometry("A Textured Box", boxshape1);
Geometry cube = new Geometry("A Box", boxshape1);
Texture tex = assetManager.loadTexture("Interface/Logo/Monkey.jpg");
Material mat = new Material(assetManager, "Common/MatDefs/Misc/UnshadedNodes.j3md");
@ -37,7 +37,25 @@ public class TestShaderNodes extends SimpleApplication {
mat.setColor("Color", ColorRGBA.Yellow);
mat.setTexture("ColorMap", tex);
cube_tex.setMaterial(mat);
rootNode.attachChild(cube_tex);
cube.setMaterial(mat);
cube.move(-2.5f,0,0);
rootNode.attachChild(cube);
cube = cube.clone();
mat = new Material(assetManager, "jme3test/matdefs/test.j3md");
mat.selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager);
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("ColorMap", tex);
cube.setMaterial(mat);
cube.move(2.5f,0,0);
rootNode.attachChild(cube);
}
}

@ -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);
}
}

@ -0,0 +1,84 @@
package jme3test.material;
import com.jme3.app.SimpleApplication;
import com.jme3.material.*;
import com.jme3.math.ColorRGBA;
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 TestShaderNodesModifApi extends SimpleApplication {
public static void main(String[] args) {
TestShaderNodesModifApi app = new TestShaderNodesModifApi();
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,"jme3test/matdefs/test.j3md");
mb.addMatParam(VarType.Vector4, "Color2");
mb.addMatParam(VarType.Texture2D, "Texture");
mb.technique().inlineVertexNode("vec2","TexCoord", "%texIn")
.inputs(
mb.map("texIn", VertexBuffer.Type.TexCoord)
);
mb.technique().inlineFragmentNode("vec4","TextureFetch","texture2D(%tex, %texCoord)")
.inputs(
mb.map("tex", "MatParam.Texture"),
mb.map("texCoord", "TexCoord.result")
).outputs(
mb.map("result", "Global.color")
);
mb.technique().addNode("ColorMult2", "ColorMult", "jme3test/matdefs/ColorMult.frag")
.inputs(
mb.map("color1", "ColorMult.result"),
mb.map("color2", "MatParam.Color2"),
mb.map("color3", "TextureFetch.result"))
.outputs(
mb.map("result", "Global.color")
);
// TODO we need a way to order the nodes. They could be sorted by scanning the inputs and outputs and building a node tree, then sort it with a topological sort,
// but that won't work in some cases when node output is the Global color or Global position. So we'd need a fallback to manually order the nodes.
// So as long as we implement the fallback... The sort is maybe not worth it.
// API could be mb.technique().moveNode("Node1").before("Node2") or after("Node2")...
// or something like mb.technique().setOrder("Node1", "Node2", etc...) but this would require the user to know all the existing nodes.
// or maybe both...
Material mat = mb.build();
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.setColor("Color2", ColorRGBA.Red);
mat.setTexture("Texture", tex);
cube.setMaterial(mat);
cube.move(0, 0, 0);
rootNode.attachChild(cube);
}
}

@ -0,0 +1,5 @@
#pragma ShaderNode defaults(vec4(1.0), ,vec4(1.0))
vec4 ColorMult(const in vec4 color1, const in vec4 color2, const in vec4 color3){
return color1 * color2 * color3;
}

@ -0,0 +1,9 @@
#pragma ShaderNode
vec4 CommonVert(const in mat4 worldViewProjectionMatrix, const in vec3 modelPosition){
return worldViewProjectionMatrix * vec4(modelPosition, 1.0);
}
#pragma ShaderNode
vec4 DoThing( in vec4 color ){
return color * 0.1;
}

@ -0,0 +1,45 @@
MaterialDef Simple {
MaterialParameters {
Vector4 Color
}
Technique {
WorldParameters {
WorldViewProjectionMatrix
}
VertexShaderNodes {
ShaderNode CommonVert {
Definition: CommonVert: jme3test/matdefs/CommonVert.vert
InputMappings{
worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix
modelPosition = Attr.inPosition
}
OutputMappings{
Global.position = result
}
}
ShaderNode ColorStuff {
Definition: DoThing: jme3test/matdefs/CommonVert.vert
InputMappings{
color = MatParam.Color
}
OutputMappings{
}
}
}
FragmentShaderNodes {
ShaderNode ColorMult {
Definition: ColorMult: jme3test/matdefs/ColorMult.frag
InputMappings{
color2 = ColorStuff.result
}
OutputMappings{
Global.color = result
}
}
}
}
}
Loading…
Cancel
Save