Enhanced jME3 Flexibility: Explore the new JSON and YAML Materials

Hello everyone!

I would like to share updates on my project to implement support for .json & .yaml formats for material definition files. By addressing the limitations of the existing format (.j3md and .j3m), the project aims to simplify material creation, improve performance, and enhance the overall material management experience in jMonkeyEngine applications.

I created 2 new classes JsonMaterialLoader and YamlMaterialLoader, converting all the features of the J3MLoader class (ShaderNodes are not included).

Pros of JSON Format

  • Readability: Human-readable and easy to understand.
  • Lightweight: Compact format, efficient for data transfer.
  • Widely supported: Supported by most programming languages and platforms.
  • Versatile: Can represent complex data structures.
  • Self-describing: Data types are explicitly defined within the structure.

Pros of YAML Format

  • Readability: Highly readable and human-friendly syntax.
  • Flexibility: Supports various data types and complex structures.
  • Compactness: Often more concise than JSON for similar data.
  • Comments: Allows for adding explanatory comments.
  • Indentation-based: Uses whitespace for structure, improving readability.

Here are some sample files for comparison:

Material

Material.j3m:

Material MyMaterial : Common/MatDefs/Light/Lighting.j3md {
    MaterialParameters {
        Diffuse : 0.505882 0.505882 0.505882 1.0
        UseMaterialColors : true
        ParallaxHeight : 0.05
        Ambient : 0.2 0.2 0.2 1.0
        BackfaceShadows : false
        DiffuseMap : WrapRepeat_S WrapRepeat_T "Models/Ferrari/Car.jpg"
        Specular : 0.5 0.5 0.5 1.0
        Shininess : 12.5
    }
    AdditionalRenderState {
        FaceCull Back
        DepthWrite On
        ColorWrite On
        PolyOffset 0.0 0.0
        DepthTest On
        Blend Off
        Wireframe Off
    }
}

Material.json

{
  "Material": {
    "name": "MyMaterial",
    "def": "Common/MatDefs/New/Lighting.json", // or Lighting.j3md
    "MaterialParameters": [
      {
        "name": "ParallaxHeight",
        "value": 0.05
      },
      {
        "name": "Shininess",
        "value": 12.5
      },
      {
        "name": "Diffuse",
        "value": [0.5, 0.5, 0.5, 1]
      },
      {
        "name": "Ambient",
        "value": [0.2, 0.2, 0.2, 1]
      },
      {
        "name": "Specular",
        "value": [0.5, 0.5, 0.5, 1]
      },
      {
        "name": "UseMaterialColors",
        "value": true
      },
      {
        "name": "BackfaceShadows",
        "value": false
      },
      {
        "name": "DiffuseMap",
        "texture": {
          "path": "Models/Ferrari/Car.jpg",
          "wrapS": "Repeat",
          "wrapT": "Repeat",
          "minFilter": "BilinearNoMipMaps",
          "magFilter": "Bilinear",
          "flipY": false,
          "anisotropy": 4
        }
      }
    ],
    "AdditionalRenderState": {
      "faceCull": "Back",
      "blend": "Off",
      "depthWrite": true,
      "colorWrite": true,
      "depthTest": true,
      "wireframe": false,
      "polyOffset": [0.0, 0.0]
    }
  }
}

Material.yaml

---
Material: 
  name: "MyMaterial"
  def: "Common/MatDefs/New/Lighting.json" # or Lighting.j3md
  MaterialParameters: 
  - name: "ParallaxHeight"
    value: "0.05"
  - name: "Shininess"
    value: "12.5"
  - name: "Diffuse"
    value: [0.5, 0.5, 0.5, 1]
  - name: "Ambient"
    value: [0.2, 0.2, 0.2, 1]
  - name: "Specular"
    value: [0.5, 0.5, 0.5, 1]
  - name: "UseMaterialColors"
    value: "true"
  - name: "BackfaceShadows"
    value: "false"
  - name: "DiffuseMap"
    texture: 
      path: "Models/Ferrari/Car.jpg"
      wrapS: "Repeat"
      wrapT: "Repeat"
      minFilter: "BilinearNoMipMaps"
      magFilter: "Bilinear"
      flipY: "false"
      anisotropy: "4"
  AdditionalRenderState: 
    faceCull: "Back"
    blend: "Off"
    depthWrite: "true"
    colorWrite: "true"
    depthTest: "true"
    wireframe: "false"
    polyOffset: [0, 0]

Material Definition

PBRLighting.j3md

PBRLighting.json

