Support KHR material extension as an explicit struct. Supported extensions are: KHR_materials_pbrSpecularGlossiness, KHR_texture_transform and KHR_materials_unlit.
diff --git a/loader_example.cc b/loader_example.cc
index 93a6866..9c41017 100644
--- a/loader_example.cc
+++ b/loader_example.cc
@@ -346,11 +346,35 @@
   }
 }
 
+static void DumpKHRTextureTransform(const tinygltf::KHRTextureTransform &texform,
+                            const int indent) {
+  assert(texform.offset.size() == 2);
+
+  std::cout << Indent(indent) << "offset     : [" << texform.offset[0] << ", " << texform.offset[1] << "]\n";
+  std::cout << Indent(indent) << "rotation   : " << texform.rotation << "\n";
+  std::cout << Indent(indent) << "scale     : [" << texform.scale[0] << ", " << texform.scale[1] << "]\n";
+  if (texform.texCoord >= 0) {
+    std::cout << Indent(indent) << "texCoord  : TEXCOORD_" << texform.texCoord << "\n";
+  }
+
+  std::cout << Indent(indent) << "extensions(items=" << texform.extensions.size() << ")"
+            << std::endl;
+  DumpExtensions(texform.extensions, indent + 1);
+  std::cout << PrintValue("extras", texform.extras, indent + 1) << "\n";
+}
+
 static void DumpTextureInfo(const tinygltf::TextureInfo &texinfo,
                             const int indent) {
   std::cout << Indent(indent) << "index     : " << texinfo.index << "\n";
   std::cout << Indent(indent) << "texCoord  : TEXCOORD_" << texinfo.texCoord
             << "\n";
+  if (texinfo.KHR_textureTransform.enabled) {
+    std::cout << Indent(indent) << "KHR_texture_transform\n";
+    DumpKHRTextureTransform(texinfo.KHR_textureTransform, indent + 1);
+  }
+
+  std::cout << Indent(indent) << "extensions(items=" << texinfo.extensions.size() << ")"
+            << std::endl;
   DumpExtensions(texinfo.extensions, indent + 1);
   std::cout << PrintValue("extras", texinfo.extras, indent + 1) << "\n";
 }
@@ -361,6 +385,14 @@
   std::cout << Indent(indent) << "texCoord  : TEXCOORD_" << texinfo.texCoord
             << "\n";
   std::cout << Indent(indent) << "scale     : " << texinfo.scale << "\n";
+
+  if (texinfo.KHR_textureTransform.enabled) {
+    std::cout << Indent(indent) << "KHR_texture_transform\n";
+    DumpKHRTextureTransform(texinfo.KHR_textureTransform, indent + 1);
+  }
+
+  std::cout << Indent(indent) << "extensions(items=" << texinfo.extensions.size() << ")"
+            << std::endl;
   DumpExtensions(texinfo.extensions, indent + 1);
   std::cout << PrintValue("extras", texinfo.extras, indent + 1) << "\n";
 }
@@ -371,6 +403,9 @@
   std::cout << Indent(indent) << "texCoord  : TEXCOORD_" << texinfo.texCoord
             << "\n";
   std::cout << Indent(indent) << "strength  : " << texinfo.strength << "\n";
+
+  std::cout << Indent(indent) << "extensions(items=" << texinfo.extensions.size() << ")"
+            << std::endl;
   DumpExtensions(texinfo.extensions, indent + 1);
   std::cout << PrintValue("extras", texinfo.extras, indent + 1) << "\n";
 }
@@ -390,10 +425,35 @@
 
   std::cout << Indent(indent) << "metallicRoughnessTexture  :\n";
   DumpTextureInfo(pbr.metallicRoughnessTexture, indent + 1);
+
+  std::cout << Indent(indent) << "extensions(items=" << pbr.extensions.size() << ")"
+            << std::endl;
   DumpExtensions(pbr.extensions, indent + 1);
   std::cout << PrintValue("extras", pbr.extras, indent + 1) << "\n";
 }
 
