Răsfoiți Sursa

Gltf: added support for PBR colored material

Nehon 8 ani în urmă
părinte
comite
3bbfabed5e

+ 3 - 2
jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java

@@ -37,6 +37,7 @@ import com.jme3.light.PointLight;
 import com.jme3.math.*;
 import com.jme3.math.*;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
+import com.jme3.scene.plugins.gltf.GltfModelKey;
 import com.jme3.scene.shape.Sphere;
 import com.jme3.scene.shape.Sphere;
 
 
 public class TestGltfLoading extends SimpleApplication {
 public class TestGltfLoading extends SimpleApplication {
@@ -67,8 +68,8 @@ public class TestGltfLoading extends SimpleApplication {
         PointLight pl1 = new PointLight(new Vector3f(-5.0f, -5.0f, -5.0f), ColorRGBA.White.mult(0.5f), 50);
         PointLight pl1 = new PointLight(new Vector3f(-5.0f, -5.0f, -5.0f), ColorRGBA.White.mult(0.5f), 50);
         rootNode.addLight(pl1);
         rootNode.addLight(pl1);
 
 
-        //rootNode.attachChild(assetManager.loadModel("Models/gltf/box/box.gltf"));
-        rootNode.attachChild(assetManager.loadModel("Models/gltf/duck/Duck.gltf"));
+        rootNode.attachChild(assetManager.loadModel("Models/gltf/box/box.gltf"));
+        //rootNode.attachChild(assetManager.loadModel(new GltfModelKey("Models/gltf/duck/Duck.gltf")));
 
 
         //rootNode.attachChild(assetManager.loadModel("Models/gltf/hornet/scene.gltf"));
         //rootNode.attachChild(assetManager.loadModel("Models/gltf/hornet/scene.gltf"));
     }
     }

+ 85 - 40
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java

@@ -4,13 +4,17 @@ import com.google.gson.*;
 import com.google.gson.stream.JsonReader;
 import com.google.gson.stream.JsonReader;
 import com.jme3.asset.*;
 import com.jme3.asset.*;
 import com.jme3.material.Material;
 import com.jme3.material.Material;
+import com.jme3.material.RenderState;
 import com.jme3.math.*;
 import com.jme3.math.*;
+import com.jme3.renderer.queue.RenderQueue;
 import com.jme3.scene.*;
 import com.jme3.scene.*;
 
 
 import java.io.*;
 import java.io.*;
 import java.nio.Buffer;
 import java.nio.Buffer;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 
 import static com.jme3.scene.plugins.gltf.GltfUtils.*;
 import static com.jme3.scene.plugins.gltf.GltfUtils.*;
 
 
@@ -20,6 +24,8 @@ import static com.jme3.scene.plugins.gltf.GltfUtils.*;
  */
  */
 public class GltfLoader implements AssetLoader {
 public class GltfLoader implements AssetLoader {
 
 
+    private static final Logger logger = Logger.getLogger(GltfLoader.class.getName());
+
     //Data cache for already parsed JME objects
     //Data cache for already parsed JME objects
     private Map<String, Object[]> dataCache = new HashMap<>();
     private Map<String, Object[]> dataCache = new HashMap<>();
     private JsonArray scenes;
     private JsonArray scenes;
@@ -30,9 +36,14 @@ public class GltfLoader implements AssetLoader {
     private JsonArray buffers;
     private JsonArray buffers;
     private JsonArray materials;
     private JsonArray materials;
     private Material defaultMat;
     private Material defaultMat;
-    private byte[] tmpByteArray;
     private AssetInfo info;
     private AssetInfo info;
 
 
+    private static Map<String, MaterialAdapter> defaultMaterialAdapters = new HashMap<>();
+
+    static {
+        defaultMaterialAdapters.put("pbrMetallicRoughness", new PBRMaterialAdapter());
+    }
+
     @Override
     @Override
     public Object load(AssetInfo assetInfo) throws IOException {
     public Object load(AssetInfo assetInfo) throws IOException {
         try {
         try {
@@ -46,7 +57,6 @@ public class GltfLoader implements AssetLoader {
                 defaultMat.setFloat("Roughness", 1f);
                 defaultMat.setFloat("Roughness", 1f);
             }
             }
 
 
-
             JsonObject root = new JsonParser().parse(new JsonReader(new InputStreamReader(assetInfo.openStream()))).getAsJsonObject();
             JsonObject root = new JsonParser().parse(new JsonReader(new InputStreamReader(assetInfo.openStream()))).getAsJsonObject();
 
 
             JsonObject asset = root.getAsJsonObject().get("asset").getAsJsonObject();
             JsonObject asset = root.getAsJsonObject().get("asset").getAsJsonObject();
@@ -66,8 +76,6 @@ public class GltfLoader implements AssetLoader {
             buffers = root.getAsJsonArray("buffers");
             buffers = root.getAsJsonArray("buffers");
             materials = root.getAsJsonArray("materials");
             materials = root.getAsJsonArray("materials");
 
 
-            allocatedTmpByteArray();
-
             JsonPrimitive defaultScene = root.getAsJsonPrimitive("scene");
             JsonPrimitive defaultScene = root.getAsJsonPrimitive("scene");
 
 
             Node n = loadScenes(defaultScene);
             Node n = loadScenes(defaultScene);
@@ -85,21 +93,6 @@ public class GltfLoader implements AssetLoader {
         }
         }
     }
     }
 
 
-    private void allocatedTmpByteArray() {
-        //Allocate the tmpByteArray to the biggest bufferView
-        if (bufferViews == null) {
-            throw new AssetLoadException("No buffer view defined but one is referenced by an accessor");
-        }
-        int maxLength = 0;
-        for (JsonElement bufferView : bufferViews) {
-            Integer byteLength = getAsInteger(bufferView.getAsJsonObject(), "byteLength");
-            if (byteLength != null && maxLength < byteLength) {
-                maxLength = byteLength;
-            }
-        }
-        tmpByteArray = new byte[maxLength];
-    }
-
     private boolean isSupported(String version, String minVersion) {
     private boolean isSupported(String version, String minVersion) {
         return "2.0".equals(version);
         return "2.0".equals(version);
     }
     }
@@ -152,26 +145,19 @@ public class GltfLoader implements AssetLoader {
 
 
             //there is a mesh in this node, however gltf can split meshes in primitives (some kind of sub meshes),
             //there is a mesh in this node, however gltf can split meshes in primitives (some kind of sub meshes),
             //We don't have this in JME so we have to make one mesh and one Geometry for each primitive.
             //We don't have this in JME so we have to make one mesh and one Geometry for each primitive.
-            Mesh[] primitives = loadMeshPrimitives(meshIndex);
+            Geometry[] primitives = loadMeshPrimitives(meshIndex);
             if (primitives.length > 1) {
             if (primitives.length > 1) {
-                //only one mesh, lets just make a geometry.
-                Geometry geometry = new Geometry(null, primitives[0]);
-                geometry.setMaterial(mat);
-                geometry.updateModelBound();
-                spatial = geometry;
+                //only one geometry, let's not wrap it in another node.
+                spatial = primitives[0];
             } else {
             } else {
-                //several meshes, let's make a parent Node and attach several geometries to it
+                //several geometries, let's make a parent Node and attach them to it
                 Node node = new Node();
                 Node node = new Node();
-                for (Mesh primitive : primitives) {
-                    Geometry geom = new Geometry(null, primitive);
-                    geom.setMaterial(mat);
-                    geom.updateModelBound();
-                    node.attachChild(geom);
+                for (Geometry primitive : primitives) {
+                    node.attachChild(primitive);
                 }
                 }
                 spatial = node;
                 spatial = node;
             }
             }
 
 
-
             spatial.setName(loadMeshName(meshIndex));
             spatial.setName(loadMeshName(meshIndex));
 
 
         } else {
         } else {
@@ -235,10 +221,15 @@ public class GltfLoader implements AssetLoader {
         return transform;
         return transform;
     }
     }
 
 
-    private Mesh[] loadMeshPrimitives(int meshIndex) throws IOException {
-        Mesh[] meshArray = (Mesh[]) fetchFromCache("meshes", meshIndex, Object.class);
-        if (meshArray != null) {
-            return meshArray;
+    private Geometry[] loadMeshPrimitives(int meshIndex) throws IOException {
+        Geometry[] geomArray = (Geometry[]) fetchFromCache("meshes", meshIndex, Object.class);
+        if (geomArray != null) {
+            //cloning the geoms.
+            Geometry[] geoms = new Geometry[geomArray.length];
+            for (int i = 0; i < geoms.length; i++) {
+                geoms[i] = geomArray[i].clone(false);
+            }
+            return geoms;
         }
         }
         JsonObject meshData = meshes.get(meshIndex).getAsJsonObject();
         JsonObject meshData = meshes.get(meshIndex).getAsJsonObject();
         JsonArray primitives = meshData.getAsJsonArray("primitives");
         JsonArray primitives = meshData.getAsJsonArray("primitives");
@@ -246,7 +237,7 @@ public class GltfLoader implements AssetLoader {
             throw new AssetLoadException("Can't find any primitives in mesh " + meshIndex);
             throw new AssetLoadException("Can't find any primitives in mesh " + meshIndex);
         }
         }
 
 
-        meshArray = new Mesh[primitives.size()];
+        geomArray = new Geometry[primitives.size()];
         int index = 0;
         int index = 0;
         for (JsonElement primitive : primitives) {
         for (JsonElement primitive : primitives) {
             JsonObject meshObject = primitive.getAsJsonObject();
             JsonObject meshObject = primitive.getAsJsonObject();
@@ -263,14 +254,28 @@ public class GltfLoader implements AssetLoader {
             for (Map.Entry<String, JsonElement> entry : attributes.entrySet()) {
             for (Map.Entry<String, JsonElement> entry : attributes.entrySet()) {
                 mesh.setBuffer(loadVertexBuffer(entry.getValue().getAsInt(), getVertexBufferType(entry.getKey())));
                 mesh.setBuffer(loadVertexBuffer(entry.getValue().getAsInt(), getVertexBufferType(entry.getKey())));
             }
             }
-            meshArray[index] = mesh;
+            Geometry geom = new Geometry(null, mesh);
+
+            Integer materialIndex = getAsInteger(meshObject, "material");
+            if (materialIndex == null) {
+                geom.setMaterial(defaultMat);
+            } else {
+                geom.setMaterial(loadMaterial(materialIndex));
+                if (geom.getMaterial().getAdditionalRenderState().getBlendMode() == RenderState.BlendMode.Alpha) {
+                    //Alpha blending is on on this material let's place the geom in the transparent bucket
+                    geom.setQueueBucket(RenderQueue.Bucket.Transparent);
+                }
+            }
+
+            geom.updateModelBound();
+            geomArray[index] = geom;
             index++;
             index++;
 
 
             //TODO material, targets(morph anim...)
             //TODO material, targets(morph anim...)
         }
         }
 
 
-        addToCache("meshes", meshIndex, meshArray, meshes.size());
-        return meshArray;
+        addToCache("meshes", meshIndex, geomArray, meshes.size());
+        return geomArray;
     }
     }
 
 
     private VertexBuffer loadVertexBuffer(int accessorIndex, VertexBuffer.Type bufferType) throws IOException {
     private VertexBuffer loadVertexBuffer(int accessorIndex, VertexBuffer.Type bufferType) throws IOException {
@@ -373,6 +378,46 @@ public class GltfLoader implements AssetLoader {
 
 
     }
     }
 
 
+    private Material loadMaterial(int materialIndex) {
+        if (materials == null) {
+            throw new AssetLoadException("There is no material defined yet a mesh references one");
+        }
+        JsonObject matData = materials.get(materialIndex).getAsJsonObject();
+        JsonObject pbrMat = matData.getAsJsonObject("pbrMetallicRoughness");
+
+        if (pbrMat == null) {
+            logger.log(Level.WARNING, "Unable to find any pbrMetallicRoughness material entry in material " + materialIndex + ". Only PBR material is supported for now");
+            return defaultMat;
+        }
+        MaterialAdapter adapter = null;
+        if (info.getKey() instanceof GltfModelKey) {
+            adapter = ((GltfModelKey) info.getKey()).getAdapterForMaterial("pbrMetallicRoughness");
+        }
+        if (adapter == null) {
+            adapter = defaultMaterialAdapters.get("pbrMetallicRoughness");
+        }
+
+        Material mat = adapter.getMaterial(info.getManager());
+        mat.setName(getAsString(matData, "name"));
+
+        adapter.setParam(mat, "baseColorFactor", getAsColor(pbrMat, "baseColorFactor", ColorRGBA.White));
+        adapter.setParam(mat, "metallicFactor", getAsFloat(pbrMat, "metallicFactor", 1f));
+        adapter.setParam(mat, "roughnessFactor", getAsFloat(pbrMat, "roughnessFactor", 1f));
+        adapter.setParam(mat, "emissiveFactor", getAsColor(matData, "emissiveFactor", ColorRGBA.Black));
+        adapter.setParam(mat, "alphaMode", getAsString(matData, "alphaMode"));
+        adapter.setParam(mat, "alphaCutoff", getAsFloat(matData, "alphaCutoff"));
+        adapter.setParam(mat, "doubleSided", getAsBoolean(matData, "doubleSided"));
+
+        //TODO textures
+        //adapter.setParam(mat, "baseColorTexture", readTexture);
+        //adapter.setParam(mat, "metallicRoughnessTexture", readTexture);
+        //adapter.setParam(mat, "normalTexture", readTexture);
+        //adapter.setParam(mat, "occlusionTexture", readTexture);
+        //adapter.setParam(mat, "emissiveTexture", readTexture);
+
+        return mat;
+    }
+
     private String loadMeshName(int meshIndex) {
     private String loadMeshName(int meshIndex) {
         JsonObject meshData = meshes.get(meshIndex).getAsJsonObject();
         JsonObject meshData = meshes.get(meshIndex).getAsJsonObject();
         return getAsString(meshData, "name");
         return getAsString(meshData, "name");

+ 29 - 0
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java

@@ -0,0 +1,29 @@
+package com.jme3.scene.plugins.gltf;
+
+import com.jme3.asset.ModelKey;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Created by Nehon on 08/08/2017.
+ */
+public class GltfModelKey extends ModelKey {
+
+    private Map<String, MaterialAdapter> materialAdapters = new HashMap<>();
+
+    public GltfModelKey(String name) {
+        super(name);
+    }
+
+    public GltfModelKey() {
+    }
+
+    public void registerMaterialAdapter(String gltfMaterialName, MaterialAdapter adapter) {
+        materialAdapters.put(gltfMaterialName, adapter);
+    }
+
+    public MaterialAdapter getAdapterForMaterial(String gltfMaterialName) {
+        return materialAdapters.get(gltfMaterialName);
+    }
+}

+ 27 - 2
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java

@@ -1,8 +1,8 @@
 package com.jme3.scene.plugins.gltf;
 package com.jme3.scene.plugins.gltf;
 
 
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
+import com.google.gson.*;
 import com.jme3.asset.AssetLoadException;
 import com.jme3.asset.AssetLoadException;
+import com.jme3.math.ColorRGBA;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.VertexBuffer;
 import com.jme3.scene.VertexBuffer;
 import com.jme3.util.LittleEndien;
 import com.jme3.util.LittleEndien;
@@ -229,6 +229,16 @@ public class GltfUtils {
         return el == null ? defaultValue : el.getAsInt();
         return el == null ? defaultValue : el.getAsInt();
     }
     }
 
 
+    public static Float getAsFloat(JsonObject parent, String name) {
+        JsonElement el = parent.get(name);
+        return el == null ? null : el.getAsFloat();
+    }
+
+    public static Float getAsFloat(JsonObject parent, String name, float defaultValue) {
+        JsonElement el = parent.get(name);
+        return el == null ? defaultValue : el.getAsFloat();
+    }
+
     public static Boolean getAsBoolean(JsonObject parent, String name) {
     public static Boolean getAsBoolean(JsonObject parent, String name) {
         JsonElement el = parent.get(name);
         JsonElement el = parent.get(name);
         return el == null ? null : el.getAsBoolean();
         return el == null ? null : el.getAsBoolean();
@@ -239,6 +249,21 @@ public class GltfUtils {
         return el == null ? defaultValue : el.getAsBoolean();
         return el == null ? defaultValue : el.getAsBoolean();
     }
     }
 
 
+    public static ColorRGBA getAsColor(JsonObject parent, String name) {
+        JsonElement el = parent.get(name);
+        if (el == null) {
+            return null;
+        }
+        JsonArray color = el.getAsJsonArray();
+        return new ColorRGBA(color.get(0).getAsFloat(), color.get(1).getAsFloat(), color.get(2).getAsFloat(), color.get(3).getAsFloat());
+    }
+
+    public static ColorRGBA getAsColor(JsonObject parent, String name, ColorRGBA defaultValue) {
+        ColorRGBA color = getAsColor(parent, name);
+        return color == null ? defaultValue : color;
+    }
+
+
     public static void assertNotNull(Object o, String errorMessage) {
     public static void assertNotNull(Object o, String errorMessage) {
         if (o == null) {
         if (o == null) {
             throw new AssetLoadException(errorMessage);
             throw new AssetLoadException(errorMessage);

+ 86 - 0
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/MaterialAdapter.java

@@ -0,0 +1,86 @@
+package com.jme3.scene.plugins.gltf;
+
+
+import com.jme3.asset.AssetLoadException;
+import com.jme3.asset.AssetManager;
+import com.jme3.material.*;
+import com.jme3.math.*;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Texture;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A MaterialAdapter allows to map a GLTF material to a JME material.
+ * It maps each gltf parameter to it's matching parameter in the JME material,
+ * and allows for some conversion if the JME material model doesn't exactly match the gltf material model
+ * Created by Nehon on 08/08/2017.
+ */
+public abstract class MaterialAdapter {
+
+    private Map<String, String> paramsMapping = new HashMap<>();
+
+    /**
+     * Should return the material definition used by this material adapter
+     *
+     * @return
+     */
+    protected abstract String getMaterialDefPath();
+
+    protected abstract MatParam adaptMatParam(Material mat, MatParam param);
+
+    public Material getMaterial(AssetManager assetManager) {
+        return new Material(assetManager, getMaterialDefPath());
+    }
+
+    public void setParam(Material mat, String gltfParamName, Object value) {
+        String name = getJmeParamName(gltfParamName);
+        if (name == null || value == null) {
+            //no mapping registered or value is null, let's ignore this param
+            return;
+        }
+        MatParam param;
+        if (value instanceof Texture) {
+            MatParam defParam = mat.getMaterialDef().getMaterialParam(name);
+            if (defParam == null) {
+                throw new AssetLoadException("Material definition " + getMaterialDefPath() + " has not param with name" + name);
+            }
+            if (!(defParam instanceof MatParamTexture)) {
+                throw new AssetLoadException("param with name" + name + "in material definition " + getMaterialDefPath() + " should be a texture param");
+            }
+            param = new MatParamTexture(VarType.Texture2D, name, (Texture) value, ((MatParamTexture) defParam).getColorSpace());
+            param = adaptMatParam(mat, param);
+            if (param != null) {
+                mat.setTextureParam(param.getName(), param.getVarType(), (Texture) param.getValue());
+            }
+        } else {
+            param = new MatParam(getVarType(value), name, value);
+            param = adaptMatParam(mat, param);
+            if (param != null) {
+                mat.setParam(param.getName(), param.getVarType(), param.getValue());
+            }
+        }
+    }
+
+    protected void addParamMapping(String gltfParamName, String jmeParamName) {
+        paramsMapping.put(gltfParamName, jmeParamName);
+    }
+
+    protected String getJmeParamName(String gltfParamName) {
+        return paramsMapping.get(gltfParamName);
+    }
+
+    private VarType getVarType(Object value) {
+        if (value instanceof Float) return VarType.Float;
+        if (value instanceof Integer) return VarType.Int;
+        if (value instanceof Boolean) return VarType.Boolean;
+        if (value instanceof ColorRGBA) return VarType.Vector4;
+        if (value instanceof Vector4f) return VarType.Vector4;
+        if (value instanceof Vector3f) return VarType.Vector3;
+        if (value instanceof Vector2f) return VarType.Vector2;
+        if (value instanceof Matrix3f) return VarType.Matrix3;
+        if (value instanceof Matrix4f) return VarType.Matrix4;
+        throw new AssetLoadException("Unsupported material parameter type : " + value.getClass().getSimpleName());
+    }
+}

+ 58 - 0
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java

@@ -0,0 +1,58 @@
+package com.jme3.scene.plugins.gltf;
+
+import com.jme3.material.*;
+
+/**
+ * Created by Nehon on 08/08/2017.
+ */
+public class PBRMaterialAdapter extends MaterialAdapter {
+
+
+    public PBRMaterialAdapter() {
+        addParamMapping("baseColorFactor", "BaseColor");
+        addParamMapping("baseColorTexture", "BaseColorMap");
+        addParamMapping("metallicFactor", "Metallic");
+        addParamMapping("roughnessFactor", "Roughness");
+        addParamMapping("metallicRoughnessTexture", "MetallicRoughnessMap");
+        addParamMapping("normalTexture", "NormalMap");
+        addParamMapping("occlusionTexture", "LightMap");
+        addParamMapping("emisiveTexture", "EmissiveMap");
+        addParamMapping("emisiveFactor", "Emissive");
+        addParamMapping("alphaMode", "alpha");
+        addParamMapping("alphaCutoff", "AlphaDiscardThreshold");
+        addParamMapping("doubleSided", "doubleSided");
+    }
+
+    @Override
+    protected String getMaterialDefPath() {
+        return "Common/MatDefs/Light/PBRLighting.j3md";
+    }
+
+    @Override
+    protected MatParam adaptMatParam(Material mat, MatParam param) {
+        if (param.getName().equals("alpha")) {
+            String alphaMode = (String) param.getValue();
+            switch (alphaMode) {
+                case "MASK":
+                case "BLEND":
+                    mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
+            }
+            return null;
+        }
+        if (param.getName().equals("doubleSided")) {
+            boolean doubleSided = (boolean) param.getValue();
+            if (doubleSided) {
+                //Note that this is not completely right as normals on the back side will be in the wrong direction.
+                mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
+            }
+            return null;
+        }
+        if (param.getName().equals("MetallicRoughnessMap")) {
+            //use packed Metallic/Roughness
+            mat.setBoolean("UsePackedMR", true);
+        }
+
+
+        return param;
+    }
+}