{
   "MaterialDef":{
      "name":"PBR Lighting",
      "MaterialParameters":[
         {
            "type":"Boolean",
            "name":"SeparateTexCoord"
         },
         {
            "type":"Boolean",
            "name":"UseSpecularAA",
            "value":true
         },
         {
            "type":"Boolean",
            "name":"BackfaceShadows",
            "value":false
         },
         {
            "type":"Boolean",
            "name":"UseSpecGloss"
         },
         {
            "type":"Boolean",
            "name":"SteepParallax"
         },
         {
            "type":"Boolean",
            "name":"HardwareShadows"
         },
         {
            "type":"Boolean",
            "name":"HorizonFade"
         },
         {
            "type":"Boolean",
            "name":"UseInstancing"
         },
         {
            "type":"Boolean",
            "name":"AoPackedInMRMap"
         },
         {
            "type":"Boolean",
            "name":"PackedNormalParallax"
         },
         {
            "type":"Boolean",
            "name":"UseVertexColor"
         },
         {
            "type":"Boolean",
            "name":"LightMapAsAOMap"
         },
         {
            "type":"Float",
            "name":"Roughness",
            "value":1
         },
         {
            "type":"Float",
            "name":"Glossiness",
            "value":1
         },
         {
            "type":"Float",
            "name":"NormalType",
            "value":-1
         },
         {
            "type":"Float",
            "name":"ShadowIntensity"
         },
         {
            "type":"Float",
            "name":"EmissivePower",
            "value":3
         },
         {
            "type":"Float",
            "name":"NormalScale"
         },
         {
            "type":"Float",
            "name":"EmissiveIntensity",
            "value":2
         },
         {
            "type":"Float",
            "name":"Metallic",
            "value":1
         },
         {
            "type":"Float",
            "name":"AlphaDiscardThreshold"
         },
         {
            "type":"Float",
            "name":"SpecularAAKappa"
         },
         {
            "type":"Float",
            "name":"SpecularAASigma"
         },
         {
            "type":"Float",
            "name":"PCFEdge"
         },
         {
            "type":"Float",
            "name":"ParallaxHeight",
            "value":0.05
         },
         {
            "type":"Float",
            "name":"AoStrength"
         },
         {
            "type":"Float",
            "name":"ShadowMapSize"
         },
         {
            "type":"FloatArray",
            "name":"MorphWeights"
         },
         {
            "type":"Int",
            "name":"NumberOfBones"
         },
         {
            "type":"Int",
            "name":"NumberOfMorphTargets"
         },
         {
            "type":"Int",
            "name":"FilterMode"
         },
         {
            "type":"Int",
            "name":"NumberOfTargetsBuffers"
         },
         {
            "type":"Int",
            "name":"BoundDrawBuffer"
         },
         {
            "type":"Matrix4",
            "name":"LightViewProjectionMatrix0"
         },
         {
            "type":"Matrix4",
            "name":"LightViewProjectionMatrix1"
         },
         {
            "type":"Matrix4",
            "name":"LightViewProjectionMatrix2"
         },
         {
            "type":"Matrix4",
            "name":"LightViewProjectionMatrix3"
         },
         {
            "type":"Matrix4",
            "name":"LightViewProjectionMatrix4"
         },
         {
            "type":"Matrix4",
            "name":"LightViewProjectionMatrix5"
         },
         {
            "type":"Matrix4Array",
            "name":"BoneMatrices"
         },
         {
            "type":"Texture2D",
            "name":"EmissiveMap"
         },
         {
            "type":"Texture2D",
            "name":"SpecularMap"
         },
         {
            "type":"Texture2D",
            "name":"SpecularGlossinessMap"
         },
         {
            "type":"Texture2D",
            "name":"MetallicRoughnessMap",
            "colorSpace":"Linear"
         },
         {
            "type":"Texture2D",
            "name":"ParallaxMap",
            "colorSpace":"Linear"
         },
         {
            "type":"Texture2D",
            "name":"RoughnessMap",
            "colorSpace":"Linear"
         },
         {
            "type":"Texture2D",
            "name":"GlossinessMap"
         },
         {
            "type":"Texture2D",
            "name":"ShadowMap5"
         },
         {
            "type":"Texture2D",
            "name":"ShadowMap4"
         },
         {
            "type":"Texture2D",
            "name":"ShadowMap1"
         },
         {
            "type":"Texture2D",
            "name":"ShadowMap0"
         },
         {
            "type":"Texture2D",
            "name":"ShadowMap3"
         },
         {
            "type":"Texture2D",
            "name":"ShadowMap2"
         },
         {
            "type":"Texture2D",
            "name":"NormalMap",
            "colorSpace":"Linear"
         },
         {
            "type":"Texture2D",
            "name":"BaseColorMap"
         },
         {
            "type":"Texture2D",
            "name":"MetallicMap",
            "colorSpace":"Linear"
         },
         {
            "type":"Texture2D",
            "name":"LightMap"
         },
         {
            "type":"Vector2",
            "name":"FadeInfo"
         },
         {
            "type":"Vector3",
            "name":"LightPos"
         },
         {
            "type":"Vector3",
            "name":"LightDir"
         },
         {
            "type":"Vector4",
            "name":"Emissive"
         },
         {
            "type":"Vector4",
            "name":"Splits"
         },
         {
            "type":"Vector4",
            "name":"BaseColor",
            "value":[
               1,
               1,
               1,
               1
            ]
         },
         {
            "type":"Vector4",
            "name":"Specular",
            "value":[
               1,
               1,
               1,
               1
            ]
         }
      ],
      "Techniques":[
         {
            "name":"PostShadow",
            "FragmentShader":"Common/MatDefs/Shadow/PostShadowPBR.frag",
            "VertexShader":"Common/MatDefs/Shadow/PostShadow.vert",
            "ShaderLanguages":[
               "GLSL310", "GLSL300", "GLSL150", "GLSL100"
            ],
            "WorldParameters":[
               "WorldViewProjectionMatrix",
               "WorldMatrix",
               "ViewProjectionMatrix",
               "ViewMatrix"
            ],
            "Defines":[
               {
                  "name":"BOUND_DRAW_BUFFER",
                  "param":"BoundDrawBuffer"
               },
               {
                  "name":"HARDWARE_SHADOWS",
                  "param":"HardwareShadows"
               },
               {
                  "name":"FILTER_MODE",
                  "param":"FilterMode"
               },
               {
                  "name":"PCFEDGE",
                  "param":"PCFEdge"
               },
               {
                  "name":"DISCARD_ALPHA",
                  "param":"AlphaDiscardThreshold"
               },
               {
                  "name":"SHADOWMAP_SIZE",
                  "param":"ShadowMapSize"
               },
               {
                  "name":"FADE",
                  "param":"FadeInfo"
               },
               {
                  "name":"PSSM",
                  "param":"Splits"
               },
               {
                  "name":"POINTLIGHT",
                  "param":"LightViewProjectionMatrix5"
               },
               {
                  "name":"NUM_BONES",
                  "param":"NumberOfBones"
               },
               {
                  "name":"INSTANCING",
                  "param":"UseInstancing"
               },
               {
                  "name":"BACKFACE_SHADOWS",
                  "param":"BackfaceShadows"
               },
               {
                  "name":"NUM_MORPH_TARGETS",
                  "param":"NumberOfMorphTargets"
               },
               {
                  "name":"NUM_TARGETS_BUFFERS",
                  "param":"NumberOfTargetsBuffers"
               }
            ],
            "ForcedRenderState":{
               "Blend":"Modulate",
               "DepthWrite":false,
               "PolyOffset":[
                  -0.1,
                  0.0
               ]
            }
         },
         {
            "name":"PreShadow",
            "FragmentShader":"Common/MatDefs/Shadow/PreShadowPBR.frag",
            "VertexShader":"Common/MatDefs/Shadow/PreShadow.vert",
            "ShaderLanguages":[
               "GLSL300", "GLSL150", "GLSL100"
            ],
            "WorldParameters":[
               "WorldViewProjectionMatrix",
               "WorldViewMatrix",
               "ViewProjectionMatrix",
               "ViewMatrix"
            ],
            "Defines":[
               {
                  "name":"BOUND_DRAW_BUFFER",
                  "param":"BoundDrawBuffer"
               },
               {
                  "name":"DISCARD_ALPHA",
                  "param":"AlphaDiscardThreshold"
               },
               {
                  "name":"NUM_BONES",
                  "param":"NumberOfBones"
               },
               {
                  "name":"INSTANCING",
                  "param":"UseInstancing"
               },
               {
                  "name":"NUM_MORPH_TARGETS",
                  "param":"NumberOfMorphTargets"
               },
               {
                  "name":"NUM_TARGETS_BUFFERS",
                  "param":"NumberOfTargetsBuffers"
               }
            ],
            "ForcedRenderState":{
               "FaceCull":"Off",
               "ColorWrite":false,
               "PolyOffset":[
                  5.0,
                  3.0
               ]
            }
         },
         {
            "name":"PreNormalPass",
            "FragmentShader":"Common/MatDefs/SSAO/normal.frag",
            "VertexShader":"Common/MatDefs/SSAO/normal.vert",
            "ShaderLanguages":[
               "GLSL300", "GLSL150", "GLSL100"
            ],
            "WorldParameters":[
               "WorldViewProjectionMatrix",
               "WorldViewMatrix",
               "NormalMatrix",
               "ViewProjectionMatrix",
               "ViewMatrix"
            ],
            "Defines":[
               {
                  "name":"BOUND_DRAW_BUFFER",
                  "param":"BoundDrawBuffer"
               },
               {
                  "name":"BASECOLORMAP_ALPHA",
                  "param":"BaseColorMap"
               },
               {
                  "name":"NUM_BONES",
                  "param":"NumberOfBones"
               },
               {
                  "name":"INSTANCING",
                  "param":"UseInstancing"
               },
               {
                  "name":"NUM_MORPH_TARGETS",
                  "param":"NumberOfMorphTargets"
               },
               {
                  "name":"NUM_TARGETS_BUFFERS",
                  "param":"NumberOfTargetsBuffers"
               }
            ]
         },
         {
            "name":"Glow",
            "FragmentShader":"Common/MatDefs/Light/PBRGlow.frag",
            "VertexShader":"Common/MatDefs/Misc/Unshaded.vert",
            "ShaderLanguages":[
               "GLSL300", "GLSL150", "GLSL100"
            ],
            "WorldParameters":[
               "WorldViewProjectionMatrix",
               "ViewProjectionMatrix",
               "ViewMatrix"
            ],
            "Defines":[
               {
                  "name":"HAS_EMISSIVEMAP",
                  "param":"EmissiveMap"
               },
               {
                  "name":"HAS_EMISSIVECOLOR",
                  "param":"Emissive"
               },
               {
                  "name":"BOUND_DRAW_BUFFER",
                  "param":"BoundDrawBuffer"
               },
               {
                  "name":"NUM_BONES",
                  "param":"NumberOfBones"
               },
               {
                  "name":"INSTANCING",
                  "param":"UseInstancing"
               },
               {
                  "name":"NUM_MORPH_TARGETS",
                  "param":"NumberOfMorphTargets"
               },
               {
                  "name":"NUM_TARGETS_BUFFERS",
                  "param":"NumberOfTargetsBuffers"
               }
            ]
         },
         {
            "name":"Default",
            "FragmentShader":"Common/MatDefs/Light/PBRLighting.frag",
            "VertexShader":"Common/MatDefs/Light/PBRLighting.vert",
            "ShaderLanguages":[
               "GLSL300", "GLSL150", "GLSL100"
            ],
            "LightMode":"SinglePassAndImageBased",
            "WorldParameters":[
               "WorldViewProjectionMatrix",
               "CameraPosition",
               "WorldMatrix",
               "WorldNormalMatrix",
               "ViewProjectionMatrix",
               "ViewMatrix"
            ],
            "Defines":[
               {
                  "name":"BOUND_DRAW_BUFFER",
                  "param":"BoundDrawBuffer"
               },
               {
                  "name":"BASECOLORMAP",
                  "param":"BaseColorMap"
               },
               {
                  "name":"NORMALMAP",
                  "param":"NormalMap"
               },
               {
                  "name":"NORMALSCALE",
                  "param":"NormalScale"
               },
               {
                  "name":"METALLICMAP",
                  "param":"MetallicMap"
               },
               {
                  "name":"ROUGHNESSMAP",
                  "param":"RoughnessMap"
               },
               {
                  "name":"EMISSIVEMAP",
                  "param":"EmissiveMap"
               },
               {
                  "name":"EMISSIVE",
                  "param":"Emissive"
               },
               {
                  "name":"SPECGLOSSPIPELINE",
                  "param":"UseSpecGloss"
               },
               {
                  "name":"PARALLAXMAP",
                  "param":"ParallaxMap"
               },
               {
                  "name":"NORMALMAP_PARALLAX",
                  "param":"PackedNormalParallax"
               },
               {
                  "name":"STEEP_PARALLAX",
                  "param":"SteepParallax"
               },
               {
                  "name":"LIGHTMAP",
                  "param":"LightMap"
               },
               {
                  "name":"SEPARATE_TEXCOORD",
                  "param":"SeparateTexCoord"
               },
               {
                  "name":"DISCARD_ALPHA",
                  "param":"AlphaDiscardThreshold"
               },
               {
                  "name":"NUM_BONES",
                  "param":"NumberOfBones"
               },
               {
                  "name":"INSTANCING",
                  "param":"UseInstancing"
               },
               {
                  "name":"USE_PACKED_MR",
                  "param":"MetallicRoughnessMap"
               },
               {
                  "name":"USE_PACKED_SG",
                  "param":"SpecularGlossinessMap"
               },
               {
                  "name":"SPECULARMAP",
                  "param":"SpecularMap"
               },
               {
                  "name":"SPECULAR_AA",
                  "param":"UseSpecularAA"
               },
               {
                  "name":"SPECULAR_AA_SCREEN_SPACE_VARIANCE",
                  "param":"SpecularAASigma"
               },
               {
                  "name":"SPECULAR_AA_THRESHOLD",
                  "param":"SpecularAAKappa"
               },
               {
                  "name":"GLOSSINESSMAP",
                  "param":"GlossinessMap"
               },
               {
                  "name":"NORMAL_TYPE",
                  "param":"NormalType"
               },
               {
                  "name":"VERTEX_COLOR",
                  "param":"UseVertexColor"
               },
               {
                  "name":"AO_MAP",
                  "param":"LightMapAsAOMap"
               },
               {
                  "name":"AO_PACKED_IN_MR_MAP",
                  "param":"AoPackedInMRMap"
               },
               {
                  "name":"AO_STRENGTH",
                  "param":"AoStrength"
               },
               {
                  "name":"NUM_MORPH_TARGETS",
                  "param":"NumberOfMorphTargets"
               },
               {
                  "name":"NUM_TARGETS_BUFFERS",
                  "param":"NumberOfTargetsBuffers"
               },
               {
                  "name":"HORIZON_FADE",
                  "param":"HorizonFade"
               }
            ]
         }
      ]
   }
}