+static void DumpKHRPbrMetallicRoughness(const tinygltf::KHRPbrSpecularGlossiness &pbr,
+                                     const int indent) {
+  std::cout << Indent(indent)
+            << "diffuseFactor   : " << PrintFloatArray(pbr.diffuseFactor)
+            << "\n";
+  std::cout << Indent(indent) << "diffuseTexture  :\n";
+  DumpTextureInfo(pbr.diffuseTexture, indent + 1);
+
+  std::cout << Indent(indent) << "specularFactor    : " << PrintFloatArray(pbr.specularFactor) << "\n";
+  std::cout << Indent(indent) << "glossinessFactor   : " << pbr.glossinessFactor
+            << "\n";
+
+  std::cout << Indent(indent) << "specularGlossinessTexture  :\n";
+  DumpTextureInfo(pbr.specularGlossinessTexture, indent + 1);
+
+  std::cout << Indent(indent) << "extensions(items=" << pbr.extensions.size() << ")"
+            << std::endl;
+  DumpExtensions(pbr.extensions, indent + 1);
+  std::cout << PrintValue("extras", pbr.extras, indent + 1) << "\n";
+}
+
+
 static void Dump(const tinygltf::Model &model) {
   std::cout << "=== Dump glTF ===" << std::endl;
   std::cout << "asset.copyright          : " << model.asset.copyright
@@ -414,6 +474,9 @@
     for (size_t i = 0; i < model.scenes.size(); i++) {
       std::cout << Indent(1) << "scene[" << i
                 << "] name  : " << model.scenes[i].name << std::endl;
+
+      std::cout << Indent(1) << "extensions(items=" << model.scenes[i].extensions.size() << ")"
+                << std::endl;
       DumpExtensions(model.scenes[i].extensions, 1);
     }
   }
@@ -589,6 +652,15 @@
       std::cout << Indent(1) << "emissiveTexture      :\n";
       DumpTextureInfo(material.emissiveTexture, 2);
 
+      if (material.KHR_materials_unlit) {
+        std::cout << Indent(1) << "KHR_materials_unlit  : true\n";
+      }
+
+      if (material.KHR_pbrSpecularGlossiness.enabled) {
+        std::cout << Indent(1) << "KHR_materials_pbrSpecularGlossinesst\n";
+        DumpKHRPbrMetallicRoughness(material.KHR_pbrSpecularGlossiness, 2);
+      }
+
       std::cout << Indent(1) << "----  legacy material parameter  ----\n";
       std::cout << Indent(1) << "values(items=" << material.values.size() << ")"
                 << std::endl;
@@ -600,7 +672,9 @@
       }
       std::cout << Indent(1) << "-------------------------------------\n";
 
-      DumpExtensions(material.extensions, 1);
+      std::cout << Indent(1) << "extensions(items=" << material.extensions.size() << ")"
+                << std::endl;
+      DumpExtensions(material.extensions, 2);
       std::cout << PrintValue("extras", material.extras, 2) << std::endl;
     }
   }
@@ -624,6 +698,9 @@
       std::cout << Indent(2) << "width     : " << image.width << std::endl;
       std::cout << Indent(2) << "height    : " << image.height << std::endl;
       std::cout << Indent(2) << "component : " << image.component << std::endl;
+
+      std::cout << Indent(1) << "extensions(items=" << image.extensions.size() << ")"
+                << std::endl;
       DumpExtensions(image.extensions, 1);
     }
   }
@@ -636,7 +713,11 @@
                 << std::endl;
       std::cout << Indent(1) << "source         : " << texture.source
                 << std::endl;
+
+      std::cout << Indent(1) << "extensions(items=" << texture.extensions.size() << ")"
+                << std::endl;
       DumpExtensions(texture.extensions, 1);
+      std::cout << "\n";
     }
   }
 
