{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreibyyuywfq5ejvghr6jppfusw4fti4anc6qsoui37bye7rgzx6by2i",
    "uri": "at://did:plc:dxjzgxe7cvirxkwfjr2tjspt/app.bsky.feed.post/3mif4jki7r7g2"
  },
  "path": "/t/adding-full-support-for-custom-materials-to-gltfloader/49444#post_1",
  "publishedAt": "2026-03-31T17:45:59.000Z",
  "site": "https://hub.jmonkeyengine.org",
  "tags": [
    "GltfLoader",
    "@link",
    "@param",
    "@return"
  ],
  "textContent": "I would like to discuss a possible improvement to the material creation process inside the GltfLoader. There are currently a number of problems, especially if one wants to use custom materials for all loaded GLTF models.\n\n* * *\n\n### What I want to achieve\n\n  * Make it possible for custom materials to be created directly by the `GltfLoader` when loading GLTF models.\n  * Custom materials should be completely independent from jME3’s standard materials. This means they can use different material definitions and parameters.\n\n\n\n* * *\n\n### Problems with the current implementation\n\n#### CustomContentManager\n\n  * **Major bug:** If multiple GLTF extensions are present on an element, the `CustomContentManager` only processes the first supported one instead of all supported ones. This should be easy to fix.\n\n  * **Minor flaw:** To add a new `ExtensionLoader`, one only provides its class rather than an instance. The manager then instantiates it using the default constructor. This complicates implementations that require dependencies or configuration. This could be improved by adding an additional method that accepts an `ExtensionLoader` instance.\n\n\n\n\n#### GLTFLoader\n\n  * **Major flaw:** There is currently no direct way to add custom material creation logic to the `GltfLoader`.\n    * Internally, there is a `defaultMaterialAdapters` map, but there is no way to add new `MaterialAdapter`s to it. In fact, it always contains exactly one adapter that cannot be changed. In its current state, this could be replaced with a single `defaultMaterialAdapter` field.\n    * One way to provide custom `MaterialAdapter`s is via `GltfModelKey.materialAdapters`, but this requires loading models using a `GltfModelKey` instead of a simple asset path. Additionally, adapters must be added separately for every key.\n    * Another approach is to use custom `ExtensionLoader`s, since they can replace the current `MaterialAdapter`. However, this has several issues:\n      * It only works if the corresponding GLTF extension is present in the file. Without it, the loader is never invoked.\n      * You must remove or replace all existing material-related `ExtensionLoader`s to prevent them from overriding your adapter. This may result in losing useful built-in extension handling.\n\n\n\n#### Existing material-based ExtensionLoaders\n\n  * **Conceptual issue:** Instead of modifying existing data, all current material-related `ExtensionLoader`s replace the `MaterialAdapter`. Ideally, they should build upon the previous adapter, but instead they discard it entirely. As a result, only the last loader has any effect. (`PBRSpecGlossExtensionLoader` and `PBREmissiveStrengthExtensionLoader` are good exaples, that cancel each other out)\n\n  * **Control issue:** The final material type depends on the order of `ExtensionLoader`s. Since execution order is not well controlled, defining a reliable material selection process is difficult.\n\n  * **Fragility:** New `ExtensionLoader`s added in future jME3 versions could easily break custom setups if they override adapters.\n\n\n\n\n#### MaterialAdapter\n\n  * **Minor issue:** These are often stored in maps with inconsistent key naming, making them difficult to work with when adding custom adapters.\n\n\n\n* * *\n\n### My suggested solution\n\n#### GltfLoader\n\nI suggest changing the material creation process in `GltfLoader.createMaterial(...)` as follows:\n\n  1. Create a container object to collect all GLTF material parameters and extensions (e.g., `GltfMaterialData`)\n  2. Add all standard GLTF material parameters to it\n  3. Process all ExtensionLoader and ExtrasLoader, allowing them to add data to this container\n_(They no longer operate on a`MaterialAdapter`)_\n  4. Select an appropriate material factory from a configurable list (e.g., `GltfMaterialFactory`)\n_(Find the first one that supports the given data)_\n  5. Use that factory to create the final material\n\n\n\n* * *\n\n\n    public interface GltfMaterialFactory {\n\n    \t/**\n    \t * Checks, if the factory is able to create a new material from the given set of available material params.\n    \t * If it accepts the params, the {@link #createMaterial(GltfMaterialData)} method can be used to create a new material.\n    \t *\n    \t * @param gltfMaterialData The {@link GltfMaterialData} containing all available GLTF material params.\n    \t * @return true if the factory is able to create a material from the given params, otherwise false.\n    \t */\n    \tboolean accepts(GltfMaterialData gltfMaterialData);\n\n    \t/**\n    \t * Creates a new material from the given set of available material params.\n    \t *\n    \t * @param gltfMaterialData The {@link GltfMaterialData} containing all available GLTF material params.\n    \t * @return The new created {@link Material}.\n    \t */\n    \tMaterial createMaterial(GltfMaterialData gltfMaterialData);\n\n    }\n\n\n**GltfMaterialFactory:**\n\n  * Each `GltfMaterialFactory` is responsible for creating materials for a specific definition (e.g., PBRLighting, Unshaded)\n  * A factory may support multiple definitions if needed\n  * It is also possible (though not recommended) to use a single factory for everything\n\n\n\n* * *\n\n\n    public class GltfMaterialData {\n\n    \tprivate Map<String, MatParam> gltfParamMap = new HashMap<>();\n\n    \tprivate Set<String> gltfExtensions = new HashSet<>();\n\n\n    \t/**\n    \t * Checks if the material provides the given GLTF extension.\n    \t *\n    \t * @param gltfExtension The GLTF extension name.\n    \t * @return true if the material provides the given GLTF extension, otherwise false.\n    \t */\n    \tpublic boolean hasGltfExtension(String gltfExtension) {\n    \t\treturn gltfExtensions.contains(gltfExtension);\n    \t}\n\n    \t/**\n    \t * Adds the given GLTF extension name.\n    \t *\n    \t * @param gltfExtension The GLTF extension name.\n    \t */\n    \tpublic void addGltfExtension(String gltfExtension) {\n    \t\tgltfExtensions.add(gltfExtension);\n    \t}\n\n\n    \t/**\n    \t * Checks if the material provides a material param with the given name.\n    \t *\n    \t * @param gltfParamName The GLTF param name.\n    \t * @return true if the material provides a material param with the given name, otherwise false.\n    \t */\n    \tpublic boolean containsGltfParam(String gltfParamName) {\n    \t\treturn gltfParamMap.containsKey(gltfParamName);\n    \t}\n\n    \t/**\n    \t * Gets the material parameter with the given name.\n    \t *\n    \t * @param gltfParamName The GLTF param name.\n    \t * @return The {@link MatParam} with the given name, or null if no such param exists.\n    \t */\n    \tpublic MatParam getGltfParam(String gltfParamName) {\n    \t\treturn gltfParamMap.get(gltfParamName);\n    \t}\n\n    \t/**\n    \t * Adds a material param with the given name and value.\n    \t *\n    \t * @param gltfParamName The GLTF param name.\n    \t * @param value         The value of the param. Does nothing, if value is null.\n    \t */\n    \tpublic void setGltfParam(String gltfParamName, Object value) {\n    \t\tif (value != null) {\n    \t\t\tMatParam gltfParam = new MatParam(getVarType(value), gltfParamName, value);\n    \t\t\tgltfParamMap.put(gltfParamName, gltfParam);\n    \t\t}\n    \t}\n\n\n    \t// Maybe more convenience methods?\n    \t// * getGltfParam(gltfParamName, defaultValue)\n    \t// * setGltfParam(gltfParamName, value, defaultValue) ???\n    \t// * removeGltfParam(gltfParamName)\n\n\n    \tprivate VarType getVarType(Object value) {\n    \t\treturn switch (value) {\n    \t\t\tcase Float __ -> VarType.Float;\n    \t\t\tcase Integer __ -> VarType.Int;\n    \t\t\tcase Boolean __ -> VarType.Boolean;\n    \t\t\tcase ColorRGBA __ -> VarType.Vector4;\n    \t\t\tcase Vector4f __ -> VarType.Vector4;\n    \t\t\tcase Vector3f __ -> VarType.Vector3;\n    \t\t\tcase Vector2f __ -> VarType.Vector2;\n    \t\t\tcase Matrix3f __ -> VarType.Matrix3;\n    \t\t\tcase Matrix4f __ -> VarType.Matrix4;\n    \t\t\tcase String __ -> VarType.Boolean;   // WTF ???\n    \t\t\tdefault -> throw new AssetLoadException(\"Unsupported material parameter type : \" + value.getClass().getSimpleName());\n    \t\t};\n    \t}\n\n    }\n\n\n**GltfMaterialData:**\n\n  * `GltfMaterialData` acts as a container for parameters and extensions\n  * Similar to `MaterialAdapter`, but does not create materials\n  * Factories handle the actual material creation\n  * Naming conventions for parameters still need to be defined carefully to avoid collisions\n(I think naming should be close to GLTF)\n\n\n\n* * *\n\n#### GltfLoader\n\n  * Maintain a configurable list of `GltfMaterialFactory` instances\n_(Effectively replaces`defaultMaterialAdapters`)_\n  * Include default factories (e.g., PBRLighting, Unshaded)\n  * Allow users to add, remove, or replace factories\n\n\n\n* * *\n\n#### Existing ExtensionLoaders\n\n  * Must be updated to work with `GltfMaterialData` instead of `MaterialAdapter`\n  * Their role becomes purely additive (no more overriding)\n\n\n\n* * *\n\n#### GltfModelKey\n\n  * Optional: Allow adding custom `GltfMaterialFactory` instances\n  * Optional: Add “material hints” to guide factory selection\n\n\n\n* * *\n\n#### CustomContentManager\n\n  1. Fix handling of multiple extensions (**critical**)\n  2. Add methods that accept loader instances instead of only classes (**minor**)\n\n\n\n* * *\n\n### Backward compatibility\n\nThis is the tricky part. The new system is not compatible with:\n\n  * `MaterialAdapter`\n  * Existing material-based `ExtensionLoader`s\n\n\n\nThis has the potential to break some existing projects, if they use custom `MaterialAdapter` in `GltfModelKeys`. It also breaks all existing material-based `ExtensionLoader`, because they no longer get a `MaterialAdapter`, but instead the new `GltfMaterialData` as input. While this may break some projects, the number is likely small due to current limitations of the system.\n\n**Suggested approach:**\n\n  * Mark old components as deprecated:\n    * `MaterialAdapter`\n    * `GltfLoader.defaultMaterialAdapters`\n    * `GltfModelKey.materialAdapters`\n    * Old material-based `ExtensionLoader`s\n  * Add a flag in `GltfLoader` to switch between old and new behavior\n    * Default to the new system\n  * Remove the old system in a future major release\n\n\n\n* * *\n\n### What do you think?\n\nI created this thread to gather feedback. Please share:\n\n  * Suggestions for improvement\n  * Potential issues I may have missed\n  * Alternative approaches\n\n\n\nIf we arrive at a solid solution, I would be happy to implement it and open a PR.",
  "title": "Adding full support for custom materials to GltfLoader"
}