PBRLighting.yaml

---
MaterialDef: 
  name: "PBR Lighting"
  MaterialParameters: 
    - Color:
      - BaseColor: [1, 1, 1, 1]
      - Specular: [1, 1, 1, 1]
    - Vector2:
      - FadeInfo: 
    - Vector3:
      - LightPos: 
      - LightDir: 
    - Float:
      - Roughness: 1
      - Metallic: 1
      - Glossiness: 1
      - NormalType: -1
      - EmissivePower: 3
      - EmissiveIntensity: 2
      - AlphaDiscardThreshold: 
      - ShadowIntensity: 
      - NormalScale: 
      - SpecularAAKappa: 
      - SpecularAASigma: 
      - PCFEdge: 
      - ParallaxHeight: 0.05
      - AoStrength: 
      - ShadowMapSize: 
    - Texture2D:
      - MetallicRoughnessMap: Linear
      - MetallicMap: Linear
      - NormalMap: Linear
      - ParallaxMap: Linear
      - BaseColorMap: 
      - EmissiveMap: 
      - SpecularMap: 
      - GlossinessMap: 
      - SpecularGlossinessMap: 
      - RoughnessMap: 
      - LightMap: 
      - ShadowMap0: 
      - ShadowMap1: 
      - ShadowMap2: 
      - ShadowMap3: 
      - ShadowMap4: 
      - ShadowMap5: 
    - Boolean:
      - UseSpecularAA: true
      - BackfaceShadows: false
      - UseSpecGloss: 
      - SteepParallax: 
      - HardwareShadows: 
      - HorizonFade: 
      - UseInstancing: 
      - AoPackedInMRMap: 
      - PackedNormalParallax: 
      - UseVertexColor: 
      - LightMapAsAOMap: 
      - SeparateTexCoord: 
    - Matrix4:
      - LightViewProjectionMatrix0: 
      - LightViewProjectionMatrix1: 
      - LightViewProjectionMatrix2: 
      - LightViewProjectionMatrix3: 
      - LightViewProjectionMatrix4: 
      - LightViewProjectionMatrix5: 
  # etc...
  Techniques: 
  - name: "PostShadow"
    FragmentShader: "Common/MatDefs/Shadow/PostShadowPBR.frag"
    VertexShader: "Common/MatDefs/Shadow/PostShadow.vert"
    ShaderLanguages: ["GLSL310", "GLSL300", "GLSL150", "GLSL100"]
    WorldParameters: 
    - "WorldViewProjectionMatrix"
    - "WorldMatrix"
    - "ViewProjectionMatrix"
    - "ViewMatrix"
    Defines: 
      BOUND_DRAW_BUFFER: "BoundDrawBuffer"
      HARDWARE_SHADOWS: "HardwareShadows"
      FILTER_MODE: "FilterMode"
      PCFEDGE: "PCFEdge"
      DISCARD_ALPHA: "AlphaDiscardThreshold"
      SHADOWMAP_SIZE: "ShadowMapSize"
      FADE: "FadeInfo"
      PSSM: "Splits"
      POINTLIGHT: "LightViewProjectionMatrix5"
      NUM_BONES: "NumberOfBones"
      INSTANCING: "UseInstancing"
      BACKFACE_SHADOWS: "BackfaceShadows"
      NUM_MORPH_TARGETS: "NumberOfMorphTargets"
      NUM_TARGETS_BUFFERS: "NumberOfTargetsBuffers"
    ForcedRenderState: 
      Blend: "Modulate"
      DepthWrite: "false"
      PolyOffset: [-0.1, 0]
  - name: "PreShadow"
    FragmentShader: "Common/MatDefs/Shadow/PreShadowPBR.frag"
    VertexShader: "Common/MatDefs/Shadow/PreShadow.vert"
    ShaderLanguages: ["GLSL300", "GLSL150", "GLSL100"]
    WorldParameters: 
    - "WorldViewProjectionMatrix"
    - "WorldViewMatrix"
    - "ViewProjectionMatrix"
    - "ViewMatrix"
    Defines: 
      BOUND_DRAW_BUFFER: "BoundDrawBuffer"
      DISCARD_ALPHA: "AlphaDiscardThreshold"
      NUM_BONES: "NumberOfBones"
      INSTANCING: "UseInstancing"
      NUM_MORPH_TARGETS: "NumberOfMorphTargets"
      NUM_TARGETS_BUFFERS: "NumberOfTargetsBuffers"
    ForcedRenderState: 
      FaceCull: "Off"
      ColorWrite: "false"
      PolyOffset: [5, 3]
  - name: "PreNormalPass"
    FragmentShader: "Common/MatDefs/SSAO/normal.frag"
    VertexShader: "Common/MatDefs/SSAO/normal.vert"
    ShaderLanguages: ["GLSL300", "GLSL150", "GLSL100"]
    WorldParameters: 
    - "WorldViewProjectionMatrix"
    - "WorldViewMatrix"
    - "NormalMatrix"
    - "ViewProjectionMatrix"
    - "ViewMatrix"
    Defines: 
      BOUND_DRAW_BUFFER: "BoundDrawBuffer"
      BASECOLORMAP_ALPHA: "BaseColorMap"
      NUM_BONES: "NumberOfBones"
      INSTANCING: "UseInstancing"
      NUM_MORPH_TARGETS: "NumberOfMorphTargets"
      NUM_TARGETS_BUFFERS: "NumberOfTargetsBuffers"
  - name: "Glow"
    FragmentShader: "Common/MatDefs/Light/PBRGlow.frag"
    VertexShader: "Common/MatDefs/Misc/Unshaded.vert"
    ShaderLanguages: ["GLSL300", "GLSL150", "GLSL100"]
    WorldParameters: 
    - "WorldViewProjectionMatrix"
    - "ViewProjectionMatrix"
    - "ViewMatrix"
    Defines: 
      HAS_EMISSIVEMAP: "EmissiveMap"
      HAS_EMISSIVECOLOR: "Emissive"
      BOUND_DRAW_BUFFER: "BoundDrawBuffer"
      NUM_BONES: "NumberOfBones"
      INSTANCING: "UseInstancing"
      NUM_MORPH_TARGETS: "NumberOfMorphTargets"
      NUM_TARGETS_BUFFERS: "NumberOfTargetsBuffers"
  - name: "Default"
    FragmentShader: "Common/MatDefs/Light/PBRLighting.frag"
    VertexShader: "Common/MatDefs/Light/PBRLighting.vert"
    ShaderLanguages: ["GLSL300", "GLSL150", "GLSL100"]
    LightMode: "SinglePassAndImageBased"
    WorldParameters: 
    - "WorldViewProjectionMatrix"
    - "CameraPosition"
    - "WorldMatrix"
    - "WorldNormalMatrix"
    - "ViewProjectionMatrix"
    - "ViewMatrix"
    Defines: 
      BOUND_DRAW_BUFFER: "BoundDrawBuffer"
      BASECOLORMAP: "BaseColorMap"
      NORMALMAP: "NormalMap"
      NORMALSCALE: "NormalScale"
      METALLICMAP: "MetallicMap"
      ROUGHNESSMAP: "RoughnessMap"
      EMISSIVEMAP: "EmissiveMap"
      EMISSIVE: "Emissive"
      SPECGLOSSPIPELINE: "UseSpecGloss"
      PARALLAXMAP: "ParallaxMap"
      NORMALMAP_PARALLAX: "PackedNormalParallax"
      STEEP_PARALLAX: "SteepParallax"
      LIGHTMAP: "LightMap"
      SEPARATE_TEXCOORD: "SeparateTexCoord"
      DISCARD_ALPHA: "AlphaDiscardThreshold"
      NUM_BONES: "NumberOfBones"
      INSTANCING: "UseInstancing"
      USE_PACKED_MR: "MetallicRoughnessMap"
      USE_PACKED_SG: "SpecularGlossinessMap"
      SPECULARMAP: "SpecularMap"
      SPECULAR_AA: "UseSpecularAA"
      SPECULAR_AA_SCREEN_SPACE_VARIANCE: "SpecularAASigma"
      SPECULAR_AA_THRESHOLD: "SpecularAAKappa"
      GLOSSINESSMAP: "GlossinessMap"
      NORMAL_TYPE: "NormalType"
      VERTEX_COLOR: "UseVertexColor"
      AO_MAP: "LightMapAsAOMap"
      AO_PACKED_IN_MR_MAP: "AoPackedInMRMap"
      AO_STRENGTH: "AoStrength"
      NUM_MORPH_TARGETS: "NumberOfMorphTargets"
      NUM_TARGETS_BUFFERS: "NumberOfTargetsBuffers"
      HORIZON_FADE: "HorizonFade"