diff --git a/tiny_gltf.h b/tiny_gltf.h
index 54faf57..ea1c128 100644
--- a/tiny_gltf.h
+++ b/tiny_gltf.h
@@ -26,6 +26,8 @@
 // THE SOFTWARE.
 
 // Version:
+//  - v2.3.1 Explicitly support KHR_materials_pbrSpecularGlossiness,
+//           KHR_materials_unlit and KHR_texture_transform
 //  - v2.3.0 Modified Material representation according to glTF 2.0 schema
 //           (and introduced TextureInfo class)
 //           Change the behavior of `Value::IsNumber`. It return true either the
@@ -592,11 +594,33 @@
   bool operator==(const Texture &) const;
 };
 
+struct KHRTextureTransform {
+  bool enabled = false;  // true if KHR_texture_transform extension exists in
+                         // glTF model. also plese set this to true if you
+                         // serialize this class to glTF.
+
+  std::vector<double> offset;  // len = 2, default [0, 0]
+  double rotation = 0.0;       // default 0.0
+  std::vector<double> scale;   // len = 2, default [1, 1]
+  int texCoord = -1;  // overwrites the textureInfo texCoord if this is not -1.
+                      // textureInfo this extension belongs to have same
+                      // texCoord value after parsing this extension.
+
+  Value extras;
+  ExtensionMap extensions;
+
+  KHRTextureTransform() : enabled(false), rotation(0.0), texCoord(-1) {}
+  bool operator==(const KHRTextureTransform &) const;
+};
+
 struct TextureInfo {
   int index = -1;  // required.
   int texCoord;    // The set index of texture's TEXCOORD attribute used for
                    // texture coordinate mapping.
 
+  KHRTextureTransform
+      KHR_textureTransform;  // extensions["KHR_texture_transform"]
+
   Value extras;
   ExtensionMap extensions;
 
@@ -611,6 +635,9 @@
   double scale;    // scaledNormal = normalize((<sampled normal texture value>
                    // * 2.0 - 1.0) * vec3(<normal scale>, <normal scale>, 1.0))
 
+  KHRTextureTransform
+      KHR_textureTransform;  // extensions["KHR_texture_transform"]
+
   Value extras;
   ExtensionMap extensions;
 
@@ -625,6 +652,9 @@
   double strength;  // occludedColor = lerp(color, color * <sampled occlusion
                     // texture value>, <occlusion strength>)
 
+  KHRTextureTransform
+      KHR_textureTransform;  // extensions["KHR_texture_transform"]
+
   Value extras;
   ExtensionMap extensions;
 
@@ -647,6 +677,25 @@
   bool operator==(const PbrMetallicRoughness &) const;
 };
 
+// KHR_materials_pbrSpecularGlossiness defined in glTF 2.0 KHR extension.
+struct KHRPbrSpecularGlossiness {
+  bool enabled = false;  // true if this KHR extension exists in glTF model.
+                         // also plese set this to true if you serialize this
+                         // class to glTF.
+
+  std::vector<double> diffuseFactor;  // len = 4. default [1,1,1,1]
+  TextureInfo diffuseTexture;
+  std::vector<double> specularFactor;  // len = 3. default [1,1,1]
+  double glossinessFactor = 1.0;       // default 1
+  TextureInfo specularGlossinessTexture;
+
+  Value extras;
+  ExtensionMap extensions;
+
+  KHRPbrSpecularGlossiness() : glossinessFactor(1.0) {}
+  bool operator==(const KHRPbrSpecularGlossiness &) const;
+};
+
 // Each extension should be stored in a ParameterMap.
 // members not in the values could be included in the ParameterMap
 // to keep a single material model
@@ -664,6 +713,11 @@
   OcclusionTextureInfo occlusionTexture;
   TextureInfo emissiveTexture;
 
+  bool KHR_materials_unlit =
+      false;  // shortcut to extensions.count("KHR_materials_unlit")
+
+  KHRPbrSpecularGlossiness KHR_pbrSpecularGlossiness;
+
   // For backward compatibility
   // TODO(syoyo): Remove `values` and `additionalValues` in the next release.
   ParameterMap values;
