Use JSON or YAML formats to define materials within jMonkeyEngine

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