For this format I had also thought of this structure for the MaterialParameters block, let me know which one you prefer:

---
MaterialDef: 
  name: "PBR Lighting"
  MatParams: 
  - Float: {name: Roughness, value: 1}
  - Texture2D: {name: MetallicRoughnessMap, colorSpace: Linear}
  - Color: {name: BaseColor, value: [1, 1, 1, 1]}
  - Vector2: {name: FadeInfo}
  - Vector3: {name: LightPos}
  - Vector3: {name: LightDir}
  - Int: {name: UseInstancing}
  - Boolean: {name: UseSpecularAA, value: true}
  - Boolean: {name: UseInstancing}
  - Matrix4: {name: LightViewProjectionMatrix0}
  
# ...

I wrote the project taking cues from other engines like Unity, partly for fun and partly because I think the current format is a bit old, limited, and difficult to evolve (see the numerous parsing problems of the SDKā€™s Materials editor).

Obviously the project is developed as a library external to the engine.
Let me know if there is interest in the idea. Any constructive criticism is always appreciated.

9 Likes

Very nice work. Itā€™s nice to have on maven-central maybe or in jme3-plugins if you will (that will be a great port). I have a simple comment, why are you using JSON arrays for the inner JSON objects, couldnā€™t they just be all JSON objects that would be faster to search for objects by keys only?