@@ -672,7 +726,11 @@
   ExtensionMap extensions;
   Value extras;
 
-  Material() : alphaMode("OPAQUE"), alphaCutoff(0.5), doubleSided(false) {}
+  Material()
+      : alphaMode("OPAQUE"),
+        alphaCutoff(0.5),
+        doubleSided(false),
+        KHR_materials_unlit(false) {}
 
   bool operator==(const Material &) const;
 };
@@ -1570,20 +1628,33 @@
          this->name == other.name && this->sampler == other.sampler &&
          this->source == other.source;
 }
+
+bool KHRTextureTransform::operator==(const KHRTextureTransform &other) const {
+  return this->extensions == other.extensions && this->extras == other.extras &&
+         Equals(this->scale, other.scale) &&
+         Equals(this->offset, other.offset) &&
+         TINYGLTF_DOUBLE_EQUAL(this->rotation, other.rotation);
+}
+
 bool TextureInfo::operator==(const TextureInfo &other) const {
   return this->extensions == other.extensions && this->extras == other.extras &&
-         this->index == other.index && this->texCoord == other.texCoord;
+         this->index == other.index && this->texCoord == other.texCoord &&
+         (this->KHR_textureTransform == other.KHR_textureTransform);
 }
+
 bool NormalTextureInfo::operator==(const NormalTextureInfo &other) const {
   return this->extensions == other.extensions && this->extras == other.extras &&
          this->index == other.index && this->texCoord == other.texCoord &&
-         TINYGLTF_DOUBLE_EQUAL(this->scale, other.scale);
+         TINYGLTF_DOUBLE_EQUAL(this->scale, other.scale) &&
+         (this->KHR_textureTransform == other.KHR_textureTransform);
 }
 bool OcclusionTextureInfo::operator==(const OcclusionTextureInfo &other) const {
   return this->extensions == other.extensions && this->extras == other.extras &&
          this->index == other.index && this->texCoord == other.texCoord &&
-         TINYGLTF_DOUBLE_EQUAL(this->strength, other.strength);
+         TINYGLTF_DOUBLE_EQUAL(this->strength, other.strength) &&
+         (this->KHR_textureTransform == other.KHR_textureTransform);
 }
+
 bool PbrMetallicRoughness::operator==(const PbrMetallicRoughness &other) const {
   return this->extensions == other.extensions && this->extras == other.extras &&
          (this->baseColorTexture == other.baseColorTexture) &&
@@ -1592,6 +1663,17 @@
          TINYGLTF_DOUBLE_EQUAL(this->metallicFactor, other.metallicFactor) &&
          TINYGLTF_DOUBLE_EQUAL(this->roughnessFactor, other.roughnessFactor);
 }
+
+bool KHRPbrSpecularGlossiness::operator==(
+    const KHRPbrSpecularGlossiness &other) const {
+  return this->extensions == other.extensions && this->extras == other.extras &&
+         Equals(this->diffuseFactor, other.diffuseFactor) &&
+         Equals(this->specularFactor, other.specularFactor) &&
+         (this->diffuseTexture == other.diffuseTexture) &&
+         (this->specularGlossinessTexture == other.specularGlossinessTexture) &&
+         TINYGLTF_DOUBLE_EQUAL(this->glossinessFactor, other.glossinessFactor);
+}
+
 bool Value::operator==(const Value &other) const {
   return Equals(*this, other);
 }
@@ -3018,6 +3100,57 @@
   return true;
 }
 
