From ef067952b21f01806a96de0c938a1331b4669567 Mon Sep 17 00:00:00 2001
From: theMinka
Date: Sun, 19 Apr 2026 12:35:12 +0200
Subject: [PATCH 01/14] Fixed multiple extension handling in
CustomContentManager
If multiple GLTF extensions are present on an element, the CustomContentManager now processes all supported ones instead of just the first one.
---
.../java/com/jme3/scene/plugins/gltf/CustomContentManager.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java
index d9c4cbf7d8..5898887e58 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java
@@ -214,7 +214,7 @@ private T readExtension(String name, JsonElement el, T input) throws AssetLo
continue;
}
try {
- return (T) loader.handleExtension(gltfLoader, name, el, ext.getValue(), input);
+ input = (T) loader.handleExtension(gltfLoader, name, el, ext.getValue(), input);
} catch (ClassCastException e) {
throw new AssetLoadException("Extension loader " + loader.getClass().getName() + " for extension " + ext.getKey() + " is incompatible with type " + input.getClass(), e);
}
From 317a56abf18e11a4dfccdd170b7ea29b43e5d085 Mon Sep 17 00:00:00 2001
From: theMinka
Date: Sun, 19 Apr 2026 20:28:24 +0200
Subject: [PATCH 02/14] Added a flag to GltfModelKey to switch between the old
and new material creation process
---
.../jme3/scene/plugins/gltf/GltfLoader.java | 10 +++++++++
.../jme3/scene/plugins/gltf/GltfModelKey.java | 21 +++++++++++++++++++
.../jme3/scene/plugins/gltf/GltfUtils.java | 5 +++++
3 files changed, 36 insertions(+)
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
index 69b0696df3..8dd1f1d4dd 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
@@ -803,6 +803,16 @@ protected ByteBuffer getBytes(int bufferIndex, String uri, Integer bufferLength)
}
public Material readMaterial(int materialIndex) throws IOException {
+ // Fallback to the old material adapter system, if the legacy flag is set.
+ if (GltfUtils.isMaterialAdaptersEnabled(info)) {
+ return readMaterialUsingMaterialAdapters(materialIndex);
+ }
+
+ // TODO Implement new material factory system.
+ return defaultMat;
+ }
+
+ protected Material readMaterialUsingMaterialAdapters(int materialIndex) throws IOException {
assertNotNull(materials, "There is no material defined yet a mesh references one");
JsonObject matData = materials.get(materialIndex).getAsJsonObject();
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java
index 2490243a00..de66ffbc0a 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java
@@ -52,6 +52,13 @@
*/
public class GltfModelKey extends ModelKey {
+ /**
+ * Enables or disables the legacy material adapter system.
+ * This should only be used in older projects for backward compatibility.
+ */
+ // TODO Set the default to false, after the new material creation process has been implemented
+ private boolean materialAdaptersEnabled = true;
+
private Map materialAdapters = new HashMap<>();
private static Map extensionLoaders = new HashMap<>();
private boolean keepSkeletonPose = false;
@@ -101,6 +108,20 @@ public boolean isStrict() {
return strictExtensionCheck;
}
+ public boolean isMaterialAdaptersEnabled() {
+ return materialAdaptersEnabled;
+ }
+
+ /**
+ * Enables or disables the legacy material adapter system.
+ * This should only be used in older projects for backward compatibility.
+ *
+ * @param materialAdaptersEnabled The value to set.
+ */
+ public void setMaterialAdaptersEnabled(boolean materialAdaptersEnabled) {
+ this.materialAdaptersEnabled = materialAdaptersEnabled;
+ }
+
/**
* Registers a MaterialAdapter for the given materialName.
* The materialName must be "pbrMetallicRoughness" or any name from KHR_materials glTF Extension (for example "pbrSpecularGlossiness" for "KHR_materials_pbrSpecularGlossiness" extension)
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java
index 677f15e6ea..6aff505079 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java
@@ -716,6 +716,11 @@ public static GltfModelKey getKey(AssetInfo info) {
return null;
}
+ public static boolean isMaterialAdaptersEnabled(AssetInfo info) {
+ GltfModelKey key = getKey(info);
+ return key != null && key.isMaterialAdaptersEnabled();
+ }
+
public static MaterialAdapter getAdapterForMaterial(AssetInfo info, String defName) {
GltfModelKey key = getKey(info);
if (key == null) {
From c164633a22c4f5a1fbe6c04feeb8f5a72200c493 Mon Sep 17 00:00:00 2001
From: theMinka
Date: Sun, 19 Apr 2026 20:38:40 +0200
Subject: [PATCH 03/14] Implemented new material creation process in GltfLoader
* Step 1: Collect all material data in the new GltfMaterialData object
* Step 1.1: GltfLoader reads all standard GLTF parameters
* Step 1.2: ExtensionLoader and ExtrasLoader can read additional GLTF parameters
* Step 2: Find a matching GltfMaterialFactory and use it to create the material
---
.../jme3/scene/plugins/gltf/GltfLoader.java | 106 ++++++++++-
.../scene/plugins/gltf/GltfMaterialData.java | 176 ++++++++++++++++++
.../plugins/gltf/GltfMaterialFactory.java | 68 +++++++
.../jme3/scene/plugins/gltf/GltfModelKey.java | 3 +-
4 files changed, 350 insertions(+), 3 deletions(-)
create mode 100644 jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialData.java
create mode 100644 jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialFactory.java
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
index 8dd1f1d4dd..77d2d4901a 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
@@ -31,6 +31,7 @@
*/
package com.jme3.scene.plugins.gltf;
+import static com.jme3.scene.plugins.gltf.GltfMaterialData.*;
import static com.jme3.scene.plugins.gltf.GltfUtils.assertNotNull;
import static com.jme3.scene.plugins.gltf.GltfUtils.findCommonAncestor;
import static com.jme3.scene.plugins.gltf.GltfUtils.getAdapterForMaterial;
@@ -109,6 +110,7 @@
import com.jme3.util.BufferInputStream;
import com.jme3.util.BufferUtils;
import com.jme3.util.IntMap;
+import com.jme3.util.SafeArrayList;
import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
/**
@@ -150,6 +152,8 @@ public class GltfLoader implements AssetLoader {
Map> skinnedSpatials = new HashMap<>();
private final IntMap skinBuffers = new IntMap<>();
+ private static SafeArrayList materialFactoryList = new SafeArrayList<>(GltfMaterialFactory.class);
+
public GltfLoader() {
defaultMaterialAdapters.put("pbrMetallicRoughness", new PBRMetalRoughMaterialAdapter());
}
@@ -808,7 +812,63 @@ public Material readMaterial(int materialIndex) throws IOException {
return readMaterialUsingMaterialAdapters(materialIndex);
}
- // TODO Implement new material factory system.
+ assertNotNull(materials, "There is no material defined yet a mesh references one");
+ JsonObject materialJson = materials.get(materialIndex).getAsJsonObject();
+
+ GltfMaterialData gltfMaterialData = readStandardMaterialParameters(materialJson);
+ gltfMaterialData = customContentManager.readExtensionAndExtras("material", materialJson, gltfMaterialData);
+ return createMaterial(gltfMaterialData, materialIndex);
+ }
+
+ protected GltfMaterialData readStandardMaterialParameters(JsonObject materialJson) throws IOException {
+ GltfMaterialData gltfMaterialData = new GltfMaterialData();
+ gltfMaterialData.setGltfParam(MATERIAL_NAME_PARAM, getAsString(materialJson, "name"));
+
+ JsonObject pbrMetallicRoughnessJson = materialJson.getAsJsonObject("pbrMetallicRoughness");
+ if (pbrMetallicRoughnessJson != null) {
+ gltfMaterialData.setGltfParam(BASE_COLOR_PARAM, getAsColor(pbrMetallicRoughnessJson, "baseColorFactor", ColorRGBA.White));
+ gltfMaterialData.setGltfParam(BASE_COLOR_TEXTURE_PARAM, getAsTexture2D(pbrMetallicRoughnessJson, "baseColorTexture"));
+ gltfMaterialData.setGltfParam(METALLIC_FACTOR_PARAM, getAsFloat(pbrMetallicRoughnessJson, "metallicFactor", 1f));
+ gltfMaterialData.setGltfParam(ROUGHNESS_FACTOR_PARAM, getAsFloat(pbrMetallicRoughnessJson, "roughnessFactor", 1f));
+ gltfMaterialData.setGltfParam(METALLIC_ROUGHNESS_TEXTURE_PARAM, getAsTexture2D(pbrMetallicRoughnessJson, "metallicRoughnessTexture"));
+ }
+
+ JsonObject normalTextureJson = materialJson.getAsJsonObject("normalTexture");
+ if (normalTextureJson != null) {
+ gltfMaterialData.setGltfParam(NORMAL_TEXTURE_PARAM, readTexture(normalTextureJson));
+ gltfMaterialData.setGltfParam(NORMAL_SCALE_PARAM, getAsFloat(normalTextureJson, "scale"));
+ useNormalsFlag = true;
+ }
+
+ JsonObject occlusionTextureJson = materialJson.getAsJsonObject("occlusionTexture");
+ if (occlusionTextureJson != null) {
+ gltfMaterialData.setGltfParam(OCCLUSION_TEXTURE_PARAM, readTexture(occlusionTextureJson));
+ gltfMaterialData.setGltfParam(OCCLUSION_TEXTURE_STRENGTH_PARAM, getAsFloat(occlusionTextureJson, "strength"));
+ }
+
+ gltfMaterialData.setGltfParam(EMISSIV_TEXTURE_PARAM, getAsTexture2D(materialJson, "emissiveTexture"));
+ gltfMaterialData.setGltfParam(EMISSIV_COLOR_PARAM, getAsColor(materialJson, "emissiveFactor", ColorRGBA.Black));
+
+ String alphaMode = getAsString(materialJson, "alphaMode");
+ gltfMaterialData.setGltfParam(ALPHA_MODE_PARAM, alphaMode);
+ if ("MASK".equals(alphaMode)) {
+ gltfMaterialData.setGltfParam(ALPHA_CUTOFF_PARAM, getAsFloat(materialJson, "alphaCutoff"));
+ }
+
+ gltfMaterialData.setGltfParam(DOUBLE_SIDED_PARAM, getAsBoolean(materialJson, "doubleSided"));
+
+ return gltfMaterialData;
+ }
+
+ protected Material createMaterial(GltfMaterialData gltfMaterialData, int materialIndex) {
+ for (GltfMaterialFactory gltfMaterialFactory : materialFactoryList) {
+ if (gltfMaterialFactory.accepts(info.getKey(), gltfMaterialData)) {
+ return gltfMaterialFactory.createMaterial(info.getManager(), info.getKey(), gltfMaterialData);
+ }
+ }
+
+ logger.log(Level.WARNING, "Couldn't find any matching GltfMaterialFactory for material " + materialIndex);
+ useNormalsFlag = false;
return defaultMat;
}
@@ -932,6 +992,10 @@ public void readCameras() throws IOException {
}
}
+ protected Texture2D getAsTexture2D(JsonObject jsonObject, String textureName) throws IOException {
+ return readTexture(jsonObject.getAsJsonObject(textureName));
+ }
+
public Texture2D readTexture(JsonObject texture) throws IOException {
return readTexture(texture, false);
}
@@ -1725,4 +1789,44 @@ public static void registerDefaultExtrasLoader(Class extends ExtrasLoader> loa
public static void unregisterDefaultExtrasLoader() {
CustomContentManager.defaultExtraLoaderClass = UserDataLoader.class;
}
+
+ /**
+ * Registers a new material factory and places it before all existing factories.
+ * The ordering of these factories defines their priority. When a new material needs to be created,
+ * the loader searches for the first material factory that accepts the given material data.
+ *
+ * @param materialFactory The {@link GltfMaterialFactory} to register.
+ */
+ public static void registerMaterialFactoryFirst(GltfMaterialFactory materialFactory) {
+ unregisterMaterialFactory(materialFactory.getClass());
+ materialFactoryList.add(0, materialFactory);
+ }
+
+ /**
+ * Registers a new material factory and places it behind all existing factories.
+ * The ordering of these factories defines their priority. When a new material needs to be created,
+ * the loader searches for the first material factory that accepts the given material data.
+ *
+ * @param materialFactory The {@link GltfMaterialFactory} to register.
+ */
+ public static void registerMaterialFactoryLast(GltfMaterialFactory materialFactory) {
+ unregisterMaterialFactory(materialFactory.getClass());
+ materialFactoryList.add(materialFactory);
+ }
+
+ /**
+ * Unregisters a material factory by its class.
+ *
+ * @param materialFactoryClass The class of the {@link GltfMaterialFactory} to unregister.
+ */
+ public static void unregisterMaterialFactory(Class extends GltfMaterialFactory> materialFactoryClass) {
+ materialFactoryList.removeIf(materialFactoryClass::isInstance);
+ }
+
+ /**
+ * Unregisters all material factories.
+ */
+ public static void unregisterAllMaterialFactories() {
+ materialFactoryList.clear();
+ }
}
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialData.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialData.java
new file mode 100644
index 0000000000..cac559e900
--- /dev/null
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialData.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2009-2026 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.gltf;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Stores all data of a single material from a GLTF file.
+ * This data can then be used by a {@link GltfMaterialFactory} to create a new material.
+ *
+ *
Parameter naming convention
+ *
+ * -
+ * Parameter names should use single dots '.' as a separator.
+ *
+ * -
+ * All standard parameter names are directly taken from the
+ * GLTF specs document
+ * (ยง 5.19. Material and following).
+ *
+ * -
+ * All additional parameter names from GLTF extensions should start with {@link #MATERIAL_EXTENSION_PARAM_PREFIX}
+ * followed by the name of the extension (e.g. "KHR_materials_unlit") and finished by the parameter name.
+ *
+ * -
+ * All additional parameter names from GLTF extras should start with {@link #MATERIAL_EXTRA_PARAM_PREFIX}.
+ *
+ *
+ *
+ */
+public class GltfMaterialData {
+
+ public static final String MATERIAL_NAME_PARAM = "material.name";
+
+ public static final String BASE_COLOR_PARAM = "material.pbrMetallicRoughness.baseColorFactor";
+
+ public static final String BASE_COLOR_TEXTURE_PARAM = "material.pbrMetallicRoughness.baseColorTexture";
+
+ public static final String METALLIC_FACTOR_PARAM = "material.pbrMetallicRoughness.metallicFactor";
+
+ public static final String ROUGHNESS_FACTOR_PARAM = "material.pbrMetallicRoughness.roughnessFactor";
+
+ public static final String METALLIC_ROUGHNESS_TEXTURE_PARAM = "material.pbrMetallicRoughness.metallicRoughnessTexture";
+
+ public static final String NORMAL_TEXTURE_PARAM = "material.normalTexture";
+
+ public static final String NORMAL_SCALE_PARAM = "material.normalTextureInfo.scale";
+
+ public static final String OCCLUSION_TEXTURE_PARAM = "material.occlusionTexture";
+
+ public static final String OCCLUSION_TEXTURE_STRENGTH_PARAM = "material.occlusionTextureInfo.strength";
+
+ public static final String EMISSIV_TEXTURE_PARAM = "material.emissiveTexture";
+
+ public static final String EMISSIV_COLOR_PARAM = "material.emissiveFactor";
+
+ public static final String ALPHA_MODE_PARAM = "material.alphaMode";
+
+ public static final String ALPHA_CUTOFF_PARAM = "material.alphaCutoff";
+
+ public static final String DOUBLE_SIDED_PARAM = "material.doubleSided";
+
+ public static final String MATERIAL_EXTENSION_PARAM_PREFIX = "material.extension.";
+
+ public static final String MATERIAL_EXTRA_PARAM_PREFIX = "material.extra.";
+
+
+ private Map gltfParamMap = new HashMap<>();
+
+ private Set gltfExtensions = new HashSet<>();
+
+
+ /**
+ * Checks if the material provides the given GLTF extension.
+ *
+ * @param gltfExtension The GLTF extension name.
+ * @return true if the material provides the given GLTF extension, otherwise false.
+ */
+ public boolean hasGltfExtension(String gltfExtension) {
+ return gltfExtensions.contains(gltfExtension);
+ }
+
+ /**
+ * Adds the given GLTF extension name.
+ *
+ * @param gltfExtension The GLTF extension name.
+ */
+ public void addGltfExtension(String gltfExtension) {
+ gltfExtensions.add(gltfExtension);
+ }
+
+ /**
+ * Removes the given GLTF extension name.
+ *
+ * @param gltfExtension The GLTF extension name.
+ */
+ public void removeGltfExtension(String gltfExtension) {
+ gltfExtensions.remove(gltfExtension);
+ }
+
+
+ /**
+ * Checks if the material provides a material parameter with the given name.
+ *
+ * @param gltfParamName The GLTF parameter name.
+ * @return true if the material provides a material parameter with the given name, otherwise false.
+ */
+ public boolean containsGltfParam(String gltfParamName) {
+ return gltfParamMap.containsKey(gltfParamName);
+ }
+
+ /**
+ * Gets the material parameter with the given name.
+ *
+ * @param gltfParamName The GLTF parameter name.
+ * @return The value of the material parameter with the given name, or null if no such parameter exists.
+ */
+ public Object getGltfParam(String gltfParamName) {
+ return gltfParamMap.get(gltfParamName);
+ }
+
+ /**
+ * Adds a material parameter with the given name and value.
+ *
+ * @param gltfParamName The GLTF parameter name.
+ * @param value The value of the material parameter. Does nothing, if value is null.
+ */
+ public void setGltfParam(String gltfParamName, Object value) {
+ if (value != null) {
+ gltfParamMap.put(gltfParamName, value);
+ }
+ }
+
+ /**
+ * Removes the material parameter with the given name.
+ *
+ * @param gltfParamName The GLTF parameter name.
+ * @return The previous value of the material parameter, or null if there was no such parameter.
+ */
+ public Object removeGltfParam(String gltfParamName) {
+ return gltfParamMap.remove(gltfParamName);
+ }
+
+}
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialFactory.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialFactory.java
new file mode 100644
index 0000000000..7d4eabf5f4
--- /dev/null
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialFactory.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2009-2026 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.gltf;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+
+/**
+ * A material factory creates {@link Material}s based on the data of a single material from a GLTF file.
+ *
+ * All material factories have bo be registered at the {@link GltfLoader} by using one of its
+ * static register methods.
+ *
+ */
+public interface GltfMaterialFactory {
+
+ /**
+ * Checks, if the factory is able to create a new material from the given material data.
+ * If it accepts the material data, the {@link #createMaterial(AssetManager, AssetKey, GltfMaterialData)} method
+ * can be used to create a new material.
+ *
+ * @param assetKey The {@link AssetKey} used for loading the GLTF model.
+ * @param gltfMaterialData The {@link GltfMaterialData} containing all available GLTF material data.
+ * @return true if the factory is able to create a material from the given material data, otherwise false.
+ */
+ boolean accepts(AssetKey> assetKey, GltfMaterialData gltfMaterialData);
+
+ /**
+ * Creates a new material from the given material data.
+ *
+ * @param assetManager The {@link AssetManager} instance.
+ * @param assetKey The {@link AssetKey} used for loading the GLTF model.
+ * @param gltfMaterialData The {@link GltfMaterialData} containing all available GLTF material data.
+ * @return The new created {@link Material}.
+ */
+ Material createMaterial(AssetManager assetManager, AssetKey> assetKey, GltfMaterialData gltfMaterialData);
+
+}
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java
index de66ffbc0a..92b6dbe463 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java
@@ -56,8 +56,7 @@ public class GltfModelKey extends ModelKey {
* Enables or disables the legacy material adapter system.
* This should only be used in older projects for backward compatibility.
*/
- // TODO Set the default to false, after the new material creation process has been implemented
- private boolean materialAdaptersEnabled = true;
+ private boolean materialAdaptersEnabled = false;
private Map materialAdapters = new HashMap<>();
private static Map extensionLoaders = new HashMap<>();
From fef2d6ef55244307efd8c575cce32a12b10c5c8e Mon Sep 17 00:00:00 2001
From: theMinka
Date: Sun, 19 Apr 2026 20:56:16 +0200
Subject: [PATCH 04/14] Added handling of GltfMaterialData objects for all
material-based ExtensionLoaders
* Retains the old MaterialAdapter handling for backward compatibility
---
.../PBREmissiveStrengthExtensionLoader.java | 34 ++++++++++++++-
.../gltf/PBRSpecGlossExtensionLoader.java | 41 +++++++++++++++++++
.../plugins/gltf/UnlitExtensionLoader.java | 21 ++++++++++
3 files changed, 94 insertions(+), 2 deletions(-)
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java
index 71ab1b84a4..3ad3c59d88 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java
@@ -33,7 +33,13 @@
import com.jme3.asset.AssetKey;
import com.jme3.plugins.json.JsonElement;
+import com.jme3.plugins.json.JsonObject;
+
import java.io.IOException;
+import java.util.logging.Logger;
+
+import static com.jme3.scene.plugins.gltf.GltfMaterialData.MATERIAL_EXTENSION_PARAM_PREFIX;
+import static com.jme3.scene.plugins.gltf.GltfUtils.getAsFloat;
/**
* Extension loader for "KHR_materials_emissive_strength".
@@ -41,11 +47,35 @@
* @author codex
*/
public class PBREmissiveStrengthExtensionLoader implements ExtensionLoader {
-
+
+ public static final String EXTENSION_NAME = "KHR_materials_emissive_strength";
+
+ public static final String EMISSIVE_STRENGTH_PARAM = MATERIAL_EXTENSION_PARAM_PREFIX + EXTENSION_NAME + ".emissiveStrength";
+
+ private static final Logger logger = Logger.getLogger(PBREmissiveStrengthExtensionLoader.class.getName());
+
private PBREmissiveStrengthMaterialAdapter materialAdapter = new PBREmissiveStrengthMaterialAdapter();
-
+
@Override
public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException {
+ if (input instanceof GltfMaterialData) {
+ GltfMaterialData gltfMaterialData = (GltfMaterialData) input;
+ gltfMaterialData.addGltfExtension(EXTENSION_NAME);
+
+ JsonObject extensionJson = extension.getAsJsonObject();
+ gltfMaterialData.setGltfParam(EMISSIVE_STRENGTH_PARAM, getAsFloat(extensionJson, "emissiveStrength"));
+
+ } else if (input instanceof MaterialAdapter) {
+ return handleExtensionForMaterialAdapter(loader, parentName, parent, extension, input);
+
+ } else {
+ logger.warning(EXTENSION_NAME + " extension added on unsupported element");
+ }
+
+ return input;
+ }
+
+ private Object handleExtensionForMaterialAdapter(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException {
MaterialAdapter adapter = materialAdapter;
AssetKey key = loader.getInfo().getKey();
//check for a custom adapter for emissive strength
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java
index abeda10cd7..395982fd8e 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java
@@ -34,7 +34,12 @@
import com.jme3.asset.AssetKey;
import java.io.IOException;
+import java.util.logging.Logger;
+
import com.jme3.plugins.json.JsonElement;
+import com.jme3.plugins.json.JsonObject;
+
+import static com.jme3.scene.plugins.gltf.GltfMaterialData.MATERIAL_EXTENSION_PARAM_PREFIX;
import static com.jme3.scene.plugins.gltf.GltfUtils.getAsColor;
import static com.jme3.scene.plugins.gltf.GltfUtils.getAsFloat;
@@ -44,10 +49,46 @@
*/
public class PBRSpecGlossExtensionLoader implements ExtensionLoader {
+ public static final String EXTENSION_NAME = "KHR_materials_pbrSpecularGlossiness";
+
+ public static final String DIFFUSE_COLOR_PARAM = MATERIAL_EXTENSION_PARAM_PREFIX + EXTENSION_NAME + ".diffuseFactor";
+
+ public static final String SPECULAR_COLOR_PARAM = MATERIAL_EXTENSION_PARAM_PREFIX + EXTENSION_NAME + ".specularFactor";
+
+ public static final String GLOSSINESS_FACTOR_PARAM = MATERIAL_EXTENSION_PARAM_PREFIX + EXTENSION_NAME + ".glossinessFactor";
+
+ public static final String DIFFUSE_TEXTURE_PARAM = MATERIAL_EXTENSION_PARAM_PREFIX + EXTENSION_NAME + ".diffuseTexture";
+
+ public static final String SPECULAR_GLOSSINESS_TEXTURE_PARAM = MATERIAL_EXTENSION_PARAM_PREFIX + EXTENSION_NAME + ".specularGlossinessTexture";
+
+ private static final Logger logger = Logger.getLogger(PBRSpecGlossExtensionLoader.class.getName());
+
private PBRSpecGlossMaterialAdapter materialAdapter = new PBRSpecGlossMaterialAdapter();
@Override
public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException {
+ if (input instanceof GltfMaterialData) {
+ GltfMaterialData gltfMaterialData = (GltfMaterialData) input;
+ gltfMaterialData.addGltfExtension(EXTENSION_NAME);
+
+ JsonObject extensionJson = extension.getAsJsonObject();
+ gltfMaterialData.setGltfParam(DIFFUSE_COLOR_PARAM, getAsColor(extensionJson, "diffuseFactor"));
+ gltfMaterialData.setGltfParam(SPECULAR_COLOR_PARAM, getAsColor(extensionJson, "specularFactor"));
+ gltfMaterialData.setGltfParam(GLOSSINESS_FACTOR_PARAM, getAsFloat(extensionJson, "glossinessFactor"));
+ gltfMaterialData.setGltfParam(DIFFUSE_TEXTURE_PARAM, loader.getAsTexture2D(extensionJson,"diffuseTexture"));
+ gltfMaterialData.setGltfParam(SPECULAR_GLOSSINESS_TEXTURE_PARAM, loader.getAsTexture2D(extensionJson,"specularGlossinessTexture"));
+
+ } else if (input instanceof MaterialAdapter) {
+ return handleExtensionForMaterialAdapter(loader, parentName, parent, extension, input);
+
+ } else {
+ logger.warning(EXTENSION_NAME + " extension added on unsupported element");
+ }
+
+ return input;
+ }
+
+ private Object handleExtensionForMaterialAdapter(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException {
MaterialAdapter adapter = materialAdapter;
AssetKey key = loader.getInfo().getKey();
//check for a custom adapter for spec/gloss pipeline
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java
index 790d70b0cf..0ec82c6aed 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java
@@ -34,16 +34,37 @@
import com.jme3.plugins.json.JsonElement;
import com.jme3.asset.AssetKey;
+import java.util.logging.Logger;
+
/**
* Material adapter for the Unlit pipeline
* @author Markil 3
*/
public class UnlitExtensionLoader implements ExtensionLoader {
+ public static final String EXTENSION_NAME = "KHR_materials_unlit";
+
+ private static final Logger logger = Logger.getLogger(UnlitExtensionLoader.class.getName());
+
private final UnlitMaterialAdapter materialAdapter = new UnlitMaterialAdapter();
@Override
public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) {
+ if (input instanceof GltfMaterialData) {
+ GltfMaterialData gltfMaterialData = (GltfMaterialData) input;
+ gltfMaterialData.addGltfExtension(EXTENSION_NAME);
+
+ } else if (input instanceof MaterialAdapter) {
+ return handleExtensionForMaterialAdapter(loader, parentName, parent, extension, input);
+
+ } else {
+ logger.warning(EXTENSION_NAME + " extension added on unsupported element");
+ }
+
+ return input;
+ }
+
+ private Object handleExtensionForMaterialAdapter(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) {
MaterialAdapter adapter = materialAdapter;
AssetKey key = loader.getInfo().getKey();
//check for a custom adapter for spec/gloss pipeline
From 11eded35be1c20c4ab9feb4dd2daaa6250577c32 Mon Sep 17 00:00:00 2001
From: theMinka
Date: Sun, 19 Apr 2026 21:18:33 +0200
Subject: [PATCH 05/14] Added GltfMaterialFactory implementations for
BPRLighting and Unshaded material
* Added both material factories as the default ones to the GltfLoader
---
.../jme3/scene/plugins/gltf/GltfLoader.java | 5 +
.../gltf/PBRLightingMaterialFactory.java | 149 ++++++++++++++++++
.../plugins/gltf/UnshadedMaterialFactory.java | 95 +++++++++++
3 files changed, 249 insertions(+)
create mode 100644 jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java
create mode 100644 jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
index 77d2d4901a..e105e93052 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
@@ -154,6 +154,11 @@ public class GltfLoader implements AssetLoader {
private static SafeArrayList materialFactoryList = new SafeArrayList<>(GltfMaterialFactory.class);
+ static {
+ materialFactoryList.add(new UnshadedMaterialFactory());
+ materialFactoryList.add(new PBRLightingMaterialFactory());
+ }
+
public GltfLoader() {
defaultMaterialAdapters.put("pbrMetallicRoughness", new PBRMetalRoughMaterialAdapter());
}
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java
new file mode 100644
index 0000000000..1100dbb3e4
--- /dev/null
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2009-2026 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.gltf;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState;
+import com.jme3.texture.Texture;
+
+import static com.jme3.scene.plugins.gltf.GltfMaterialData.*;
+import static com.jme3.scene.plugins.gltf.PBREmissiveStrengthExtensionLoader.EMISSIVE_STRENGTH_PARAM;
+import static com.jme3.scene.plugins.gltf.PBRSpecGlossExtensionLoader.*;
+
+/**
+ * This material factory creates jME3's standard "PBRLighting" materials.
+ */
+public class PBRLightingMaterialFactory implements GltfMaterialFactory {
+
+ @Override
+ public boolean accepts(AssetKey> assetKey, GltfMaterialData gltfMaterialData) {
+ // Since PBRLighting is the default material, it accepts all material data,
+ // making any subsequent material factories effectively unreachable.
+ return true;
+ }
+
+ @Override
+ public Material createMaterial(AssetManager assetManager, AssetKey> assetKey, GltfMaterialData gltfMaterialData) {
+ Material material = new Material(assetManager, getMaterialDefPath());
+ material.setName((String) gltfMaterialData.getGltfParam(MATERIAL_NAME_PARAM));
+
+ setStandardParams(material, gltfMaterialData);
+
+ if (gltfMaterialData.hasGltfExtension(PBRSpecGlossExtensionLoader.EXTENSION_NAME)) {
+ setSpecularGlossinessParams(material, gltfMaterialData);
+
+ } else {
+ setMetallicRoughnessParams(material, gltfMaterialData);
+ }
+
+ return material;
+ }
+
+ protected String getMaterialDefPath() {
+ return "Common/MatDefs/Light/PBRLighting.j3md";
+ }
+
+ protected void setStandardParams(Material material, GltfMaterialData gltfMaterialData) {
+ if (gltfMaterialData.containsGltfParam(NORMAL_TEXTURE_PARAM)) {
+ setParam(material, "NormalMap", gltfMaterialData.getGltfParam(NORMAL_TEXTURE_PARAM));
+ setParam(material, "NormalScale", gltfMaterialData.getGltfParam(NORMAL_SCALE_PARAM));
+ material.setFloat("NormalType", 1f);
+ }
+
+ if (gltfMaterialData.containsGltfParam(OCCLUSION_TEXTURE_PARAM)) {
+ // Gltf only supports AO maps (gray scales and only the r channel must be read)
+ material.setBoolean("LightMapAsAOMap", true);
+ setParam(material, "LightMap", gltfMaterialData.getGltfParam(OCCLUSION_TEXTURE_PARAM));
+ setParam(material, "AoStrength", gltfMaterialData.getGltfParam(OCCLUSION_TEXTURE_STRENGTH_PARAM));
+
+ // Check if the occlusion texture is actually the same instance as the metallic-roughness texture.
+ boolean isAoPackedInMRMap = false;
+ if (gltfMaterialData.containsGltfParam(METALLIC_ROUGHNESS_TEXTURE_PARAM)) {
+ Texture occlusionTexture = (Texture) gltfMaterialData.getGltfParam(OCCLUSION_TEXTURE_PARAM);
+ Texture metallicRoughnessTexture = (Texture) gltfMaterialData.getGltfParam(METALLIC_ROUGHNESS_TEXTURE_PARAM);
+ isAoPackedInMRMap = occlusionTexture == metallicRoughnessTexture;
+ }
+ material.setBoolean("AoPackedInMRMap", isAoPackedInMRMap);
+ }
+
+ setParam(material, "EmissiveMap", gltfMaterialData.getGltfParam(EMISSIV_TEXTURE_PARAM));
+ setParam(material, "Emissive", gltfMaterialData.getGltfParam(EMISSIV_COLOR_PARAM));
+ setParam(material, "EmissiveIntensity", gltfMaterialData.getGltfParam(EMISSIVE_STRENGTH_PARAM));
+
+ if (gltfMaterialData.containsGltfParam(ALPHA_MODE_PARAM)) {
+ String alphaMode = (String) gltfMaterialData.getGltfParam(ALPHA_MODE_PARAM);
+ switch (alphaMode) {
+ case "MASK":
+ // "MASK" -> BlendMode.Off
+ setParam(material, "AlphaDiscardThreshold", gltfMaterialData.getGltfParam(ALPHA_CUTOFF_PARAM));
+ break;
+ case "BLEND":
+ material.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
+ break;
+ }
+ }
+
+ if (gltfMaterialData.containsGltfParam(DOUBLE_SIDED_PARAM)) {
+ boolean doubleSided = (boolean) gltfMaterialData.getGltfParam(DOUBLE_SIDED_PARAM);
+ if (doubleSided) {
+ //Note that this is not completely right as normals on the back side will be in the wrong direction.
+ material.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
+ }
+ }
+ }
+
+ protected void setMetallicRoughnessParams(Material material, GltfMaterialData gltfMaterialData) {
+ setParam(material, "BaseColor", gltfMaterialData.getGltfParam(BASE_COLOR_PARAM));
+ setParam(material, "BaseColorMap", gltfMaterialData.getGltfParam(BASE_COLOR_TEXTURE_PARAM));
+ setParam(material, "Metallic", gltfMaterialData.getGltfParam(METALLIC_FACTOR_PARAM));
+ setParam(material, "Roughness", gltfMaterialData.getGltfParam(ROUGHNESS_FACTOR_PARAM));
+ setParam(material, "MetallicRoughnessMap", gltfMaterialData.getGltfParam(METALLIC_ROUGHNESS_TEXTURE_PARAM));
+ }
+
+ protected void setSpecularGlossinessParams(Material material, GltfMaterialData gltfMaterialData) {
+ material.setBoolean("UseSpecGloss", true);
+ setParam(material, "BaseColor", gltfMaterialData.getGltfParam(DIFFUSE_COLOR_PARAM));
+ setParam(material, "BaseColorMap", gltfMaterialData.getGltfParam(DIFFUSE_TEXTURE_PARAM));
+ setParam(material, "Specular", gltfMaterialData.getGltfParam(SPECULAR_COLOR_PARAM));
+ setParam(material, "Glossiness", gltfMaterialData.getGltfParam(GLOSSINESS_FACTOR_PARAM));
+ setParam(material, "SpecularGlossinessMap", gltfMaterialData.getGltfParam(SPECULAR_GLOSSINESS_TEXTURE_PARAM));
+ }
+
+ protected void setParam(Material material, String paramName, Object value) {
+ if (value != null) {
+ material.setParam(paramName, value);
+ }
+ }
+
+}
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java
new file mode 100644
index 0000000000..ae47824046
--- /dev/null
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2009-2026 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.gltf;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState;
+
+import static com.jme3.scene.plugins.gltf.GltfMaterialData.*;
+
+/**
+ * This material factory creates jME3's standard "Unshaded" materials.
+ */
+public class UnshadedMaterialFactory implements GltfMaterialFactory {
+
+ @Override
+ public boolean accepts(AssetKey> assetKey, GltfMaterialData gltfMaterialData) {
+ return gltfMaterialData.hasGltfExtension(UnlitExtensionLoader.EXTENSION_NAME);
+ }
+
+ @Override
+ public Material createMaterial(AssetManager assetManager, AssetKey> assetKey, GltfMaterialData gltfMaterialData) {
+ Material material = new Material(assetManager,getMaterialDefPath());
+ material.setName((String) gltfMaterialData.getGltfParam(MATERIAL_NAME_PARAM));
+
+ setParam(material, "Color", gltfMaterialData.getGltfParam(BASE_COLOR_PARAM));
+ setParam(material, "ColorMap", gltfMaterialData.getGltfParam(BASE_COLOR_TEXTURE_PARAM));
+ setParam(material, "GlowColor", gltfMaterialData.getGltfParam(EMISSIV_COLOR_PARAM));
+ setParam(material, "GlowMap", gltfMaterialData.getGltfParam(EMISSIV_TEXTURE_PARAM));
+
+ if (gltfMaterialData.containsGltfParam(ALPHA_MODE_PARAM)) {
+ String alphaMode = (String) gltfMaterialData.getGltfParam(ALPHA_MODE_PARAM);
+ switch (alphaMode) {
+ case "MASK":
+ // "MASK" -> BlendMode.Off
+ setParam(material, "AlphaDiscardThreshold", gltfMaterialData.getGltfParam(ALPHA_CUTOFF_PARAM));
+ break;
+ case "BLEND":
+ material.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
+ break;
+ }
+ }
+
+ if (gltfMaterialData.containsGltfParam(DOUBLE_SIDED_PARAM)) {
+ boolean doubleSided = (boolean) gltfMaterialData.getGltfParam(DOUBLE_SIDED_PARAM);
+ if (doubleSided) {
+ //Note that this is not completely right as normals on the back side will be in the wrong direction.
+ material.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
+ }
+ }
+
+ return material;
+ }
+
+ protected String getMaterialDefPath() {
+ return "Common/MatDefs/Misc/Unshaded.j3md";
+ }
+
+ protected void setParam(Material material, String paramName, Object value) {
+ if (value != null) {
+ material.setParam(paramName, value);
+ }
+ }
+
+}
From ae12167eed21c011bad876578c9b115e6a00fb38 Mon Sep 17 00:00:00 2001
From: theMinka
Date: Sun, 19 Apr 2026 22:20:48 +0200
Subject: [PATCH 06/14] Move default values for material parameters to material
factories
Reason: Setting default values directly in GltfMaterialData obscures whether a parameter is actually defined in the glTF material. The presence of material parameters may be relevant for some material factories. Therefore, default values should be set by the material factories themselves.
---
.../com/jme3/scene/plugins/gltf/GltfLoader.java | 8 ++++----
.../plugins/gltf/PBRLightingMaterialFactory.java | 15 ++++++++++-----
.../plugins/gltf/UnshadedMaterialFactory.java | 11 ++++++++---
3 files changed, 22 insertions(+), 12 deletions(-)
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
index e105e93052..bb83f4f369 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
@@ -831,10 +831,10 @@ protected GltfMaterialData readStandardMaterialParameters(JsonObject materialJso
JsonObject pbrMetallicRoughnessJson = materialJson.getAsJsonObject("pbrMetallicRoughness");
if (pbrMetallicRoughnessJson != null) {
- gltfMaterialData.setGltfParam(BASE_COLOR_PARAM, getAsColor(pbrMetallicRoughnessJson, "baseColorFactor", ColorRGBA.White));
+ gltfMaterialData.setGltfParam(BASE_COLOR_PARAM, getAsColor(pbrMetallicRoughnessJson, "baseColorFactor"));
gltfMaterialData.setGltfParam(BASE_COLOR_TEXTURE_PARAM, getAsTexture2D(pbrMetallicRoughnessJson, "baseColorTexture"));
- gltfMaterialData.setGltfParam(METALLIC_FACTOR_PARAM, getAsFloat(pbrMetallicRoughnessJson, "metallicFactor", 1f));
- gltfMaterialData.setGltfParam(ROUGHNESS_FACTOR_PARAM, getAsFloat(pbrMetallicRoughnessJson, "roughnessFactor", 1f));
+ gltfMaterialData.setGltfParam(METALLIC_FACTOR_PARAM, getAsFloat(pbrMetallicRoughnessJson, "metallicFactor"));
+ gltfMaterialData.setGltfParam(ROUGHNESS_FACTOR_PARAM, getAsFloat(pbrMetallicRoughnessJson, "roughnessFactor"));
gltfMaterialData.setGltfParam(METALLIC_ROUGHNESS_TEXTURE_PARAM, getAsTexture2D(pbrMetallicRoughnessJson, "metallicRoughnessTexture"));
}
@@ -852,7 +852,7 @@ protected GltfMaterialData readStandardMaterialParameters(JsonObject materialJso
}
gltfMaterialData.setGltfParam(EMISSIV_TEXTURE_PARAM, getAsTexture2D(materialJson, "emissiveTexture"));
- gltfMaterialData.setGltfParam(EMISSIV_COLOR_PARAM, getAsColor(materialJson, "emissiveFactor", ColorRGBA.Black));
+ gltfMaterialData.setGltfParam(EMISSIV_COLOR_PARAM, getAsColor(materialJson, "emissiveFactor"));
String alphaMode = getAsString(materialJson, "alphaMode");
gltfMaterialData.setGltfParam(ALPHA_MODE_PARAM, alphaMode);
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java
index 1100dbb3e4..07d0f47f7b 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java
@@ -35,6 +35,7 @@
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
+import com.jme3.math.ColorRGBA;
import com.jme3.texture.Texture;
import static com.jme3.scene.plugins.gltf.GltfMaterialData.*;
@@ -98,7 +99,7 @@ protected void setStandardParams(Material material, GltfMaterialData gltfMateria
}
setParam(material, "EmissiveMap", gltfMaterialData.getGltfParam(EMISSIV_TEXTURE_PARAM));
- setParam(material, "Emissive", gltfMaterialData.getGltfParam(EMISSIV_COLOR_PARAM));
+ setParam(material, "Emissive", gltfMaterialData.getGltfParam(EMISSIV_COLOR_PARAM), ColorRGBA.Black);
setParam(material, "EmissiveIntensity", gltfMaterialData.getGltfParam(EMISSIVE_STRENGTH_PARAM));
if (gltfMaterialData.containsGltfParam(ALPHA_MODE_PARAM)) {
@@ -106,7 +107,7 @@ protected void setStandardParams(Material material, GltfMaterialData gltfMateria
switch (alphaMode) {
case "MASK":
// "MASK" -> BlendMode.Off
- setParam(material, "AlphaDiscardThreshold", gltfMaterialData.getGltfParam(ALPHA_CUTOFF_PARAM));
+ setParam(material, "AlphaDiscardThreshold", gltfMaterialData.getGltfParam(ALPHA_CUTOFF_PARAM), 0.5f);
break;
case "BLEND":
material.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
@@ -124,10 +125,10 @@ protected void setStandardParams(Material material, GltfMaterialData gltfMateria
}
protected void setMetallicRoughnessParams(Material material, GltfMaterialData gltfMaterialData) {
- setParam(material, "BaseColor", gltfMaterialData.getGltfParam(BASE_COLOR_PARAM));
+ setParam(material, "BaseColor", gltfMaterialData.getGltfParam(BASE_COLOR_PARAM), ColorRGBA.White);
setParam(material, "BaseColorMap", gltfMaterialData.getGltfParam(BASE_COLOR_TEXTURE_PARAM));
- setParam(material, "Metallic", gltfMaterialData.getGltfParam(METALLIC_FACTOR_PARAM));
- setParam(material, "Roughness", gltfMaterialData.getGltfParam(ROUGHNESS_FACTOR_PARAM));
+ setParam(material, "Metallic", gltfMaterialData.getGltfParam(METALLIC_FACTOR_PARAM), 1f);
+ setParam(material, "Roughness", gltfMaterialData.getGltfParam(ROUGHNESS_FACTOR_PARAM), 1f);
setParam(material, "MetallicRoughnessMap", gltfMaterialData.getGltfParam(METALLIC_ROUGHNESS_TEXTURE_PARAM));
}
@@ -146,4 +147,8 @@ protected void setParam(Material material, String paramName, Object value) {
}
}
+ protected void setParam(Material material, String paramName, Object value, Object defaultValue) {
+ setParam(material, paramName, value != null ? value : defaultValue);
+ }
+
}
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java
index ae47824046..58443a95ec 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java
@@ -35,6 +35,7 @@
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
+import com.jme3.math.ColorRGBA;
import static com.jme3.scene.plugins.gltf.GltfMaterialData.*;
@@ -53,9 +54,9 @@ public Material createMaterial(AssetManager assetManager, AssetKey> assetKey,
Material material = new Material(assetManager,getMaterialDefPath());
material.setName((String) gltfMaterialData.getGltfParam(MATERIAL_NAME_PARAM));
- setParam(material, "Color", gltfMaterialData.getGltfParam(BASE_COLOR_PARAM));
+ setParam(material, "Color", gltfMaterialData.getGltfParam(BASE_COLOR_PARAM), ColorRGBA.White);
setParam(material, "ColorMap", gltfMaterialData.getGltfParam(BASE_COLOR_TEXTURE_PARAM));
- setParam(material, "GlowColor", gltfMaterialData.getGltfParam(EMISSIV_COLOR_PARAM));
+ setParam(material, "GlowColor", gltfMaterialData.getGltfParam(EMISSIV_COLOR_PARAM), ColorRGBA.Black);
setParam(material, "GlowMap", gltfMaterialData.getGltfParam(EMISSIV_TEXTURE_PARAM));
if (gltfMaterialData.containsGltfParam(ALPHA_MODE_PARAM)) {
@@ -63,7 +64,7 @@ public Material createMaterial(AssetManager assetManager, AssetKey> assetKey,
switch (alphaMode) {
case "MASK":
// "MASK" -> BlendMode.Off
- setParam(material, "AlphaDiscardThreshold", gltfMaterialData.getGltfParam(ALPHA_CUTOFF_PARAM));
+ setParam(material, "AlphaDiscardThreshold", gltfMaterialData.getGltfParam(ALPHA_CUTOFF_PARAM), 0.5f);
break;
case "BLEND":
material.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
@@ -92,4 +93,8 @@ protected void setParam(Material material, String paramName, Object value) {
}
}
+ protected void setParam(Material material, String paramName, Object value, Object defaultValue) {
+ setParam(material, paramName, value != null ? value : defaultValue);
+ }
+
}
From 53b75ca00e3945b2e50c781bdb3786212474afc2 Mon Sep 17 00:00:00 2001
From: theMinka
Date: Sun, 19 Apr 2026 22:28:11 +0200
Subject: [PATCH 07/14] Deprecating most parts of the old MaterialAdapter
system
---
.../gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java | 3 ++-
.../java/com/jme3/scene/plugins/gltf/GltfModelKey.java | 7 +++++++
.../gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java | 1 +
.../java/com/jme3/scene/plugins/gltf/MaterialAdapter.java | 4 ++++
.../plugins/gltf/PBREmissiveStrengthExtensionLoader.java | 2 ++
.../plugins/gltf/PBREmissiveStrengthMaterialAdapter.java | 4 ++++
.../com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java | 4 ++++
.../scene/plugins/gltf/PBRMetalRoughMaterialAdapter.java | 4 ++++
.../scene/plugins/gltf/PBRSpecGlossExtensionLoader.java | 2 ++
.../scene/plugins/gltf/PBRSpecGlossMaterialAdapter.java | 4 ++++
.../com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java | 2 ++
.../com/jme3/scene/plugins/gltf/UnlitMaterialAdapter.java | 4 ++++
12 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
index bb83f4f369..69596e7f6a 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
@@ -145,7 +145,7 @@ public class GltfLoader implements AssetLoader {
private final Vector3fArrayPopulator vector3fArrayPopulator = new Vector3fArrayPopulator();
private final QuaternionArrayPopulator quaternionArrayPopulator = new QuaternionArrayPopulator();
private final Matrix4fArrayPopulator matrix4fArrayPopulator = new Matrix4fArrayPopulator();
- private final Map defaultMaterialAdapters = new HashMap<>();
+ @Deprecated private final Map defaultMaterialAdapters = new HashMap<>();
private final CustomContentManager customContentManager = new CustomContentManager();
private boolean useNormalsFlag = false;
@@ -877,6 +877,7 @@ protected Material createMaterial(GltfMaterialData gltfMaterialData, int materia
return defaultMat;
}
+ @Deprecated
protected Material readMaterialUsingMaterialAdapters(int materialIndex) throws IOException {
assertNotNull(materials, "There is no material defined yet a mesh references one");
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java
index 92b6dbe463..cea372e7df 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java
@@ -58,7 +58,9 @@ public class GltfModelKey extends ModelKey {
*/
private boolean materialAdaptersEnabled = false;
+ @Deprecated
private Map materialAdapters = new HashMap<>();
+
private static Map extensionLoaders = new HashMap<>();
private boolean keepSkeletonPose = false;
private ExtrasLoader extrasLoader;
@@ -127,7 +129,11 @@ public void setMaterialAdaptersEnabled(boolean materialAdaptersEnabled) {
*
* @param gltfMaterialName the name of the gltf material
* @param adapter the material adapter
+ *
+ * @deprecated This will be removed in a future version of the engine. To migrate,
+ * create a custom {@link GltfMaterialFactory} and register it with the {@link GltfLoader}.
*/
+ @Deprecated
public void registerMaterialAdapter(String gltfMaterialName, MaterialAdapter adapter) {
materialAdapters.put(gltfMaterialName, adapter);
}
@@ -144,6 +150,7 @@ public void registerExtensionLoader(String extensionName, ExtensionLoader loader
extensionLoaders.put(extensionName, loader);
}
+ @Deprecated
public MaterialAdapter getAdapterForMaterial(String gltfMaterialName) {
return materialAdapters.get(gltfMaterialName);
}
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java
index 6aff505079..e8f0735f67 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java
@@ -721,6 +721,7 @@ public static boolean isMaterialAdaptersEnabled(AssetInfo info) {
return key != null && key.isMaterialAdaptersEnabled();
}
+ @Deprecated
public static MaterialAdapter getAdapterForMaterial(AssetInfo info, String defName) {
GltfModelKey key = getKey(info);
if (key == null) {
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/MaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/MaterialAdapter.java
index b6b10f9c83..d94d1662fa 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/MaterialAdapter.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/MaterialAdapter.java
@@ -47,7 +47,11 @@
* It maps each gltf parameter to its 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.
+ *
+ * @deprecated This will be removed in a future version of the engine. To migrate,
+ * create a custom {@link GltfMaterialFactory} and register it with the {@link GltfLoader}.
*/
+@Deprecated
public abstract class MaterialAdapter {
private final Map paramsMapping = new HashMap<>();
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java
index 3ad3c59d88..a29691a669 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java
@@ -54,6 +54,7 @@ public class PBREmissiveStrengthExtensionLoader implements ExtensionLoader {
private static final Logger logger = Logger.getLogger(PBREmissiveStrengthExtensionLoader.class.getName());
+ @Deprecated
private PBREmissiveStrengthMaterialAdapter materialAdapter = new PBREmissiveStrengthMaterialAdapter();
@Override
@@ -75,6 +76,7 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement
return input;
}
+ @Deprecated
private Object handleExtensionForMaterialAdapter(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException {
MaterialAdapter adapter = materialAdapter;
AssetKey key = loader.getInfo().getKey();
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthMaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthMaterialAdapter.java
index 5d078c97f7..59386c0a8b 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthMaterialAdapter.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthMaterialAdapter.java
@@ -35,7 +35,11 @@
* Adapter for converting GLTF emissive strength to JME emissive intensity.
*
* @author codex
+ *
+ * @deprecated This will be removed in a future version of the engine. To migrate,
+ * create a custom {@link GltfMaterialFactory} and register it with the {@link GltfLoader}.
*/
+@Deprecated
public class PBREmissiveStrengthMaterialAdapter extends PBRMaterialAdapter {
public PBREmissiveStrengthMaterialAdapter() {
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java
index 340cdb513b..6e73560548 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java
@@ -37,7 +37,11 @@
* Adapts GLTF PBR materials to JME PBR materials.
*
* @author Nehon
+ *
+ * @deprecated This will be removed in a future version of the engine. To migrate,
+ * create a custom {@link GltfMaterialFactory} and register it with the {@link GltfLoader}.
*/
+@Deprecated
public abstract class PBRMaterialAdapter extends MaterialAdapter {
/**
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMetalRoughMaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMetalRoughMaterialAdapter.java
index fd744cffdd..c9c99c595f 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMetalRoughMaterialAdapter.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMetalRoughMaterialAdapter.java
@@ -33,7 +33,11 @@
/**
* Created by Nehon on 20/08/2017.
+ *
+ * @deprecated This will be removed in a future version of the engine. To migrate,
+ * create a custom {@link GltfMaterialFactory} and register it with the {@link GltfLoader}.
*/
+@Deprecated
public class PBRMetalRoughMaterialAdapter extends PBRMaterialAdapter {
public PBRMetalRoughMaterialAdapter() {
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java
index 395982fd8e..984e649fb4 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java
@@ -63,6 +63,7 @@ public class PBRSpecGlossExtensionLoader implements ExtensionLoader {
private static final Logger logger = Logger.getLogger(PBRSpecGlossExtensionLoader.class.getName());
+ @Deprecated
private PBRSpecGlossMaterialAdapter materialAdapter = new PBRSpecGlossMaterialAdapter();
@Override
@@ -88,6 +89,7 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement
return input;
}
+ @Deprecated
private Object handleExtensionForMaterialAdapter(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException {
MaterialAdapter adapter = materialAdapter;
AssetKey key = loader.getInfo().getKey();
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossMaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossMaterialAdapter.java
index bd4c25c8af..df74552ea6 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossMaterialAdapter.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossMaterialAdapter.java
@@ -35,7 +35,11 @@
/**
* Created by Nehon on 20/08/2017.
+ *
+ * @deprecated This will be removed in a future version of the engine. To migrate,
+ * create a custom {@link GltfMaterialFactory} and register it with the {@link GltfLoader}.
*/
+@Deprecated
public class PBRSpecGlossMaterialAdapter extends PBRMaterialAdapter {
public PBRSpecGlossMaterialAdapter() {
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java
index 0ec82c6aed..a95eda8960 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java
@@ -46,6 +46,7 @@ public class UnlitExtensionLoader implements ExtensionLoader {
private static final Logger logger = Logger.getLogger(UnlitExtensionLoader.class.getName());
+ @Deprecated
private final UnlitMaterialAdapter materialAdapter = new UnlitMaterialAdapter();
@Override
@@ -64,6 +65,7 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement
return input;
}
+ @Deprecated
private Object handleExtensionForMaterialAdapter(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) {
MaterialAdapter adapter = materialAdapter;
AssetKey key = loader.getInfo().getKey();
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitMaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitMaterialAdapter.java
index 65acdf7f2b..9b32dc8fce 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitMaterialAdapter.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitMaterialAdapter.java
@@ -37,7 +37,11 @@
/**
* @author Markil 3
+ *
+ * @deprecated This will be removed in a future version of the engine. To migrate,
+ * create a custom {@link GltfMaterialFactory} and register it with the {@link GltfLoader}.
*/
+@Deprecated
public class UnlitMaterialAdapter extends MaterialAdapter {
public UnlitMaterialAdapter() {
From 13ad8ba5df51120280f48165f3dd227add898cae Mon Sep 17 00:00:00 2001
From: theMinka
Date: Sat, 25 Apr 2026 18:16:20 +0200
Subject: [PATCH 08/14] Fixed vertex color flag in GltfLoader
---
.../jme3/scene/plugins/gltf/GltfLoader.java | 9 +++-----
.../scene/plugins/gltf/GltfMaterialData.java | 22 +++++++++++++++++++
.../gltf/PBRLightingMaterialFactory.java | 2 ++
.../plugins/gltf/UnshadedMaterialFactory.java | 2 ++
4 files changed, 29 insertions(+), 6 deletions(-)
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
index 69596e7f6a..ed8dca5d32 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
@@ -535,7 +535,7 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException {
geom.setMaterial(defaultMat);
} else {
useNormalsFlag = false;
- geom.setMaterial(readMaterial(materialIndex));
+ geom.setMaterial(readMaterial(materialIndex, useVertexColors));
if (geom.getMaterial().getAdditionalRenderState()
.getBlendMode() == RenderState.BlendMode.Alpha) {
// Alpha blending is enabled for this material. Let's place the geom in the
@@ -549,10 +549,6 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException {
}
}
- if (useVertexColors) {
- geom.getMaterial().setBoolean("UseVertexColor", useVertexColors);
- }
-
geom.setName(name + "_" + index);
geom.updateModelBound();
@@ -811,7 +807,7 @@ protected ByteBuffer getBytes(int bufferIndex, String uri, Integer bufferLength)
return data;
}
- public Material readMaterial(int materialIndex) throws IOException {
+ public Material readMaterial(int materialIndex, boolean usesVertexColors) throws IOException {
// Fallback to the old material adapter system, if the legacy flag is set.
if (GltfUtils.isMaterialAdaptersEnabled(info)) {
return readMaterialUsingMaterialAdapters(materialIndex);
@@ -821,6 +817,7 @@ public Material readMaterial(int materialIndex) throws IOException {
JsonObject materialJson = materials.get(materialIndex).getAsJsonObject();
GltfMaterialData gltfMaterialData = readStandardMaterialParameters(materialJson);
+ gltfMaterialData.setHasVertexColors(usesVertexColors);
gltfMaterialData = customContentManager.readExtensionAndExtras("material", materialJson, gltfMaterialData);
return createMaterial(gltfMaterialData, materialIndex);
}
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialData.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialData.java
index cac559e900..989a612318 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialData.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialData.java
@@ -101,6 +101,11 @@ public class GltfMaterialData {
private Set gltfExtensions = new HashSet<>();
+ /**
+ * Indicates the existence of a vertex color buffer.
+ */
+ private boolean hasVertexColors;
+
/**
* Checks if the material provides the given GLTF extension.
@@ -173,4 +178,21 @@ public Object removeGltfParam(String gltfParamName) {
return gltfParamMap.remove(gltfParamName);
}
+
+ /**
+ * @return Indicates the existence of a vertex color buffer.
+ */
+ public boolean hasVertexColors() {
+ return hasVertexColors;
+ }
+
+ /**
+ * Sets the vertex color flag.
+ *
+ * @param hasVertexColors The value to set.
+ */
+ public void setHasVertexColors(boolean hasVertexColors) {
+ this.hasVertexColors = hasVertexColors;
+ }
+
}
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java
index 07d0f47f7b..fbde3c53e8 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java
@@ -122,6 +122,8 @@ protected void setStandardParams(Material material, GltfMaterialData gltfMateria
material.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
}
}
+
+ setParam(material, "UseVertexColor", gltfMaterialData.hasVertexColors());
}
protected void setMetallicRoughnessParams(Material material, GltfMaterialData gltfMaterialData) {
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java
index 58443a95ec..7dd384a9f2 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java
@@ -80,6 +80,8 @@ public Material createMaterial(AssetManager assetManager, AssetKey> assetKey,
}
}
+ setParam(material, "VertexColor", gltfMaterialData.hasVertexColors());
+
return material;
}
From 58410b626d2fa290a32ac981c649e83a328250b0 Mon Sep 17 00:00:00 2001
From: theMinka
Date: Sat, 25 Apr 2026 19:03:21 +0200
Subject: [PATCH 09/14] Fixed QueueBucket for BlendMode.AlphaAdditive in
GltfLoader
---
.../gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
index ed8dca5d32..d011925e75 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
@@ -84,6 +84,7 @@
import com.jme3.asset.TextureKey;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
+import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
@@ -535,9 +536,10 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException {
geom.setMaterial(defaultMat);
} else {
useNormalsFlag = false;
- geom.setMaterial(readMaterial(materialIndex, useVertexColors));
- if (geom.getMaterial().getAdditionalRenderState()
- .getBlendMode() == RenderState.BlendMode.Alpha) {
+ Material material = readMaterial(materialIndex, useVertexColors);
+ geom.setMaterial(material);
+ BlendMode blendMode = material.getAdditionalRenderState().getBlendMode();
+ if (blendMode == BlendMode.Alpha || blendMode == BlendMode.AlphaAdditive) {
// Alpha blending is enabled for this material. Let's place the geom in the
// transparent bucket.
geom.setQueueBucket(RenderQueue.Bucket.Transparent);
From 2921b830b686247f3a43fbac7c120d510218bf43 Mon Sep 17 00:00:00 2001
From: theMinka
Date: Tue, 28 Apr 2026 11:20:25 +0200
Subject: [PATCH 10/14] Fixed typo in emissive constants in GltfMaterialData
---
.../gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java | 5 ++---
.../java/com/jme3/scene/plugins/gltf/GltfMaterialData.java | 4 ++--
.../jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java | 4 ++--
.../com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java | 4 ++--
4 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
index d011925e75..211ad735dd 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
@@ -83,7 +83,6 @@
import com.jme3.asset.AssetLoader;
import com.jme3.asset.TextureKey;
import com.jme3.material.Material;
-import com.jme3.material.RenderState;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
@@ -850,8 +849,8 @@ protected GltfMaterialData readStandardMaterialParameters(JsonObject materialJso
gltfMaterialData.setGltfParam(OCCLUSION_TEXTURE_STRENGTH_PARAM, getAsFloat(occlusionTextureJson, "strength"));
}
- gltfMaterialData.setGltfParam(EMISSIV_TEXTURE_PARAM, getAsTexture2D(materialJson, "emissiveTexture"));
- gltfMaterialData.setGltfParam(EMISSIV_COLOR_PARAM, getAsColor(materialJson, "emissiveFactor"));
+ gltfMaterialData.setGltfParam(EMISSIVE_TEXTURE_PARAM, getAsTexture2D(materialJson, "emissiveTexture"));
+ gltfMaterialData.setGltfParam(EMISSIVE_COLOR_PARAM, getAsColor(materialJson, "emissiveFactor"));
String alphaMode = getAsString(materialJson, "alphaMode");
gltfMaterialData.setGltfParam(ALPHA_MODE_PARAM, alphaMode);
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialData.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialData.java
index 989a612318..490bc2973f 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialData.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialData.java
@@ -82,9 +82,9 @@ public class GltfMaterialData {
public static final String OCCLUSION_TEXTURE_STRENGTH_PARAM = "material.occlusionTextureInfo.strength";
- public static final String EMISSIV_TEXTURE_PARAM = "material.emissiveTexture";
+ public static final String EMISSIVE_TEXTURE_PARAM = "material.emissiveTexture";
- public static final String EMISSIV_COLOR_PARAM = "material.emissiveFactor";
+ public static final String EMISSIVE_COLOR_PARAM = "material.emissiveFactor";
public static final String ALPHA_MODE_PARAM = "material.alphaMode";
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java
index fbde3c53e8..5dcf6761c2 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java
@@ -98,8 +98,8 @@ protected void setStandardParams(Material material, GltfMaterialData gltfMateria
material.setBoolean("AoPackedInMRMap", isAoPackedInMRMap);
}
- setParam(material, "EmissiveMap", gltfMaterialData.getGltfParam(EMISSIV_TEXTURE_PARAM));
- setParam(material, "Emissive", gltfMaterialData.getGltfParam(EMISSIV_COLOR_PARAM), ColorRGBA.Black);
+ setParam(material, "EmissiveMap", gltfMaterialData.getGltfParam(EMISSIVE_TEXTURE_PARAM));
+ setParam(material, "Emissive", gltfMaterialData.getGltfParam(EMISSIVE_COLOR_PARAM), ColorRGBA.Black);
setParam(material, "EmissiveIntensity", gltfMaterialData.getGltfParam(EMISSIVE_STRENGTH_PARAM));
if (gltfMaterialData.containsGltfParam(ALPHA_MODE_PARAM)) {
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java
index 7dd384a9f2..0d89573392 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnshadedMaterialFactory.java
@@ -56,8 +56,8 @@ public Material createMaterial(AssetManager assetManager, AssetKey> assetKey,
setParam(material, "Color", gltfMaterialData.getGltfParam(BASE_COLOR_PARAM), ColorRGBA.White);
setParam(material, "ColorMap", gltfMaterialData.getGltfParam(BASE_COLOR_TEXTURE_PARAM));
- setParam(material, "GlowColor", gltfMaterialData.getGltfParam(EMISSIV_COLOR_PARAM), ColorRGBA.Black);
- setParam(material, "GlowMap", gltfMaterialData.getGltfParam(EMISSIV_TEXTURE_PARAM));
+ setParam(material, "GlowColor", gltfMaterialData.getGltfParam(EMISSIVE_COLOR_PARAM), ColorRGBA.Black);
+ setParam(material, "GlowMap", gltfMaterialData.getGltfParam(EMISSIVE_TEXTURE_PARAM));
if (gltfMaterialData.containsGltfParam(ALPHA_MODE_PARAM)) {
String alphaMode = (String) gltfMaterialData.getGltfParam(ALPHA_MODE_PARAM);
From d005d0b1e74c8b6db74f85b7f396bfcc45e1eee0 Mon Sep 17 00:00:00 2001
From: theMinka
Date: Wed, 13 May 2026 02:33:49 +0200
Subject: [PATCH 11/14] Fixed missing vertex coloring flag in GltfLoader
---
.../java/com/jme3/scene/plugins/gltf/GltfLoader.java | 12 +++++++++---
.../jme3/scene/plugins/gltf/PBRMaterialAdapter.java | 1 +
.../scene/plugins/gltf/UnlitMaterialAdapter.java | 1 +
3 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
index 211ad735dd..98942d9417 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
@@ -532,7 +532,11 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException {
Integer materialIndex = getAsInteger(meshObject, "material");
if (materialIndex == null) {
- geom.setMaterial(defaultMat);
+ // Create a new default material
+ Material material = defaultMat.clone();
+ material.setBoolean("UseVertexColor", useVertexColors);
+ geom.setMaterial(material);
+
} else {
useNormalsFlag = false;
Material material = readMaterial(materialIndex, useVertexColors);
@@ -811,7 +815,7 @@ protected ByteBuffer getBytes(int bufferIndex, String uri, Integer bufferLength)
public Material readMaterial(int materialIndex, boolean usesVertexColors) throws IOException {
// Fallback to the old material adapter system, if the legacy flag is set.
if (GltfUtils.isMaterialAdaptersEnabled(info)) {
- return readMaterialUsingMaterialAdapters(materialIndex);
+ return readMaterialUsingMaterialAdapters(materialIndex, usesVertexColors);
}
assertNotNull(materials, "There is no material defined yet a mesh references one");
@@ -876,7 +880,7 @@ protected Material createMaterial(GltfMaterialData gltfMaterialData, int materia
}
@Deprecated
- protected Material readMaterialUsingMaterialAdapters(int materialIndex) throws IOException {
+ protected Material readMaterialUsingMaterialAdapters(int materialIndex, boolean usesVertexColors) throws IOException {
assertNotNull(materials, "There is no material defined yet a mesh references one");
JsonObject matData = materials.get(materialIndex).getAsJsonObject();
@@ -949,6 +953,8 @@ protected Material readMaterialUsingMaterialAdapters(int materialIndex) throws I
adapter.setParam("emissiveTexture", readTexture(matData.getAsJsonObject("emissiveTexture")));
+ adapter.setParam("usesVertexColors", usesVertexColors);
+
return adapter.getMaterial();
}
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java
index 6e73560548..185b961b0f 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java
@@ -59,6 +59,7 @@ public PBRMaterialAdapter() {
addParamMapping("alphaMode", "alpha");
addParamMapping("alphaCutoff", "AlphaDiscardThreshold");
addParamMapping("doubleSided", "doubleSided");
+ addParamMapping("usesVertexColors", "UseVertexColor");
}
@Override
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitMaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitMaterialAdapter.java
index 9b32dc8fce..e921025dd5 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitMaterialAdapter.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitMaterialAdapter.java
@@ -52,6 +52,7 @@ public UnlitMaterialAdapter() {
addParamMapping("alphaMode", "alpha");
addParamMapping("alphaCutoff", "AlphaDiscardThreshold");
addParamMapping("doubleSided", "doubleSided");
+ addParamMapping("usesVertexColors", "VertexColor");
}
@Override
From 7909f1bef0b5613ee548bc416245dc32f48248b5 Mon Sep 17 00:00:00 2001
From: theMinka
Date: Wed, 13 May 2026 02:36:33 +0200
Subject: [PATCH 12/14] Smaller fixes in some components for loading GLTF files
---
.../gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java | 4 ++--
.../com/jme3/scene/plugins/gltf/GltfMaterialFactory.java | 2 +-
.../java/com/jme3/scene/plugins/gltf/GltfModelKey.java | 8 +++++---
.../scene/plugins/gltf/PBRLightingMaterialFactory.java | 2 +-
4 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
index 98942d9417..bbca2de105 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
@@ -1808,8 +1808,8 @@ public static void unregisterDefaultExtrasLoader() {
* @param materialFactory The {@link GltfMaterialFactory} to register.
*/
public static void registerMaterialFactoryFirst(GltfMaterialFactory materialFactory) {
- unregisterMaterialFactory(materialFactory.getClass());
- materialFactoryList.add(0, materialFactory);
+ unregisterMaterialFactory(materialFactory.getClass());
+ materialFactoryList.add(0, materialFactory);
}
/**
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialFactory.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialFactory.java
index 7d4eabf5f4..32554b9919 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialFactory.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfMaterialFactory.java
@@ -38,7 +38,7 @@
/**
* A material factory creates {@link Material}s based on the data of a single material from a GLTF file.
*
- * All material factories have bo be registered at the {@link GltfLoader} by using one of its
+ * All material factories have bo be registered with the {@link GltfLoader} by using one of its
* static register methods.
*
*/
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java
index cea372e7df..f045a5a7b3 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java
@@ -196,15 +196,17 @@ public boolean equals(Object object) {
|| !Objects.equals(extrasLoader, other.extrasLoader)) {
return false;
}
- return keepSkeletonPose == other.keepSkeletonPose;
+ return keepSkeletonPose == other.keepSkeletonPose
+ && materialAdaptersEnabled == other.materialAdaptersEnabled;
}
@Override
public int hashCode() {
int hash = 5;
hash = 37 * hash + materialAdapters.hashCode();
- hash = 37 * hash + Objects.hashCode(this.extrasLoader);
- hash = 37 * hash + (this.keepSkeletonPose ? 1 : 0);
+ hash = 37 * hash + Objects.hashCode(extrasLoader);
+ hash = 37 * hash + (keepSkeletonPose ? 1 : 0);
+ hash = 37 * hash + (materialAdaptersEnabled ? 1 : 0);
return hash;
}
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java
index 5dcf6761c2..334d21f1cb 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRLightingMaterialFactory.java
@@ -100,7 +100,7 @@ protected void setStandardParams(Material material, GltfMaterialData gltfMateria
setParam(material, "EmissiveMap", gltfMaterialData.getGltfParam(EMISSIVE_TEXTURE_PARAM));
setParam(material, "Emissive", gltfMaterialData.getGltfParam(EMISSIVE_COLOR_PARAM), ColorRGBA.Black);
- setParam(material, "EmissiveIntensity", gltfMaterialData.getGltfParam(EMISSIVE_STRENGTH_PARAM));
+ setParam(material, "EmissiveIntensity", gltfMaterialData.getGltfParam(EMISSIVE_STRENGTH_PARAM), 1f);
if (gltfMaterialData.containsGltfParam(ALPHA_MODE_PARAM)) {
String alphaMode = (String) gltfMaterialData.getGltfParam(ALPHA_MODE_PARAM);
From b245ada292192ec26957eccb52cf61482f87d0b2 Mon Sep 17 00:00:00 2001
From: theMinka
Date: Fri, 15 May 2026 17:31:08 +0200
Subject: [PATCH 13/14] PBREmissiveStrengthExtensionLoader no longer replaces
other MaterialAdapters
---
.../PBREmissiveStrengthExtensionLoader.java | 20 +++++---
.../PBREmissiveStrengthMaterialAdapter.java | 50 -------------------
2 files changed, 13 insertions(+), 57 deletions(-)
delete mode 100644 jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthMaterialAdapter.java
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java
index a29691a669..8fdf2934d7 100644
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java
@@ -32,8 +32,10 @@
package com.jme3.scene.plugins.gltf;
import com.jme3.asset.AssetKey;
+import com.jme3.material.MatParam;
import com.jme3.plugins.json.JsonElement;
import com.jme3.plugins.json.JsonObject;
+import com.jme3.shader.VarType;
import java.io.IOException;
import java.util.logging.Logger;
@@ -54,9 +56,6 @@ public class PBREmissiveStrengthExtensionLoader implements ExtensionLoader {
private static final Logger logger = Logger.getLogger(PBREmissiveStrengthExtensionLoader.class.getName());
- @Deprecated
- private PBREmissiveStrengthMaterialAdapter materialAdapter = new PBREmissiveStrengthMaterialAdapter();
-
@Override
public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException {
if (input instanceof GltfMaterialData) {
@@ -78,7 +77,7 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement
@Deprecated
private Object handleExtensionForMaterialAdapter(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException {
- MaterialAdapter adapter = materialAdapter;
+ MaterialAdapter adapter = (MaterialAdapter) input;
AssetKey key = loader.getInfo().getKey();
//check for a custom adapter for emissive strength
if (key instanceof GltfModelKey) {
@@ -86,9 +85,16 @@ private Object handleExtensionForMaterialAdapter(GltfLoader loader, String paren
if (custom != null) {
adapter = custom;
}
- }
- adapter.init(loader.getInfo().getManager());
- adapter.setParam("emissiveStrength", GltfUtils.getAsFloat(extension.getAsJsonObject(), "emissiveStrength"));
+ }
+
+ // Simply add a param mapping for the emissive strength, if the given material supports a
+ // material parameter with the exact name "EmissiveIntensity".
+ MatParam matParam = adapter.getMaterial().getMaterialDef().getMaterialParam("EmissiveIntensity");
+ if (matParam != null && matParam.getVarType() == VarType.Float) {
+ adapter.addParamMapping("emissiveStrength", "EmissiveIntensity");
+ adapter.setParam("emissiveStrength", GltfUtils.getAsFloat(extension.getAsJsonObject(), "emissiveStrength"));
+ }
+
return adapter;
}
diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthMaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthMaterialAdapter.java
deleted file mode 100644
index 59386c0a8b..0000000000
--- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthMaterialAdapter.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (c) 2023 jMonkeyEngine
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.jme3.scene.plugins.gltf;
-
-/**
- * Adapter for converting GLTF emissive strength to JME emissive intensity.
- *
- * @author codex
- *
- * @deprecated This will be removed in a future version of the engine. To migrate,
- * create a custom {@link GltfMaterialFactory} and register it with the {@link GltfLoader}.
- */
-@Deprecated
-public class PBREmissiveStrengthMaterialAdapter extends PBRMaterialAdapter {
-
- public PBREmissiveStrengthMaterialAdapter() {
- super();
- addParamMapping("emissiveStrength", "EmissiveIntensity");
- }
-
-}
From ccec70ac41e60a04cbe4226e130cca542b73fea8 Mon Sep 17 00:00:00 2001
From: theMinka
Date: Fri, 15 May 2026 17:32:17 +0200
Subject: [PATCH 14/14] Added material parameter tests to GltfLoaderTest
---
.../scene/plugins/gltf/GltfLoaderTest.java | 279 +++++++++-
.../src/test/resources/gltf/ColorTexture.png | Bin 0 -> 530 bytes
.../test/resources/gltf/EmissiveTexture.png | Bin 0 -> 530 bytes
.../test/resources/gltf/MaterialTestCubes.bin | Bin 0 -> 3720 bytes
.../resources/gltf/MaterialTestCubes.gltf | 523 ++++++++++++++++++
.../resources/gltf/MaterialTextCubes.blend | Bin 0 -> 943925 bytes
.../MetallicRoughnessOcclusionTexture.png | Bin 0 -> 530 bytes
.../src/test/resources/gltf/NormalTexture.png | Bin 0 -> 530 bytes
8 files changed, 801 insertions(+), 1 deletion(-)
create mode 100644 jme3-plugins/src/test/resources/gltf/ColorTexture.png
create mode 100644 jme3-plugins/src/test/resources/gltf/EmissiveTexture.png
create mode 100644 jme3-plugins/src/test/resources/gltf/MaterialTestCubes.bin
create mode 100644 jme3-plugins/src/test/resources/gltf/MaterialTestCubes.gltf
create mode 100644 jme3-plugins/src/test/resources/gltf/MaterialTextCubes.blend
create mode 100644 jme3-plugins/src/test/resources/gltf/MetallicRoughnessOcclusionTexture.png
create mode 100644 jme3-plugins/src/test/resources/gltf/NormalTexture.png
diff --git a/jme3-plugins/src/test/java/com/jme3/scene/plugins/gltf/GltfLoaderTest.java b/jme3-plugins/src/test/java/com/jme3/scene/plugins/gltf/GltfLoaderTest.java
index d2662b9ec0..d485ec2f31 100644
--- a/jme3-plugins/src/test/java/com/jme3/scene/plugins/gltf/GltfLoaderTest.java
+++ b/jme3-plugins/src/test/java/com/jme3/scene/plugins/gltf/GltfLoaderTest.java
@@ -37,7 +37,12 @@
import com.jme3.light.Light;
import com.jme3.light.PointLight;
import com.jme3.light.SpotLight;
+import com.jme3.material.MatParam;
+import com.jme3.material.MatParamTexture;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState;
import com.jme3.material.plugin.TestMaterialWrite;
+import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
@@ -196,4 +201,276 @@ private void dumpScene(Spatial s, int indent) {
}
}
}
-}
\ No newline at end of file
+
+ @Test
+ public void testPBRMaterialNoTextures() {
+ GltfModelKey gltfModelKey = new GltfModelKey("gltf/MaterialTestCubes.gltf");
+ Node sceneNode = (Node) assetManager.loadModel(gltfModelKey);
+
+ Geometry pbrNoTexturesCube = (Geometry) sceneNode.getChild("pbrNoTexturesCube_0");
+ Material pbrMaterial = pbrNoTexturesCube.getMaterial();
+
+ assertMaterialNameAndDefinition(pbrMaterial, "PBR Lighting", "pbrNoTexturesMaterial");
+
+ assertMaterialParam(pbrMaterial, "BaseColor", new ColorRGBA(0.9f, 0.6f, 0.3f, 0f));
+ assertMaterialParam(pbrMaterial, "BaseColorMap", null);
+ assertMaterialParam(pbrMaterial, "Metallic", 0.4f);
+ assertMaterialParam(pbrMaterial, "Roughness", 0.6f);
+ assertMaterialParam(pbrMaterial, "MetallicRoughnessMap", null);
+
+ assertMaterialParam(pbrMaterial, "NormalMap", null);
+ assertMaterialParam(pbrMaterial, "NormalScale", null);
+ assertMaterialParam(pbrMaterial, "NormalType", -1f);
+
+ assertMaterialParam(pbrMaterial, "LightMapAsAOMap", null);
+ assertMaterialParam(pbrMaterial, "LightMap", null);
+ assertMaterialParam(pbrMaterial, "AoStrength", null);
+ assertMaterialParam(pbrMaterial, "AoPackedInMRMap", null);
+
+ assertMaterialParam(pbrMaterial, "EmissiveMap", null);
+ assertMaterialParam(pbrMaterial, "Emissive", new ColorRGBA(0.2f, 0.6f, 1.f, 1f));
+ assertMaterialParam(pbrMaterial, "EmissiveIntensity", 2.7f);
+
+ assertMaterialParam(pbrMaterial, "AlphaDiscardThreshold", 0.5f);
+ Assertions.assertEquals(RenderState.BlendMode.Off, pbrMaterial.getAdditionalRenderState().getBlendMode());
+ Assertions.assertEquals(RenderState.FaceCullMode.Off, pbrMaterial.getAdditionalRenderState().getFaceCullMode());
+
+ assertMaterialParam(pbrMaterial, "UseVertexColor", true);
+ }
+
+ @Test
+ public void testPBRMaterialWithTextures() {
+ GltfModelKey gltfModelKey = new GltfModelKey("gltf/MaterialTestCubes.gltf");
+ Node sceneNode = (Node) assetManager.loadModel(gltfModelKey);
+
+ Geometry pbrWithTexturesCube = (Geometry) sceneNode.getChild("pbrWithTexturesCube_0");
+ Material pbrMaterial = pbrWithTexturesCube.getMaterial();
+
+ assertMaterialNameAndDefinition(pbrMaterial, "PBR Lighting", "pbrWithTexturesMaterial");
+
+ assertMaterialParam(pbrMaterial, "BaseColor", ColorRGBA.White);
+ assertMaterialParam(pbrMaterial, "BaseColorMap", "gltf/ColorTexture.png");
+ assertMaterialParam(pbrMaterial, "Metallic", 1f);
+ assertMaterialParam(pbrMaterial, "Roughness", 1f);
+ assertMaterialParam(pbrMaterial, "MetallicRoughnessMap", "gltf/MetallicRoughnessOcclusionTexture.png");
+
+ assertMaterialParam(pbrMaterial, "NormalMap", "gltf/NormalTexture.png");
+ assertMaterialParam(pbrMaterial, "NormalScale", 1.8f);
+ assertMaterialParam(pbrMaterial, "NormalType", 1f);
+
+ assertMaterialParam(pbrMaterial, "LightMapAsAOMap", true);
+ assertMaterialParam(pbrMaterial, "LightMap", "gltf/MetallicRoughnessOcclusionTexture.png");
+ assertMaterialParam(pbrMaterial, "AoStrength", null);
+ assertMaterialParam(pbrMaterial, "AoPackedInMRMap", true);
+
+ assertMaterialParam(pbrMaterial, "EmissiveMap", "gltf/EmissiveTexture.png");
+ assertMaterialParam(pbrMaterial, "Emissive", ColorRGBA.White);
+ assertMaterialParam(pbrMaterial, "EmissiveIntensity", 2.7f);
+
+ assertMaterialParam(pbrMaterial, "AlphaDiscardThreshold", null);
+ Assertions.assertEquals(RenderState.BlendMode.Alpha, pbrMaterial.getAdditionalRenderState().getBlendMode());
+ Assertions.assertEquals(RenderState.FaceCullMode.Back, pbrMaterial.getAdditionalRenderState().getFaceCullMode());
+
+ assertMaterialParam(pbrMaterial, "UseVertexColor", false);
+ }
+
+ @Test
+ public void testUnlitMaterialNoTextures() {
+ GltfModelKey gltfModelKey = new GltfModelKey("gltf/MaterialTestCubes.gltf");
+ Node sceneNode = (Node) assetManager.loadModel(gltfModelKey);
+
+ Geometry unlitNoTexturesCube = (Geometry) sceneNode.getChild("unlitNoTexturesCube_0");
+ Material unlitMaterial = unlitNoTexturesCube.getMaterial();
+
+ assertMaterialNameAndDefinition(unlitMaterial, "Unshaded", "unlitNoTexturesMaterial");
+
+ assertMaterialParam(unlitMaterial, "Color", new ColorRGBA(0.2f, 0.4f, 0.6f, 0f));
+ assertMaterialParam(unlitMaterial, "ColorMap", null);
+
+ assertMaterialParam(unlitMaterial, "GlowMap", null);
+ assertMaterialParam(unlitMaterial, "GlowColor", ColorRGBA.Black);
+
+ assertMaterialParam(unlitMaterial, "AlphaDiscardThreshold", 0.5f);
+ Assertions.assertEquals(RenderState.BlendMode.Off, unlitMaterial.getAdditionalRenderState().getBlendMode());
+ Assertions.assertEquals(RenderState.FaceCullMode.Off, unlitMaterial.getAdditionalRenderState().getFaceCullMode());
+
+ assertMaterialParam(unlitMaterial, "VertexColor", true);
+ }
+
+ @Test
+ public void testUnlitMaterialWithTextures() {
+ GltfModelKey gltfModelKey = new GltfModelKey("gltf/MaterialTestCubes.gltf");
+ Node sceneNode = (Node) assetManager.loadModel(gltfModelKey);
+
+ Geometry unlitWithTexturesCube = (Geometry) sceneNode.getChild("unlitWithTexturesCube_0");
+ Material unlitMaterial = unlitWithTexturesCube.getMaterial();
+
+ assertMaterialNameAndDefinition(unlitMaterial, "Unshaded", "unlitWithTexturesMaterial");
+
+ assertMaterialParam(unlitMaterial, "Color", ColorRGBA.White);
+ assertMaterialParam(unlitMaterial, "ColorMap", "gltf/ColorTexture.png");
+
+ assertMaterialParam(unlitMaterial, "GlowMap", null);
+ assertMaterialParam(unlitMaterial, "GlowColor", ColorRGBA.Black);
+
+ assertMaterialParam(unlitMaterial, "AlphaDiscardThreshold", null);
+ Assertions.assertEquals(RenderState.BlendMode.Alpha, unlitMaterial.getAdditionalRenderState().getBlendMode());
+ Assertions.assertEquals(RenderState.FaceCullMode.Back, unlitMaterial.getAdditionalRenderState().getFaceCullMode());
+
+ assertMaterialParam(unlitMaterial, "VertexColor", false);
+ }
+
+ @Test
+ public void testPBRMaterialNoTextures_LegacyMechanism() {
+ GltfModelKey gltfModelKey = new GltfModelKey("gltf/MaterialTestCubes.gltf");
+ gltfModelKey.setMaterialAdaptersEnabled(true);
+ Node sceneNode = (Node) assetManager.loadModel(gltfModelKey);
+
+ Geometry pbrNoTexturesCube = (Geometry) sceneNode.getChild("pbrNoTexturesCube_0");
+ Material pbrMaterial = pbrNoTexturesCube.getMaterial();
+
+ assertMaterialNameAndDefinition(pbrMaterial, "PBR Lighting", "pbrNoTexturesMaterial");
+
+ assertMaterialParam(pbrMaterial, "BaseColor", new ColorRGBA(0.9f, 0.6f, 0.3f, 0f));
+ assertMaterialParam(pbrMaterial, "BaseColorMap", null);
+ assertMaterialParam(pbrMaterial, "Metallic", 0.4f);
+ assertMaterialParam(pbrMaterial, "Roughness", 0.6f);
+ assertMaterialParam(pbrMaterial, "MetallicRoughnessMap", null);
+
+ assertMaterialParam(pbrMaterial, "NormalMap", null);
+ assertMaterialParam(pbrMaterial, "NormalScale", null);
+ assertMaterialParam(pbrMaterial, "NormalType", -1f);
+
+ assertMaterialParam(pbrMaterial, "LightMapAsAOMap", null);
+ assertMaterialParam(pbrMaterial, "LightMap", null);
+ assertMaterialParam(pbrMaterial, "AoStrength", null);
+ assertMaterialParam(pbrMaterial, "AoPackedInMRMap", null);
+
+ assertMaterialParam(pbrMaterial, "EmissiveMap", null);
+ assertMaterialParam(pbrMaterial, "Emissive", new ColorRGBA(0.2f, 0.6f, 1.f, 1f));
+ assertMaterialParam(pbrMaterial, "EmissiveIntensity", 2.7f);
+
+ assertMaterialParam(pbrMaterial, "AlphaDiscardThreshold", 0.5f);
+ Assertions.assertEquals(RenderState.BlendMode.Off, pbrMaterial.getAdditionalRenderState().getBlendMode());
+ Assertions.assertEquals(RenderState.FaceCullMode.Off, pbrMaterial.getAdditionalRenderState().getFaceCullMode());
+
+ assertMaterialParam(pbrMaterial, "UseVertexColor", true);
+ }
+
+ @Test
+ public void testPBRMaterialWithTextures_LegacyMechanism() {
+ GltfModelKey gltfModelKey = new GltfModelKey("gltf/MaterialTestCubes.gltf");
+ gltfModelKey.setMaterialAdaptersEnabled(true);
+ Node sceneNode = (Node) assetManager.loadModel(gltfModelKey);
+
+ Geometry pbrWithTexturesCube = (Geometry) sceneNode.getChild("pbrWithTexturesCube_0");
+ Material pbrMaterial = pbrWithTexturesCube.getMaterial();
+
+ assertMaterialNameAndDefinition(pbrMaterial, "PBR Lighting", "pbrWithTexturesMaterial");
+
+ assertMaterialParam(pbrMaterial, "BaseColor", ColorRGBA.White);
+ assertMaterialParam(pbrMaterial, "BaseColorMap", "gltf/ColorTexture.png");
+ assertMaterialParam(pbrMaterial, "Metallic", 1f);
+ assertMaterialParam(pbrMaterial, "Roughness", 1f);
+ assertMaterialParam(pbrMaterial, "MetallicRoughnessMap", "gltf/MetallicRoughnessOcclusionTexture.png");
+
+ assertMaterialParam(pbrMaterial, "NormalMap", "gltf/NormalTexture.png");
+ assertMaterialParam(pbrMaterial, "NormalScale", 1.8f);
+ assertMaterialParam(pbrMaterial, "NormalType", 1f);
+
+ // Differences to new material system:
+ // - LightMap is not set to occlusion texture
+ // - LightMapAsAOMap is not set
+ assertMaterialParam(pbrMaterial, "LightMapAsAOMap", null);
+ assertMaterialParam(pbrMaterial, "LightMap", null);
+ assertMaterialParam(pbrMaterial, "AoStrength", null);
+ assertMaterialParam(pbrMaterial, "AoPackedInMRMap", true);
+
+ assertMaterialParam(pbrMaterial, "EmissiveMap", "gltf/EmissiveTexture.png");
+ assertMaterialParam(pbrMaterial, "Emissive", ColorRGBA.White);
+ assertMaterialParam(pbrMaterial, "EmissiveIntensity", 2.7f);
+
+ assertMaterialParam(pbrMaterial, "AlphaDiscardThreshold", null);
+ Assertions.assertEquals(RenderState.BlendMode.Alpha, pbrMaterial.getAdditionalRenderState().getBlendMode());
+ Assertions.assertEquals(RenderState.FaceCullMode.Back, pbrMaterial.getAdditionalRenderState().getFaceCullMode());
+
+ assertMaterialParam(pbrMaterial, "UseVertexColor", false);
+ }
+
+ @Test
+ public void testUnlitMaterialNoTextures_LegacyMechanism() {
+ GltfModelKey gltfModelKey = new GltfModelKey("gltf/MaterialTestCubes.gltf");
+ gltfModelKey.setMaterialAdaptersEnabled(true);
+ Node sceneNode = (Node) assetManager.loadModel(gltfModelKey);
+
+ Geometry unlitNoTexturesCube = (Geometry) sceneNode.getChild("unlitNoTexturesCube_0");
+ Material unlitMaterial = unlitNoTexturesCube.getMaterial();
+
+ assertMaterialNameAndDefinition(unlitMaterial, "Unshaded", "unlitNoTexturesMaterial");
+
+ assertMaterialParam(unlitMaterial, "Color", new ColorRGBA(0.2f, 0.4f, 0.6f, 0f));
+ assertMaterialParam(unlitMaterial, "ColorMap", null);
+
+ assertMaterialParam(unlitMaterial, "GlowMap", null);
+ assertMaterialParam(unlitMaterial, "GlowColor", ColorRGBA.Black);
+
+ // Differences to new material system:
+ // - UnlitMaterialAdapter translates alphaMode=MASK to BlendMode.Alpha
+ // - AlphaDiscardThreshold is also not set
+ assertMaterialParam(unlitMaterial, "AlphaDiscardThreshold", null);
+ Assertions.assertEquals(RenderState.BlendMode.Alpha, unlitMaterial.getAdditionalRenderState().getBlendMode());
+ Assertions.assertEquals(RenderState.FaceCullMode.Off, unlitMaterial.getAdditionalRenderState().getFaceCullMode());
+
+ assertMaterialParam(unlitMaterial, "VertexColor", true);
+ }
+
+ @Test
+ public void testUnlitMaterialWithTextures_LegacyMechanism() {
+ GltfModelKey gltfModelKey = new GltfModelKey("gltf/MaterialTestCubes.gltf");
+ gltfModelKey.setMaterialAdaptersEnabled(true);
+ Node sceneNode = (Node) assetManager.loadModel(gltfModelKey);
+
+ Geometry unlitWithTexturesCube = (Geometry) sceneNode.getChild("unlitWithTexturesCube_0");
+ Material unlitMaterial = unlitWithTexturesCube.getMaterial();
+
+ assertMaterialNameAndDefinition(unlitMaterial, "Unshaded", "unlitWithTexturesMaterial");
+
+ assertMaterialParam(unlitMaterial, "Color", ColorRGBA.White);
+ assertMaterialParam(unlitMaterial, "ColorMap", "gltf/ColorTexture.png");
+
+ assertMaterialParam(unlitMaterial, "GlowMap", null);
+ assertMaterialParam(unlitMaterial, "GlowColor", ColorRGBA.Black);
+
+ assertMaterialParam(unlitMaterial, "AlphaDiscardThreshold", null);
+ Assertions.assertEquals(RenderState.BlendMode.Alpha, unlitMaterial.getAdditionalRenderState().getBlendMode());
+ Assertions.assertEquals(RenderState.FaceCullMode.Back, unlitMaterial.getAdditionalRenderState().getFaceCullMode());
+
+ assertMaterialParam(unlitMaterial, "VertexColor", false);
+ }
+
+ private void assertMaterialNameAndDefinition(Material material, String expectedDefinitionName, String expectedMaterialName) {
+ Assertions.assertEquals(expectedMaterialName, material.getName(), "Wrong material name.");
+ Assertions.assertEquals(expectedDefinitionName, material.getMaterialDef().getName(), "Wrong material definition.");
+ }
+
+ private void assertMaterialParam(Material material, String paramName, Object expectedValue) {
+ MatParam matParam = material.getParam(paramName);
+ if (expectedValue == null) {
+ Assertions.assertNull(matParam, () -> "Material parameter '" + paramName + "' should not be set.");
+ return;
+
+ } else {
+ Assertions.assertNotNull(matParam, () -> "Missing material parameter '" + paramName + "'.");
+ }
+
+ if (matParam instanceof MatParamTexture) {
+ String imageName = ((MatParamTexture) matParam).getTextureValue().getName();
+ Assertions.assertEquals(expectedValue, imageName, () -> "Wrong value of texture parameter '" + paramName + "'.");
+
+ } else {
+ Object value = matParam.getValue();
+ Assertions.assertEquals(expectedValue, value, () -> "Wrong value of material parameter '" + paramName + "'.");
+ }
+ }
+
+}
diff --git a/jme3-plugins/src/test/resources/gltf/ColorTexture.png b/jme3-plugins/src/test/resources/gltf/ColorTexture.png
new file mode 100644
index 0000000000000000000000000000000000000000..9a6f42a6b200f2983d8cce87d07383c7f217d9a5
GIT binary patch
literal 530
zcmeAS@N?(olHy`uVBq!ia0vp^DImM)dUR)
b0i6@f`N_YzA}0f&|Vmv|V=MYLYxq5ar=$z%1d%J*o*B
b5C@xV+zvC(Znl5y1&S6=S3j3^P6|R82=n={7y(Ta7
z5+{dlj_Bg}tjRIE2hY`C4A;=f=X7Bo?TcM_y|s7t#_s)jZ{O^PeYIPE-r8sTWUuVT
zpEvg1UfZQz`}5k~+ns&zfp=b4Ui`*Vr!zgiGFyFG&DiQS1Hacx14q655vIBy&4I^W
z9PISV*>g_ru{f(W7FIQ7gf-h_!uy)n&5Y&*Py1`;C=;;Bq{6h1?i^)0b9Q)M5xJ-6
z5y^3nBQEz^_j`rp_Fj{hd5M!pH%D}FeAeWc-Gk@qFNSOA9A!Flc6eS9xnJlJ$#MT8F85LQa)spfUXz!3iIYb+M|5#~*5sJogXiimhHL2L
zbDCdxJzgG;4^Q(P@o;>+{QG;JBgUL~%!$j7C_iG%iN~CHIXlkp`PbqSbxxLrqWnSXs(ajNE9G^8gX7}K^`itQj
kI{BCXf6#Xp@z2%IUx(v7M~pe~m=l*DQGUdj6OTFZ|M0|StN;K2
literal 0
HcmV?d00001
diff --git a/jme3-plugins/src/test/resources/gltf/MaterialTestCubes.gltf b/jme3-plugins/src/test/resources/gltf/MaterialTestCubes.gltf
new file mode 100644
index 0000000000..4000b4dca3
--- /dev/null
+++ b/jme3-plugins/src/test/resources/gltf/MaterialTestCubes.gltf
@@ -0,0 +1,523 @@
+{
+ "asset":{
+ "generator":"Khronos glTF Blender I/O v4.3.47",
+ "version":"2.0"
+ },
+ "extensionsUsed":[
+ "KHR_materials_emissive_strength",
+ "KHR_materials_unlit"
+ ],
+ "scene":0,
+ "scenes":[
+ {
+ "name":"Scene",
+ "nodes":[
+ 0,
+ 1,
+ 2,
+ 3
+ ]
+ }
+ ],
+ "nodes":[
+ {
+ "mesh":0,
+ "name":"pbrWithTexturesCube",
+ "translation":[
+ -2,
+ 0,
+ 0
+ ]
+ },
+ {
+ "mesh":1,
+ "name":"unlitWithTexturesCube",
+ "translation":[
+ 2,
+ 0,
+ 0
+ ]
+ },
+ {
+ "mesh":2,
+ "name":"pbrNoTexturesCube",
+ "translation":[
+ -2,
+ 0,
+ 3
+ ]
+ },
+ {
+ "mesh":3,
+ "name":"unlitNoTexturesCube",
+ "translation":[
+ 2,
+ 0,
+ 3
+ ]
+ }
+ ],
+ "materials":[
+ {
+ "alphaMode":"BLEND",
+ "emissiveFactor":[
+ 1,
+ 1,
+ 1
+ ],
+ "emissiveTexture":{
+ "index":0
+ },
+ "extensions":{
+ "KHR_materials_emissive_strength":{
+ "emissiveStrength":2.700000047683716
+ }
+ },
+ "name":"pbrWithTexturesMaterial",
+ "normalTexture":{
+ "index":1,
+ "scale":1.7999999523162842
+ },
+ "occlusionTexture":{
+ "index":2
+ },
+ "pbrMetallicRoughness":{
+ "baseColorTexture":{
+ "index":3
+ },
+ "metallicRoughnessTexture":{
+ "index":2
+ }
+ }
+ },
+ {
+ "alphaMode":"BLEND",
+ "extensions":{
+ "KHR_materials_unlit":{}
+ },
+ "name":"unlitWithTexturesMaterial",
+ "pbrMetallicRoughness":{
+ "baseColorTexture":{
+ "index":4
+ },
+ "metallicFactor":0,
+ "roughnessFactor":0.9
+ }
+ },
+ {
+ "alphaMode":"MASK",
+ "doubleSided":true,
+ "emissiveFactor":[
+ 0.20000000298023224,
+ 0.6000000238418579,
+ 1
+ ],
+ "extensions":{
+ "KHR_materials_emissive_strength":{
+ "emissiveStrength":2.700000047683716
+ }
+ },
+ "name":"pbrNoTexturesMaterial",
+ "pbrMetallicRoughness":{
+ "baseColorFactor":[
+ 0.8999999761581421,
+ 0.6000000238418579,
+ 0.30000001192092896,
+ 0
+ ],
+ "metallicFactor":0.4000000059604645,
+ "roughnessFactor":0.6000000238418579
+ }
+ },
+ {
+ "alphaMode":"MASK",
+ "doubleSided":true,
+ "extensions":{
+ "KHR_materials_unlit":{}
+ },
+ "name":"unlitNoTexturesMaterial",
+ "pbrMetallicRoughness":{
+ "baseColorFactor":[
+ 0.20000000298023224,
+ 0.4000000059604645,
+ 0.6000000238418579,
+ 0
+ ],
+ "metallicFactor":0,
+ "roughnessFactor":0.9
+ }
+ }
+ ],
+ "meshes":[
+ {
+ "name":"pbrWithTexturesCube",
+ "primitives":[
+ {
+ "attributes":{
+ "POSITION":0,
+ "NORMAL":1,
+ "TEXCOORD_0":2
+ },
+ "indices":3,
+ "material":0
+ }
+ ]
+ },
+ {
+ "name":"unlitWithTexturesCube",
+ "primitives":[
+ {
+ "attributes":{
+ "POSITION":4,
+ "NORMAL":5,
+ "TEXCOORD_0":6
+ },
+ "indices":3,
+ "material":1
+ }
+ ]
+ },
+ {
+ "name":"pbrNoTexturesCube",
+ "primitives":[
+ {
+ "attributes":{
+ "POSITION":7,
+ "NORMAL":8,
+ "TEXCOORD_0":9,
+ "COLOR_0":10,
+ "COLOR_1":11
+ },
+ "indices":3,
+ "material":2
+ }
+ ]
+ },
+ {
+ "name":"unlitNoTexturesCube",
+ "primitives":[
+ {
+ "attributes":{
+ "POSITION":12,
+ "NORMAL":13,
+ "TEXCOORD_0":14,
+ "COLOR_0":15,
+ "COLOR_1":16
+ },
+ "indices":3,
+ "material":3
+ }
+ ]
+ }
+ ],
+ "textures":[
+ {
+ "sampler":0,
+ "source":0
+ },
+ {
+ "sampler":0,
+ "source":1
+ },
+ {
+ "sampler":0,
+ "source":2
+ },
+ {
+ "sampler":0,
+ "source":3
+ },
+ {
+ "sampler":0,
+ "source":3
+ }
+ ],
+ "images":[
+ {
+ "mimeType":"image/png",
+ "name":"EmissiveTexture",
+ "uri":"EmissiveTexture.png"
+ },
+ {
+ "mimeType":"image/png",
+ "name":"NormalTexture",
+ "uri":"NormalTexture.png"
+ },
+ {
+ "mimeType":"image/png",
+ "name":"MetallicRoughnessOcclusionTexture",
+ "uri":"MetallicRoughnessOcclusionTexture.png"
+ },
+ {
+ "mimeType":"image/png",
+ "name":"ColorTexture",
+ "uri":"ColorTexture.png"
+ }
+ ],
+ "accessors":[
+ {
+ "bufferView":0,
+ "componentType":5126,
+ "count":24,
+ "max":[
+ 1,
+ 1,
+ 1
+ ],
+ "min":[
+ -1,
+ -1,
+ -1
+ ],
+ "type":"VEC3"
+ },
+ {
+ "bufferView":1,
+ "componentType":5126,
+ "count":24,
+ "type":"VEC3"
+ },
+ {
+ "bufferView":2,
+ "componentType":5126,
+ "count":24,
+ "type":"VEC2"
+ },
+ {
+ "bufferView":3,
+ "componentType":5123,
+ "count":36,
+ "type":"SCALAR"
+ },
+ {
+ "bufferView":4,
+ "componentType":5126,
+ "count":24,
+ "max":[
+ 1,
+ 1,
+ 1
+ ],
+ "min":[
+ -1,
+ -1,
+ -1
+ ],
+ "type":"VEC3"
+ },
+ {
+ "bufferView":5,
+ "componentType":5126,
+ "count":24,
+ "type":"VEC3"
+ },
+ {
+ "bufferView":6,
+ "componentType":5126,
+ "count":24,
+ "type":"VEC2"
+ },
+ {
+ "bufferView":7,
+ "componentType":5126,
+ "count":24,
+ "max":[
+ 1,
+ 1,
+ 1
+ ],
+ "min":[
+ -1,
+ -1,
+ -1
+ ],
+ "type":"VEC3"
+ },
+ {
+ "bufferView":8,
+ "componentType":5126,
+ "count":24,
+ "type":"VEC3"
+ },
+ {
+ "bufferView":9,
+ "componentType":5126,
+ "count":24,
+ "type":"VEC2"
+ },
+ {
+ "bufferView":10,
+ "componentType":5121,
+ "count":24,
+ "normalized":true,
+ "type":"VEC4"
+ },
+ {
+ "bufferView":11,
+ "componentType":5123,
+ "count":24,
+ "normalized":true,
+ "type":"VEC4"
+ },
+ {
+ "bufferView":12,
+ "componentType":5126,
+ "count":24,
+ "max":[
+ 1,
+ 1,
+ 1
+ ],
+ "min":[
+ -1,
+ -1,
+ -1
+ ],
+ "type":"VEC3"
+ },
+ {
+ "bufferView":13,
+ "componentType":5126,
+ "count":24,
+ "type":"VEC3"
+ },
+ {
+ "bufferView":14,
+ "componentType":5126,
+ "count":24,
+ "type":"VEC2"
+ },
+ {
+ "bufferView":15,
+ "componentType":5121,
+ "count":24,
+ "normalized":true,
+ "type":"VEC4"
+ },
+ {
+ "bufferView":16,
+ "componentType":5123,
+ "count":24,
+ "normalized":true,
+ "type":"VEC4"
+ }
+ ],
+ "bufferViews":[
+ {
+ "buffer":0,
+ "byteLength":288,
+ "byteOffset":0,
+ "target":34962
+ },
+ {
+ "buffer":0,
+ "byteLength":288,
+ "byteOffset":288,
+ "target":34962
+ },
+ {
+ "buffer":0,
+ "byteLength":192,
+ "byteOffset":576,
+ "target":34962
+ },
+ {
+ "buffer":0,
+ "byteLength":72,
+ "byteOffset":768,
+ "target":34963
+ },
+ {
+ "buffer":0,
+ "byteLength":288,
+ "byteOffset":840,
+ "target":34962
+ },
+ {
+ "buffer":0,
+ "byteLength":288,
+ "byteOffset":1128,
+ "target":34962
+ },
+ {
+ "buffer":0,
+ "byteLength":192,
+ "byteOffset":1416,
+ "target":34962
+ },
+ {
+ "buffer":0,
+ "byteLength":288,
+ "byteOffset":1608,
+ "target":34962
+ },
+ {
+ "buffer":0,
+ "byteLength":288,
+ "byteOffset":1896,
+ "target":34962
+ },
+ {
+ "buffer":0,
+ "byteLength":192,
+ "byteOffset":2184,
+ "target":34962
+ },
+ {
+ "buffer":0,
+ "byteLength":96,
+ "byteOffset":2376,
+ "target":34962
+ },
+ {
+ "buffer":0,
+ "byteLength":192,
+ "byteOffset":2472,
+ "target":34962
+ },
+ {
+ "buffer":0,
+ "byteLength":288,
+ "byteOffset":2664,
+ "target":34962
+ },
+ {
+ "buffer":0,
+ "byteLength":288,
+ "byteOffset":2952,
+ "target":34962
+ },
+ {
+ "buffer":0,
+ "byteLength":192,
+ "byteOffset":3240,
+ "target":34962
+ },
+ {
+ "buffer":0,
+ "byteLength":96,
+ "byteOffset":3432,
+ "target":34962
+ },
+ {
+ "buffer":0,
+ "byteLength":192,
+ "byteOffset":3528,
+ "target":34962
+ }
+ ],
+ "samplers":[
+ {
+ "magFilter":9729,
+ "minFilter":9987,
+ "wrapS":33071,
+ "wrapT":33071
+ }
+ ],
+ "buffers":[
+ {
+ "byteLength":3720,
+ "uri":"MaterialTestCubes.bin"
+ }
+ ]
+}
diff --git a/jme3-plugins/src/test/resources/gltf/MaterialTextCubes.blend b/jme3-plugins/src/test/resources/gltf/MaterialTextCubes.blend
new file mode 100644
index 0000000000000000000000000000000000000000..ee16d8624e04fac30a27136a701619718d016f15
GIT binary patch
literal 943925
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