Looking good, especially json would make interfacing with external tools a lot easier.

Maybe a nitpick, why are some property names capitalized (MaterialParameters, AdditionalRenderState), while all the others arenā€™t? Feels a bit inconsistent.

One thought is documentation. The j3md format allows for comments while json doesnā€™t. The parameters in particular often need some documentation to describe how to use them (my Typed materials plugin uses the comments on the j3md to produce javadocs).

Perhaps a dedicated documentation key?

2 Likes

Thanks for your replies guys.

JSON support is a game-changer for external tool interactions. Couldnā€™t agree more!

Correct observation. I can easily fix that. :+1:

From Google search:
All JSON field names should follow the Naming Conventions (camelCase , American English, etc.) Field names MUST be ASCII alpha num characters, underscore ( _ ) or dollar sign ( $ ). Fields with null value SHOULD be omitted.

Unfortunately I have no experience on how to publish a library to maven, I would like to learn. Maybe someone could teach me how to do it. Time will tell if it becomes the new engine standard :wink:

Simple answer, no. For example, if you think about it, the quantity and names of the MaterialParameters are defined by the programmers in a completely random way, and are not predictable. To read and interpret the data properly, a JsonArray is required. Considering the optional fields and values, the current json structure (type, name, value, texture, etcā€¦) seems to me the most efficient and readable.

