Gltf: added support for PBR colored material
This commit is contained in:
parent
7951f5a987
commit
3bbfabed5e
@ -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…
x
Reference in New Issue
Block a user