+static bool ParseKHRTextureTransform(KHRTextureTransform *texform,
+                                     std::string *err, const json &o) {
+  if (texform == nullptr) {
+    return false;
+  }
+
+  std::vector<double> offset;
+  if (ParseNumberArrayProperty(&offset, err, o, "offset",
+                               /* required */ false)) {
+    if (offset.size() != 2) {
+      if (err) {
+        (*err) +=
+            "Array length of `offset` parameter in "
+            "KHR_texture_transform must be 2, but got " +
+            std::to_string(offset.size()) + "\n";
+      }
+      return false;
+    }
+  } else {
+    offset = {0.0, 0.0};
+  }
+  texform->offset = offset;
+
+  std::vector<double> scale;
+  if (ParseNumberArrayProperty(&scale, err, o, "scale",
+                               /* required */ false)) {
+    if (scale.size() != 2) {
+      if (err) {
+        (*err) +=
+            "Array length of `scale` parameter in "
+            "KHR_texture_transform must be 2, but got " +
+            std::to_string(scale.size()) + "\n";
+      }
+      return false;
+    }
+  } else {
+    scale = {0.0, 0.0};
+  }
+  texform->scale = scale;
+
+  ParseNumberProperty(&texform->rotation, err, o, "rotation",
+                      /* required */ false);
+
+  ParseIntegerProperty(&texform->texCoord, err, o, "texCoord",
+                       /* required */ false);
+
+  texform->enabled = true;
+
+  return true;
+}
+
 static bool ParseTextureInfo(TextureInfo *texinfo, std::string *err,
                              const json &o) {
   if (texinfo == nullptr) {
@@ -3034,7 +3167,19 @@
   ParseExtensionsProperty(&texinfo->extensions, err, o);
   ParseExtrasProperty(&texinfo->extras, o);
 
-  return true;
+  bool ret = true;
+
+  if (texinfo->extensions.count("KHR_texture_transform")) {
+    ret &= ParseKHRTextureTransform(&texinfo->KHR_textureTransform, err,
+                                    o["extensions"]["KHR_texture_transform"]);
+    if (texinfo->KHR_textureTransform.enabled &&
+        texinfo->KHR_textureTransform.texCoord >= 0) {
+      // As described in the specification, overwrite texCoord.
+      texinfo->texCoord = texinfo->KHR_textureTransform.texCoord;
+    }
+  }
+
+  return ret;
 }
 
 static bool ParseNormalTextureInfo(NormalTextureInfo *texinfo, std::string *err,
@@ -3054,7 +3199,18 @@
   ParseExtensionsProperty(&texinfo->extensions, err, o);
   ParseExtrasProperty(&texinfo->extras, o);
 
-  return true;
+  bool ret = true;
+  if (texinfo->extensions.count("KHR_texture_transform")) {
+    ret &= ParseKHRTextureTransform(&texinfo->KHR_textureTransform, err,
+                                    o["extensions"]["KHR_texture_transform"]);
+    if (texinfo->KHR_textureTransform.enabled &&
+        texinfo->KHR_textureTransform.texCoord >= 0) {
+      // As described in the specification, overwrite texCoord.
+      texinfo->texCoord = texinfo->KHR_textureTransform.texCoord;
+    }
+  }
+
+  return ret;
 }
 
 static bool ParseOcclusionTextureInfo(OcclusionTextureInfo *texinfo,
@@ -3074,7 +3230,18 @@
   ParseExtensionsProperty(&texinfo->extensions, err, o);
   ParseExtrasProperty(&texinfo->extras, o);
 
-  return true;
+  bool ret = true;
+  if (texinfo->extensions.count("KHR_texture_transform")) {
+    ret &= ParseKHRTextureTransform(&texinfo->KHR_textureTransform, err,
+                                    o["extensions"]["KHR_texture_transform"]);
+    if (texinfo->KHR_textureTransform.enabled &&
+        texinfo->KHR_textureTransform.texCoord >= 0) {
+      // As described in the specification, overwrite texCoord.
+      texinfo->texCoord = texinfo->KHR_textureTransform.texCoord;
+    }
+  }
+
+  return ret;
 }
 
 static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
@@ -3703,17 +3870,18 @@
   }
   pbr->baseColorFactor = baseColorFactor;
 