Here is the solution for those who want to add comments in json files :wink:

Comments Inside JSON:

According to the JSON specification, a JSON document should only contain data structures like arrays and objects and not include comments. This is because JSON is intended to be a simple, easily parsable data format that can be quickly and efficiently processed.

Comments, while useful for providing additional context or explanation for human readers, can add complexity to the parsing process. This slows down performance and increases the risk of errors.

The primary reason why JSON does not support comments is that its creator, Douglas Crockford, deliberately removed them from the format to prevent misuse and keep it as a pure data-only format.

Then, how can you add comments to a JSON file?

The only way this can be done is to include comments as data pairs in a JSON file. It is not a commonly used or recommended practice, but technically itā€™s the best way to add comments to your JSON file.

Create a custom element within your JSON object, such as ā€œ_commentā€, to distinguish them from the rest of the data. You can decide to use two slash such as ā€œ//commentā€ or any other allowed character. The goal is to make it clear that itā€™s a comment .

{
    "_comment": "Put your JSON comment here"
    "name": "John Doe",
    "age": 35,
    "city": "New York City",
    "isMarried": true,
    "occupation": "Software Engineer",
}

You now know how to add comments to your JSON file technically. But how can you add multiple comments? This is possible, but you should remember that JSON does not allow duplicate object keys. You must include unique letters or numbers in the comment element, ensuring it is valid and distinguishable from other elements in the JSON file.

