Gltf: added support for PBR colored material

fix-456
Nehon 7 years ago committed by Rémy Bouquet
parent 7951f5a987
commit 3bbfabed5e
  1. 5
      jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
  2. 125
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
  3. 29
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java
  4. 29
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java
  5. 86
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/MaterialAdapter.java
  6. 58
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java

@ -37,6 +37,7 @@ import com.jme3.light.PointLight;
import com.jme3.math.*;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.gltf.GltfModelKey;
import com.jme3.scene.shape.Sphere;
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);
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"));
}

@ -4,13 +4,17 @@ import com.google.gson.*;
import com.google.gson.stream.JsonReader;
import com.jme3.asset.*;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.*;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.*;
import java.io.*;
import java.nio.Buffer;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
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 {
private static final Logger logger = Logger.getLogger(GltfLoader.class.getName());
//Data cache for already parsed JME objects
private Map<String, Object[]> dataCache = new HashMap<>();
private JsonArray scenes;
@ -30,9 +36,14 @@ public class GltfLoader implements AssetLoader {
private JsonArray buffers;
private JsonArray materials;
private Material defaultMat;
private byte[] tmpByteArray;
private AssetInfo info;
private static Map<String, MaterialAdapter> defaultMaterialAdapters = new HashMap<>();
static {
defaultMaterialAdapters.put("pbrMetallicRoughness", new PBRMaterialAdapter());
}
@Override
public Object load(AssetInfo assetInfo) throws IOException {
try {
@ -46,7 +57,6 @@ public class GltfLoader implements AssetLoader {
defaultMat.setFloat("Roughness", 1f);
}
JsonObject root = new JsonParser().parse(new JsonReader(new InputStreamReader(assetInfo.openStream()))).getAsJsonObject();
JsonObject asset = root.getAsJsonObject().get("asset").getAsJsonObject();
@ -66,8 +76,6 @@ public class GltfLoader implements AssetLoader {
buffers = root.getAsJsonArray("buffers");
materials = root.getAsJsonArray("materials");
allocatedTmpByteArray();
JsonPrimitive defaultScene = root.getAsJsonPrimitive("scene");
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) {
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),
//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) {
//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 {
//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();
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.setName(loadMeshName(meshIndex));
} else {
@ -235,10 +221,15 @@ public class GltfLoader implements AssetLoader {
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();
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);
}
meshArray = new Mesh[primitives.size()];
geomArray = new Geometry[primitives.size()];
int index = 0;
for (JsonElement primitive : primitives) {
JsonObject meshObject = primitive.getAsJsonObject();
@ -263,14 +254,28 @@ public class GltfLoader implements AssetLoader {
for (Map.Entry<String, JsonElement> entry : attributes.entrySet()) {
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++;
//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 {
@ -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) {
JsonObject meshData = meshes.get(meshIndex).getAsJsonObject();
return getAsString(meshData, "name");

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

@ -1,8 +1,8 @@
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.math.ColorRGBA;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.util.LittleEndien;
@ -229,6 +229,16 @@ public class GltfUtils {
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) {
JsonElement el = parent.get(name);
return el == null ? null : el.getAsBoolean();
@ -239,6 +249,21 @@ public class GltfUtils {
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) {
if (o == null) {
throw new AssetLoadException(errorMessage);

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

@ -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;
}
}
Loading…
Cancel
Save