+  bool ret = true;
   {
     json::const_iterator it = o.find("baseColorTexture");
     if (it != o.end()) {
-      ParseTextureInfo(&pbr->baseColorTexture, err, it.value());
+      ret &= ParseTextureInfo(&pbr->baseColorTexture, err, it.value());
     }
   }
 
   {
     json::const_iterator it = o.find("metallicRoughnessTexture");
     if (it != o.end()) {
-      ParseTextureInfo(&pbr->metallicRoughnessTexture, err, it.value());
+      ret &= ParseTextureInfo(&pbr->metallicRoughnessTexture, err, it.value());
     }
   }
 
@@ -3726,7 +3894,78 @@
   return true;
 }
 
+static bool ParseKHRPbrSpecularGlossiness(KHRPbrSpecularGlossiness *pbr,
+                                          std::string *err, const json &o) {
+  if (pbr == nullptr) {
+    return false;
+  }
+
+  std::vector<double> diffuseFactor;
+  if (ParseNumberArrayProperty(&diffuseFactor, err, o, "diffuseFactor",
+                               /* required */ false)) {
+    if (diffuseFactor.size() != 4) {
+      if (err) {
+        (*err) +=
+            "Array length of `diffuseFactor` parameter in "
+            "KHR_materials_pbrMetallicRoughness must be 4, but got " +
+            std::to_string(diffuseFactor.size()) + "\n";
+      }
+      return false;
+    }
+  } else {
+    // fill with default values
+    diffuseFactor = {1.0, 1.0, 1.0, 1.0};
+  }
+  pbr->diffuseFactor = diffuseFactor;
+
+  bool ret = true;
+
+  {
+    json::const_iterator it = o.find("diffuseTexture");
+    if (it != o.end()) {
+      ret &= ParseTextureInfo(&pbr->diffuseTexture, err, it.value());
+    }
+  }
+
+  {
+    json::const_iterator it = o.find("specularGlossinessTexture");
+    if (it != o.end()) {
+      ret &= ParseTextureInfo(&pbr->specularGlossinessTexture, err, it.value());
+    }
+  }
+
+  std::vector<double> specularFactor;
+  if (ParseNumberArrayProperty(&specularFactor, err, o, "specularFactor",
+                               /* required */ false)) {
+    if (specularFactor.size() != 3) {
+      if (err) {
+        (*err) +=
+            "Array length of `specularFactor` parameter in "
+            "KHR_materials_pbrMetallicRoughness must be 3, but got " +
+            std::to_string(diffuseFactor.size()) + "\n";
+      }
+      return false;
+    }
+  } else {
+    // fill with default values
+    specularFactor = {1.0, 1.0, 1.0};
+  }
+  pbr->specularFactor = specularFactor;
+
+  ParseNumberProperty(&pbr->glossinessFactor, err, o, "glossinessFactor",
+                      false);
+
+  ParseExtensionsProperty(&pbr->extensions, err, o);
+  ParseExtrasProperty(&pbr->extras, o);
+
+  pbr->enabled = true;
+
+  return ret;
+}
+
 static bool ParseMaterial(Material *material, std::string *err, const json &o) {
+  bool ret = true;
+
   ParseStringProperty(&material->name, err, o, "name", /* required */ false);
 
   ParseNumberArrayProperty(&material->emissiveFactor, err, o, "emissiveFactor",
@@ -3742,29 +3981,30 @@
   {
     json::const_iterator it = o.find("pbrMetallicRoughness");
     if (it != o.end()) {
-      ParsePbrMetallicRoughness(&material->pbrMetallicRoughness, err,
-                                it.value());
+      ret &= ParsePbrMetallicRoughness(&material->pbrMetallicRoughness, err,
+                                       it.value());
     }
   }
 
   {
     json::const_iterator it = o.find("normalTexture");
     if (it != o.end()) {
-      ParseNormalTextureInfo(&material->normalTexture, err, it.value());
+      ret &= ParseNormalTextureInfo(&material->normalTexture, err, it.value());
     }
   }
 
   {
     json::const_iterator it = o.find("occlusionTexture");
     if (it != o.end()) {
-      ParseOcclusionTextureInfo(&material->occlusionTexture, err, it.value());
+      ret &= ParseOcclusionTextureInfo(&material->occlusionTexture, err,
+                                       it.value());
     }
   }
 
   {
     json::const_iterator it = o.find("emissiveTexture");
     if (it != o.end()) {
-      ParseTextureInfo(&material->emissiveTexture, err, it.value());
+      ret &= ParseTextureInfo(&material->emissiveTexture, err, it.value());
     }
   }
 
@@ -3812,7 +4052,25 @@
   ParseExtensionsProperty(&material->extensions, err, o);
   ParseExtrasProperty(&(material->extras), o);
 
-  return true;
+  //
+  // Materialize some KHR material extensions.
+  // KHR material extensions still exists in `exntensions` as Value(opaque)
+  // variable.
+  //
+  if (material->extensions.count("KHR_materials_unlit")) {
+    material->KHR_materials_unlit = true;
+  }
+
+  const std::string kKHR_materials_pbrSpecularGlossiness =
+      "KHR_materials_pbrSpecularGlossiness";
+  if (material->extensions.count(kKHR_materials_pbrSpecularGlossiness)) {
+    // Use JSON
+    ret &= ParseKHRPbrSpecularGlossiness(
+        &material->KHR_pbrSpecularGlossiness, err,
+        o["extensions"][kKHR_materials_pbrSpecularGlossiness]);
+  }
+
+  return ret;
 }
 
 static bool ParseAnimationChannel(AnimationChannel *channel, std::string *err,
@@ -5314,10 +5572,39 @@
   SerializeExtensionMap(image.extensions, o);
 }
 
+static void SerializeKHRTextureTransform(KHRTextureTransform &texform,
+                                         json &o) {
+  SerializeNumberProperty("rotation", texform.rotation, o);
+
+  if (texform.texCoord >= 0) {
+    SerializeNumberProperty("texCoord", texform.texCoord, o);
+  }
+
+  {
+    std::vector<double> default_offset = {0.0, 0.0};
+    if (!Equals(texform.offset, default_offset)) {
+      SerializeNumberArrayProperty("offset", texform.offset, o);
+    }
+  }
+
+  {
+    std::vector<double> default_scale = {1.0, 1.0};
+    if (!Equals(texform.offset, default_scale)) {
+      SerializeNumberArrayProperty("scale", texform.scale, o);
+    }
+  }
+
+  if (texform.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", texform.extras, o);
+  }
+
+  SerializeExtensionMap(texform.extensions, o);
+}
+
 static void SerializeGltfTextureInfo(TextureInfo &texinfo, json &o) {
   SerializeNumberProperty("index", texinfo.index, o);
 
-  if (texinfo.texCoord != 0) {
+  if (texinfo.texCoord >= 0) {
     SerializeNumberProperty("texCoord", texinfo.texCoord, o);
   }
 
@@ -5326,13 +5613,20 @@
   }
 
   SerializeExtensionMap(texinfo.extensions, o);
+
+  // Overwite KHR_texture_transform
+  if (texinfo.KHR_textureTransform.enabled) {
+    json texform;
+    SerializeKHRTextureTransform(texinfo.KHR_textureTransform, texform);
+    o["extensions"]["KHR_texture_transform"] = texform;
+  }
 }
 
 static void SerializeGltfNormalTextureInfo(NormalTextureInfo &texinfo,
                                            json &o) {
   SerializeNumberProperty("index", texinfo.index, o);
 
-  if (texinfo.texCoord != 0) {
+  if (texinfo.texCoord >= 0) {
     SerializeNumberProperty("texCoord", texinfo.texCoord, o);
   }
 
@@ -5345,13 +5639,20 @@
   }
 
   SerializeExtensionMap(texinfo.extensions, o);
+
+  // Overwite KHR_texture_transform
+  if (texinfo.KHR_textureTransform.enabled) {
+    json texform;
+    SerializeKHRTextureTransform(texinfo.KHR_textureTransform, texform);
+    o["extensions"]["KHR_texture_transform"] = texform;
+  }
 }
 
 static void SerializeGltfOcclusionTextureInfo(OcclusionTextureInfo &texinfo,
                                               json &o) {
   SerializeNumberProperty("index", texinfo.index, o);
 
-  if (texinfo.texCoord != 0) {
+  if (texinfo.texCoord >= 0) {
     SerializeNumberProperty("texCoord", texinfo.texCoord, o);
   }
 
@@ -5364,6 +5665,13 @@
   }
 
   SerializeExtensionMap(texinfo.extensions, o);
+
+  // Overwite KHR_texture_transform
+  if (texinfo.KHR_textureTransform.enabled) {
+    json texform;
+    SerializeKHRTextureTransform(texinfo.KHR_textureTransform, texform);
+    o["extensions"]["KHR_texture_transform"] = texform;
+  }
 }
 
 static void SerializeGltfPbrMetallicRoughness(PbrMetallicRoughness &pbr,
@@ -5401,6 +5709,41 @@
   }
 }
 