{
    "_comment1": "This is the basic data",
    "name": "John Doe",
    "age": 35,
    "city": "New York City",
    "_comment2": "Marital information",
    "isMarried": true,
    "wifeName": "Jane Doe"
}

When you have nested JSON objects, you can use similar object keys:

{
    "_comment": "This is the basic data",
    "name": "John Doe",
    "age": 35,
    "city": "New York City",
    "maritalInfo": {
        "_comment": "Marital information",
        "isMarried": true,
        "wifeName": "Jane Doe"
    }
}

Conlcusion:

You now know how to add comments to your JSON file. But because these comments are also processed and can be accessed, you need to be careful when adding comments to JSON files using custom elements.

Personally I prefer the YAML format (also used by Unity, see its .metadata files), because its structure is more compact and allows you to add comments.
While JSON is typically more detailed, both formats have their merits. Ultimately, the best format depends on individual preferences and project constraints.

Pros of JSON Format

  • Readability: Human-readable and easy to understand.
  • Lightweight: Compact format, efficient for data transfer.
  • Widely supported: Supported by most programming languages and platforms.
  • Versatile: Can represent complex data structures.
  • Self-describing: Data types are explicitly defined within the structure.

Pros of YAML Format

  • Readability: Highly readable and human-friendly syntax.
  • Flexibility: Supports various data types and complex structures.
  • Compactness: Often more concise than JSON for similar data.
  • Comments: Allows for adding explanatory comments.
  • Indentation-based: Uses whitespace for structure, improving readability.