+static void SerializeGltfKHRMaterialsPbrSpecularGlossiness(
+    KHRPbrSpecularGlossiness &pbr, json &o) {
+  std::vector<double> default_diffuseFactor = {1.0, 1.0, 1.0, 1.0};
+  if (!Equals(pbr.diffuseFactor, default_diffuseFactor)) {
+    SerializeNumberArrayProperty<double>("diffuseFactor", pbr.diffuseFactor, o);
+  }
+  std::vector<double> default_specularFactor = {1.0, 1.0, 1.0};
+  if (!Equals(pbr.specularFactor, default_specularFactor)) {
+    SerializeNumberArrayProperty<double>("specularFactor", pbr.specularFactor,
+                                         o);
+  }
+
+  if (!TINYGLTF_DOUBLE_EQUAL(pbr.glossinessFactor, 1.0)) {
+    SerializeNumberProperty("glossinessFactor", pbr.glossinessFactor, o);
+  }
+
+  if (pbr.diffuseTexture.index >= -1) {
+    json texinfo;
+    SerializeGltfTextureInfo(pbr.diffuseTexture, texinfo);
+    o["diffuseTexture"] = texinfo;
+  }
+
+  if (pbr.specularGlossinessTexture.index >= -1) {
+    json texinfo;
+    SerializeGltfTextureInfo(pbr.specularGlossinessTexture, texinfo);
+    o["specularGlossinessTexture"] = texinfo;
+  }
+
+  SerializeExtensionMap(pbr.extensions, o);
+
+  if (pbr.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", pbr.extras, o);
+  }
+}
+
 static void SerializeGltfMaterial(Material &material, json &o) {
   if (material.name.size()) {
     SerializeStringProperty("name", material.name, o);
@@ -5467,6 +5810,21 @@
 
   SerializeExtensionMap(material.extensions, o);
 
+  // Overwrite KHR_materials_unlit
+  if (material.KHR_materials_unlit) {
+    o["extensions"]["KHR_materials_unlit"] = true;
+  }
+
+  // Overwrite KHR_materials_pbrSpecularGlossiness
+  if (material.KHR_pbrSpecularGlossiness.enabled) {
+    json pbr;
+
+    SerializeGltfKHRMaterialsPbrSpecularGlossiness(
+        material.KHR_pbrSpecularGlossiness, pbr);
+
+    o["extensions"]["KHR_materials_pbrSpecularGlossiness"] = pbr;
+  }
+
   if (material.extras.Type() != NULL_TYPE) {
     SerializeValue("extras", material.extras, o);
   }