Usage example (Read):

assetManager.registerLoader(JsonMaterialLoader.class, "json");
assetManager.registerLoader(YamlMaterialLoader.class, "yaml");

Material mat = assetManager.loadAsset(new YamlMaterialKey("MyMaterial.yaml"));
// or
Material mat = assetManager.loadAsset(new JsonMaterialKey("MyMaterial.json"));
// or from MaterialDef
Material mat = new Material(assetManager, "MatDefs/New/PBRLighting.yaml");
// or from MaterialDef
Material mat = new Material(assetManager, "MatDefs/New/PBRLighting.json");

Usage example (Write):

    private void writeJ3m(Material mat, File file) {
        try {
            JsonMaterialExporter exporter = new JsonMaterialExporter();
            // or
            // YamlMaterialExporter exporter = new YamlMaterialExporter();
            exporter.save(mat, file);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
5 Likes

Iā€™m happy to help people publish open-source software to Maven Central, provided their projects use Gradle.

The first step is to claim an Internet domain. An easy way to do this is to create an account at GitHub. For instance, if your GitHub username is ā€œcapdevonā€, then you control the ā€œcapdevon.github.ioā€ domain.

The 2nd step is to create an account at SonaTypeā€™s ā€œCentral Portalā€:
https://central.sonatype.org/register/central-portal/

Once you get that far, let me know.

4 Likes

I cannot help you; becasue I am using the old nexus manager. Note that, old tutorials are now misleading because Sonatype has decommissioned the use of the nexus manager and in a migration move to their central domain, so unless you have something already there at the nexus domain, you shouldnā€™t use it anymore, and lot of tutorials still using it, so beware and use the official tutorial for publishing on sonatype central.

1 Like

Hi everyone,

As promised, version 0.1.0 of the jme-material-portable library is now available on GitHub.

Iā€™ll be writing a comprehensive wiki guide soon to document the new structure of the JSON and YAML formats for Material and MaterialDef objects.

In the meantime, you can explore some examples of j3md files converted to JSON/YAML in the resources folder.

Please note that all parameter names follow the camelCase convention.

Feel free to report any bugs or suggestions you encounter.

Thank you, and I hope you find this library helpful!

7 Likes

Hi everyone,
Iā€™m excited to announce that the first version of projectā€™s wiki is now available on GitHub!

Documenting the library is a lot of time consuming work, but I hope this wiki will prove valuable for future development.

One of the highlights is the support for both JSON and YAML formats. This means you can use your preferred IDEā€™s editors and leverage its powerful validation and formatting tools.

The codebase is now more maintainable thanks to optimized parsers and a familiar syntax. The definition files are better organized, making it easier to add new configuration parameters.

Iā€™m particularly proud of the texture section, which offers a streamlined way to configure various options and includes the ā€˜anisotropyā€™ parameter.

---
material: 
  name: "MyMaterial"
  def: "MatDefs/New/Lighting.yaml"
  materialParameters: 
  - name: "DiffuseMap"
    texture: 
      path: "Models/Ferrari/Car.jpg"
      wrapS: "Repeat"
      wrapT: "Repeat"
      minFilter: "BilinearNoMipMaps"
      magFilter: "Bilinear"
      flipY: "false"
      anisotropy: "4"
...

Check out the wiki for more details. Let me know if you have any questions or suggestions!

3 Likes

I do think adding a defined coment field would be good. I know you can just ā€œdo whateverā€ to add a comment into json but that isnā€™t any good to downstream consumers. A library that wanted to generate java classes (with javadocs) based on material definitions would want a particular documentation field to pull from