From 042fbefac686666190915d206600a5dab8e03066 Mon Sep 17 00:00:00 2001
From: Julien Duroure <julien.duroure@gmail.com>
Date: Thu, 7 Jul 2022 08:03:39 +0200
Subject: [PATCH] glTF importer/exporter: Manage some official Khronos
 Extensions about Materials

    KHR_materials_ior
    KHR_materials_sheen
    KHR_materials_specular
    KHR_materials_transmission
    KHR_materials_variants
    KHR_materials_emissive_strength
    KHR_materials_volume

Documentation update is still in progress
---
 io_scene_gltf2/__init__.py                    | 106 +++-
 .../blender/com/gltf2_blender_default.py      |   6 +
 .../com/gltf2_blender_material_helpers.py     |  20 +-
 .../blender/com/gltf2_blender_ui.py           | 472 ++++++++++++++++++
 .../blender/exp/gltf2_blender_export_keys.py  |   1 -
 .../blender/exp/gltf2_blender_gather.py       |   2 +
 .../blender/exp/gltf2_blender_gather_image.py |  95 +++-
 .../exp/gltf2_blender_gather_materials.py     | 260 +++-------
 ...ltf2_blender_gather_materials_clearcoat.py |  81 +++
 ...gltf2_blender_gather_materials_emission.py |  61 +++
 .../exp/gltf2_blender_gather_materials_ior.py |  35 ++
 ...gather_materials_pbr_metallic_roughness.py |   8 +-
 .../gltf2_blender_gather_materials_sheen.py   |  68 +++
 ...gltf2_blender_gather_materials_specular.py | 168 +++++++
 ...2_blender_gather_materials_transmission.py |  47 ++
 .../gltf2_blender_gather_materials_unlit.py   |   6 +-
 ...gltf2_blender_gather_materials_variants.py |  18 +
 .../gltf2_blender_gather_materials_volume.py  |  75 +++
 .../exp/gltf2_blender_gather_primitives.py    |  56 ++-
 .../exp/gltf2_blender_gather_texture.py       |  17 +-
 .../exp/gltf2_blender_gather_texture_info.py  |  44 +-
 .../blender/exp/gltf2_blender_gather_tree.py  |  17 +
 .../blender/exp/gltf2_blender_get.py          |  41 +-
 .../blender/exp/gltf2_blender_image.py        |  46 +-
 .../exp/gltf2_blender_texture_specular.py     |  94 ++++
 .../imp/gltf2_blender_KHR_materials_ior.py    |  12 +
 ...der_KHR_materials_pbrSpecularGlossiness.py |  17 +-
 .../imp/gltf2_blender_KHR_materials_sheen.py  |  88 ++++
 .../gltf2_blender_KHR_materials_specular.py   | 350 +++++++++++++
 ...ltf2_blender_KHR_materials_transmission.py |  64 +++
 .../imp/gltf2_blender_KHR_materials_unlit.py  |   5 +-
 .../imp/gltf2_blender_KHR_materials_volume.py |  82 +++
 .../blender/imp/gltf2_blender_gltf.py         |  28 ++
 .../blender/imp/gltf2_blender_material.py     |   5 +
 .../blender/imp/gltf2_blender_mesh.py         |  56 ++-
 .../imp/gltf2_blender_pbrMetallicRoughness.py | 238 ++++++++-
 .../blender/imp/gltf2_blender_texture.py      |  19 +-
 io_scene_gltf2/io/com/gltf2_io_constants.py   |   2 +
 io_scene_gltf2/io/com/gltf2_io_variants.py    |  29 ++
 io_scene_gltf2/io/imp/gltf2_io_gltf.py        |   9 +-
 40 files changed, 2558 insertions(+), 290 deletions(-)
 create mode 100644 io_scene_gltf2/blender/com/gltf2_blender_default.py
 create mode 100644 io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_clearcoat.py
 create mode 100644 io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_emission.py
 create mode 100644 io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_ior.py
 create mode 100644 io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_sheen.py
 create mode 100644 io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_specular.py
 create mode 100644 io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_transmission.py
 create mode 100644 io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_variants.py
 create mode 100644 io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_volume.py
 create mode 100644 io_scene_gltf2/blender/exp/gltf2_blender_texture_specular.py
 create mode 100644 io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_ior.py
 create mode 100644 io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_sheen.py
 create mode 100644 io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_specular.py
 create mode 100644 io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_transmission.py
 create mode 100644 io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_volume.py
 create mode 100644 io_scene_gltf2/io/com/gltf2_io_variants.py

diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py
index bf23d0171..358759fa0 100755
--- a/io_scene_gltf2/__init__.py
+++ b/io_scene_gltf2/__init__.py
@@ -4,7 +4,7 @@
 bl_info = {
     'name': 'glTF 2.0 format',
     'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
-    "version": (3, 3, 12),
+    "version": (3, 3, 13),
     'blender': (3, 3, 0),
     'location': 'File > Import-Export',
     'description': 'Import-Export as glTF 2.0',
@@ -259,6 +259,14 @@ class ExportGLTF2_Base:
         default='EXPORT'
     )
 
+    export_original_specular: BoolProperty(
+        name='Export original PBR Specular',
+        description=(
+            'Export original glTF PBR Specular, instead of Blender Principled Shader Specular'
+        ),
+        default=False,
+    )
+
     export_colors: BoolProperty(
         name='Vertex Colors',
         description='Export vertex colors with meshes',
@@ -447,13 +455,6 @@ class ExportGLTF2_Base:
         default=False
     )
 
-    export_displacement: BoolProperty(
-        name='Displacement Textures (EXPERIMENTAL)',
-        description='EXPERIMENTAL: Export displacement textures. '
-                    'Uses incomplete "KHR_materials_displacement" glTF extension',
-        default=False
-    )
-
     will_save_settings: BoolProperty(
         name='Remember Export Settings',
         description='Store glTF export settings in the Blender project',
@@ -564,6 +565,7 @@ class ExportGLTF2_Base:
         export_settings['gltf_colors'] = self.export_colors
         export_settings['gltf_cameras'] = self.export_cameras
 
+        export_settings['gltf_original_specular'] = self.export_original_specular
 
         export_settings['gltf_visible'] = self.use_visible
         export_settings['gltf_renderable'] = self.use_renderable
@@ -611,7 +613,6 @@ class ExportGLTF2_Base:
             export_settings['gltf_morph_tangent'] = False
 
         export_settings['gltf_lights'] = self.export_lights
-        export_settings['gltf_displacement'] = self.export_displacement
 
         export_settings['gltf_binary'] = bytearray()
         export_settings['gltf_binaryfilename'] = (
@@ -756,6 +757,22 @@ class GLTF_PT_export_geometry(bpy.types.Panel):
 
         return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
 
+    def draw(self, context):
+        pass
+
+class GLTF_PT_export_geometry_mesh(bpy.types.Panel):
+    bl_space_type = 'FILE_BROWSER'
+    bl_region_type = 'TOOL_PROPS'
+    bl_label = "Mesh"
+    bl_parent_id = "GLTF_PT_export_geometry"
+    bl_options = {'DEFAULT_CLOSED'}
+
+    @classmethod
+    def poll(cls, context):
+        sfile = context.space_data
+        operator = sfile.active_operator
+        return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
+
     def draw(self, context):
         layout = self.layout
         layout.use_property_split = True
@@ -776,11 +793,56 @@ class GLTF_PT_export_geometry(bpy.types.Panel):
         col.prop(operator, 'use_mesh_edges')
         col.prop(operator, 'use_mesh_vertices')
 
+
+class GLTF_PT_export_geometry_material(bpy.types.Panel):
+    bl_space_type = 'FILE_BROWSER'
+    bl_region_type = 'TOOL_PROPS'
+    bl_label = "Material"
+    bl_parent_id = "GLTF_PT_export_geometry"
+    bl_options = {'DEFAULT_CLOSED'}
+
+    @classmethod
+    def poll(cls, context):
+        sfile = context.space_data
+        operator = sfile.active_operator
+        return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
+
+    def draw(self, context):
+        layout = self.layout
+        layout.use_property_split = True
+        layout.use_property_decorate = False  # No animation.
+
+        sfile = context.space_data
+        operator = sfile.active_operator
+
         layout.prop(operator, 'export_materials')
         col = layout.column()
         col.active = operator.export_materials == "EXPORT"
         col.prop(operator, 'export_image_format')
 
+class GLTF_PT_export_geometry_original_pbr(bpy.types.Panel):
+    bl_space_type = 'FILE_BROWSER'
+    bl_region_type = 'TOOL_PROPS'
+    bl_label = "PBR Extensions"
+    bl_parent_id = "GLTF_PT_export_geometry_material"
+    bl_options = {'DEFAULT_CLOSED'}
+
+    @classmethod
+    def poll(cls, context):
+        sfile = context.space_data
+        operator = sfile.active_operator
+        return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
+
+    def draw(self, context):
+        layout = self.layout
+        layout.use_property_split = True
+        layout.use_property_decorate = False  # No animation.
+
+        sfile = context.space_data
+        operator = sfile.active_operator
+
+        layout.prop(operator, 'export_original_specular')
+
 
 class GLTF_PT_export_geometry_compression(bpy.types.Panel):
     bl_space_type = 'FILE_BROWSER'
@@ -1182,6 +1244,14 @@ class ImportGLTF2(Operator, ImportHelper):
             self.loglevel = logging.NOTSET
 
 
+def gltf_variant_ui_update(self, context):
+    from .blender.com.gltf2_blender_ui import variant_register, variant_unregister
+    if self.KHR_materials_variants_ui is True:
+        # register all needed types
+        variant_register()
+    else:
+        variant_unregister()
+
 class GLTF_AddonPreferences(bpy.types.AddonPreferences):
     bl_idname = __package__
 
@@ -1190,11 +1260,18 @@ class GLTF_AddonPreferences(bpy.types.AddonPreferences):
             description="Displays glTF Settings node in Shader Editor (Menu Add > Output)"
             )
 
+    KHR_materials_variants_ui : bpy.props.BoolProperty(
+        default= False,
+        description="Displays glTF UI to manage material variants",
+        update=gltf_variant_ui_update
+        )
+
 
     def draw(self, context):
         layout = self.layout
         row = layout.row()
         row.prop(self, "settings_node_ui", text="Shader Editor Add-ons")
+        row.prop(self, "KHR_materials_variants_ui", text="Material Variants")
 
 def menu_func_import(self, context):
     self.layout.operator(ImportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')
@@ -1206,6 +1283,9 @@ classes = (
     GLTF_PT_export_include,
     GLTF_PT_export_transform,
     GLTF_PT_export_geometry,
+    GLTF_PT_export_geometry_mesh,
+    GLTF_PT_export_geometry_material,
+    GLTF_PT_export_geometry_original_pbr,
     GLTF_PT_export_geometry_compression,
     GLTF_PT_export_animation,
     GLTF_PT_export_animation_export,
@@ -1225,6 +1305,8 @@ def register():
     # bpy.utils.register_module(__name__)
 
     blender_ui.register()
+    if bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is True:
+        blender_ui.variant_register()
 
     # add to the export / import menu
     bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
@@ -1233,6 +1315,10 @@ def register():
 
 def unregister():
     import io_scene_gltf2.blender.com.gltf2_blender_ui as blender_ui
+    blender_ui.unregister()
+    if bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is True:
+        blender_ui.variant_unregister()
+
     for c in classes:
         bpy.utils.unregister_class(c)
     for f in exporter_extension_panel_unregister_functors:
@@ -1243,8 +1329,6 @@ def unregister():
         f()
     importer_extension_panel_unregister_functors.clear()
 
-    blender_ui.unregister()
-
     # bpy.utils.unregister_module(__name__)
 
     # remove from the export / import menu
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_default.py b/io_scene_gltf2/blender/com/gltf2_blender_default.py
new file mode 100644
index 000000000..c3951f4e0
--- /dev/null
+++ b/io_scene_gltf2/blender/com/gltf2_blender_default.py
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+BLENDER_IOR = 1.45
+BLENDER_SPECULAR = 0.5
+BLENDER_SPECULAR_TINT = 0.0
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py b/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py
index 7b90b0a38..a44562a1d 100755
--- a/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py
+++ b/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py
@@ -9,7 +9,25 @@ def get_gltf_node_name():
 def create_settings_group(name):
     gltf_node_group = bpy.data.node_groups.new(name, 'ShaderNodeTree')
     gltf_node_group.inputs.new("NodeSocketFloat", "Occlusion")
+    thicknessFactor  = gltf_node_group.inputs.new("NodeSocketFloat", "Thickness")
+    thicknessFactor.default_value = 1.0
     gltf_node_group.nodes.new('NodeGroupOutput')
     gltf_node_group_input = gltf_node_group.nodes.new('NodeGroupInput')
     gltf_node_group_input.location = -200, 0
-    return gltf_node_group
\ No newline at end of file
+    return gltf_node_group
+
+def get_gltf_pbr_non_converted_name():
+    return "original glTF PBR data"
+
+def create_gltf_pbr_non_converted_group(name):
+    gltf_node_group = bpy.data.node_groups.new(name, 'ShaderNodeTree')
+
+    specular = gltf_node_group.inputs.new("NodeSocketFloat", "specular glTF")
+    specular.default_value = 1.0
+    specularColor = gltf_node_group.inputs.new("NodeSocketColor", "specularColor glTF")
+    specularColor.default_value = [1.0,1.0,1.0,1.0]
+
+    gltf_node_group.nodes.new('NodeGroupOutput')
+    gltf_node_group_input = gltf_node_group.nodes.new('NodeGroupInput')
+    gltf_node_group_input.location = -400, 0
+    return gltf_node_group    
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_ui.py b/io_scene_gltf2/blender/com/gltf2_blender_ui.py
index 59c364fb5..7895d3d47 100644
--- a/io_scene_gltf2/blender/com/gltf2_blender_ui.py
+++ b/io_scene_gltf2/blender/com/gltf2_blender_ui.py
@@ -3,6 +3,9 @@
 
 import bpy
 from ..com.gltf2_blender_material_helpers import get_gltf_node_name, create_settings_group
+from ..com.gltf2_blender_material_helpers import get_gltf_pbr_non_converted_name, create_gltf_pbr_non_converted_group
+
+################ glTF Settings node ###########################################
 
 def create_gltf_ao_group(operator, group_name):
 
@@ -40,10 +43,479 @@ def add_gltf_settings_to_menu(self, context) :
     if bpy.context.preferences.addons['io_scene_gltf2'].preferences.settings_node_ui is True:
         self.layout.operator("node.gltf_settings_node_operator")
 
+class NODE_OT_GLTF_PBR_NON_CONVERTED_EXTENSIONS(bpy.types.Operator):
+    bl_idname = "node.gltf_pbr_non_converted_extensions_operator"
+    bl_label  = "glTF PBR Non Converted Extensions"
+
+
+    @classmethod
+    def poll(cls, context):
+        space = context.space_data
+        return space.type == "NODE_EDITOR" \
+            and context.object and context.object.active_material \
+            and context.object.active_material.use_nodes is True \
+            and bpy.context.preferences.addons['io_scene_gltf2'].preferences.settings_node_ui is True
+
+    def execute(self, context):
+        gltf_node_name = get_gltf_pbr_non_converted_name()
+        if gltf_node_name in bpy.data.node_groups:
+            my_group = bpy.data.node_groups[get_gltf_pbr_non_converted_name()]
+        else:
+            my_group = create_gltf_pbr_non_converted_group(gltf_node_name)
+        node_tree = context.object.active_material.node_tree
+        new_node = node_tree.nodes.new("ShaderNodeGroup")
+        new_node.node_tree = bpy.data.node_groups[my_group.name]
+        return {"FINISHED"}
+
+
+def add_gltf_pbr_non_converted_extensions_to_menu(self, context) :
+    if bpy.context.preferences.addons['io_scene_gltf2'].preferences.settings_node_ui is True:
+        self.layout.operator("node.gltf_pbr_non_converted_extensions_operator")
+
+
+################################### KHR_materials_variants ####################
+
+# Global UI panel
+
+class gltf2_KHR_materials_variants_variant(bpy.types.PropertyGroup):
+    variant_idx : bpy.props.IntProperty()
+    name : bpy.props.StringProperty(name="Variant Name")
+
+class SCENE_UL_gltf2_variants(bpy.types.UIList):
+    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
+
+        if self.layout_type in {'DEFAULT', 'COMPACT'}:
+            layout.prop(item, "name", text="", emboss=False)
+
+        elif self.layout_type in {'GRID'}:
+            layout.alignment = 'CENTER'
+
+class SCENE_PT_gltf2_variants(bpy.types.Panel):
+    bl_label = "glTF Material Variants"
+    bl_space_type = 'VIEW_3D'
+    bl_region_type = 'UI'
+    bl_category = "glTF Variants"
+
+    @classmethod
+    def poll(self, context):
+        return bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is True
+
+    def draw(self, context):
+        layout = self.layout
+        row = layout.row()
+
+        if bpy.data.scenes[0].get('gltf2_KHR_materials_variants_variants') and len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) > 0:
+
+            row.template_list("SCENE_UL_gltf2_variants", "", bpy.data.scenes[0], "gltf2_KHR_materials_variants_variants", bpy.data.scenes[0], "gltf2_active_variant")
+            col = row.column()
+            row = col.column(align=True)
+            row.operator("scene.gltf2_variant_add", icon="ADD", text="")
+            row.operator("scene.gltf2_variant_remove", icon="REMOVE", text="")
+
+            row = layout.row()
+            row.operator("scene.gltf2_display_variant", text="Display Variant")
+            row = layout.row()
+            row.operator("scene.gltf2_assign_to_variant", text="Assign To Variant")
+            row = layout.row()
+            row.operator("scene.gltf2_reset_to_original", text="Reset To Original")
+            row.operator("scene.gltf2_assign_as_original", text="Assign as Original")
+        else:
+            row.operator("scene.gltf2_variant_add", text="Add Material Variant")
+
+class SCENE_OT_gltf2_variant_add(bpy.types.Operator):
+    """Add a new Material Variant"""
+    bl_idname = "scene.gltf2_variant_add"
+    bl_label = "Add Material Variant"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return True
+
+    def execute(self, context):
+        var = bpy.data.scenes[0].gltf2_KHR_materials_variants_variants.add()
+        var.variant_idx = len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) - 1
+        var.name = "VariantName"
+        bpy.data.scenes[0].gltf2_active_variant = len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) - 1
+        return {'FINISHED'}
+
+class SCENE_OT_gltf2_variant_remove(bpy.types.Operator):
+    """Add a new Material Variant"""
+    bl_idname = "scene.gltf2_variant_remove"
+    bl_label = "Remove Variant"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) > 0
+
+    def execute(self, context):
+        bpy.data.scenes[0].gltf2_KHR_materials_variants_variants.remove(bpy.data.scenes[0].gltf2_active_variant)
+
+        # loop on all mesh
+        for obj in [o for o in bpy.data.objects if o.type == "MESH"]:
+            mesh = obj.data
+            remove_idx_data = []
+            for idx, i in enumerate(mesh.gltf2_variant_mesh_data):
+                remove_idx_variants = []
+                for idx_var, v in enumerate(i.variants):
+                    if v.variant.variant_idx == bpy.data.scenes[0].gltf2_active_variant:
+                        remove_idx_variants.append(idx_var)
+                    elif v.variant.variant_idx > bpy.data.scenes[0].gltf2_active_variant:
+                        v.variant.variant_idx -= 1
+
+                if len(remove_idx_variants) > 0:
+                    for idx_var in remove_idx_variants:
+                        i.variants.remove(idx_var)
+
+                if len(i.variants) == 0:
+                    remove_idx_data.append(idx)
+
+            if len(remove_idx_data) > 0:
+                for idx_data in remove_idx_data:
+                    mesh.gltf2_variant_mesh_data.remove(idx_data)
+                
+        return {'FINISHED'}    
+
+
+# Operator to display a variant
+class SCENE_OT_gltf2_display_variant(bpy.types.Operator):
+    bl_idname = "scene.gltf2_display_variant"
+    bl_label = "Display Variant"
+    bl_options = {'REGISTER'}
+
+
+    @classmethod
+    def poll(self, context):
+        return len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) > 0
+
+    def execute(self, context):
+
+        gltf2_active_variant = bpy.data.scenes[0].gltf2_active_variant
+
+        # loop on all mesh
+        for obj in [o for o in bpy.data.objects if o.type == "MESH"]:
+            mesh = obj.data
+            for i in mesh.gltf2_variant_mesh_data:
+                if i.variants and gltf2_active_variant in [v.variant.variant_idx for v in i.variants]:
+                    mat = i.material
+                    slot = i.material_slot_index
+                    if slot < len(obj.material_slots): # Seems user remove some slots...
+                        obj.material_slots[slot].material = mat
+
+        return {'FINISHED'}
+
+# Operator to assign current mesh materials to a variant
+class SCENE_OT_gltf2_assign_to_variant(bpy.types.Operator):
+    bl_idname = "scene.gltf2_assign_to_variant"
+    bl_label = "Assign To Variant"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) > 0 \
+            and bpy.context.object.type == "MESH"
+
+    def execute(self, context):
+        gltf2_active_variant = bpy.data.scenes[0].gltf2_active_variant
+        obj = bpy.context.object
+
+        # loop on material slots ( primitives )
+        for mat_slot_idx, s in enumerate(obj.material_slots):
+            # Check if there is already data for this slot
+            found = False
+            for i in obj.data.gltf2_variant_mesh_data:
+                if i.material_slot_index == mat_slot_idx and i.material == s.material:
+                    found = True
+                    variant_primitive = i
+
+            if found is False:
+                variant_primitive = obj.data.gltf2_variant_mesh_data.add()
+                variant_primitive.material_slot_index = mat_slot_idx
+                variant_primitive.material = s.material
+
+            vari = variant_primitive.variants.add()
+            vari.variant.variant_idx = bpy.data.scenes[0].gltf2_active_variant
+
+        return {'FINISHED'}
+
+# Operator to reset mesh to orignal (using default material when exists)
+class SCENE_OT_gltf2_reset_to_original(bpy.types.Operator):
+    bl_idname = "scene.gltf2_reset_to_original"
+    bl_label = "Reset to Orignal"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return bpy.context.object.type == "MESH" and len(context.object.data.gltf2_variant_default_materials) > 0
+
+    def execute(self, context):
+        obj = bpy.context.object
+
+        # loop on material slots ( primitives )
+        for mat_slot_idx, s in enumerate(obj.material_slots):
+            # Check if there is a default material for this slot
+            found = False
+            for i in obj.data.gltf2_variant_default_materials:
+                if i.material_slot_index == mat_slot_idx:
+                    s.material = i.default_material
+                    break
+
+        return {'FINISHED'}
+
+# Operator to assign current materials as default materials
+class SCENE_OT_gltf2_assign_as_original(bpy.types.Operator):
+    bl_idname = "scene.gltf2_assign_as_original"
+    bl_label = "Assign as Original"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return bpy.context.object.type == "MESH"
+
+    def execute(self, context):
+        obj = bpy.context.object
+
+        # loop on material slots ( primitives )
+        for mat_slot_idx, s in enumerate(obj.material_slots):
+            # Check if there is a default material for this slot
+            found = False
+            for i in obj.data.gltf2_variant_default_materials:
+                if i.material_slot_index == mat_slot_idx:
+                    found = True
+                    # Update if needed
+                    i.default_material = s.material
+                    break
+
+            if found is False:
+                default_mat = obj.data.gltf2_variant_default_materials.add()
+                default_mat.material_slot_index = mat_slot_idx
+                default_mat.default_material = s.material
+
+        return {'FINISHED'}
+
+# Mesh Panel
+
+class gltf2_KHR_materials_variant_pointer(bpy.types.PropertyGroup):
+    variant: bpy.props.PointerProperty(type=gltf2_KHR_materials_variants_variant)
+
+class gltf2_KHR_materials_variants_default_material(bpy.types.PropertyGroup):
+    material_slot_index: bpy.props.IntProperty(name="Material Slot Index")
+    default_material: bpy.props.PointerProperty(type=bpy.types.Material)
+
+class gltf2_KHR_materials_variants_primitive(bpy.types.PropertyGroup):
+    material_slot_index : bpy.props.IntProperty(name="Material Slot Index")
+    material: bpy.props.PointerProperty(type=bpy.types.Material)
+    variants: bpy.props.CollectionProperty(type=gltf2_KHR_materials_variant_pointer)
+    active_variant_idx: bpy.props.IntProperty()
+
+class MESH_UL_gltf2_mesh_variants(bpy.types.UIList):
+    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
+
+        vari = item.variant
+        layout.context_pointer_set("id", vari)
+
+        if self.layout_type in {'DEFAULT', 'COMPACT'}:
+            layout.prop(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants[vari.variant_idx], "name", text="", emboss=False)
+        elif self.layout_type in {'GRID'}:
+            layout.alignment = 'CENTER'
+
+class MESH_PT_gltf2_mesh_variants(bpy.types.Panel):
+    bl_label = "glTF Material Variants"
+    bl_space_type = 'PROPERTIES'
+    bl_region_type = 'WINDOW'
+    bl_context = "material"
+
+    @classmethod
+    def poll(self, context):
+        return bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is True \
+            and len(bpy.context.object.material_slots) > 0
+
+    def draw(self, context):
+        layout = self.layout
+
+        active_material_slots = bpy.context.object.active_material_index
+
+        found = False
+        if 'gltf2_variant_mesh_data' in bpy.context.object.data.keys():
+            for idx, prim in enumerate(bpy.context.object.data.gltf2_variant_mesh_data):
+                if prim.material_slot_index == active_material_slots and id(prim.material) == id(bpy.context.object.material_slots[active_material_slots].material):
+                    found = True
+                    break
+
+        row = layout.row()
+        if found is True:
+            row.template_list("MESH_UL_gltf2_mesh_variants", "", prim, "variants", prim, "active_variant_idx")
+            col = row.column()
+            row = col.column(align=True)
+            row.operator("scene.gltf2_variants_slot_add", icon="ADD", text="")
+            row.operator("scene.gltf2_remove_material_variant", icon="REMOVE", text="")
+
+            row = layout.row()
+            if 'gltf2_KHR_materials_variants_variants' in bpy.data.scenes[0].keys() and len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) > 0:
+                row.prop_search(context.object.data, "gltf2_variant_pointer", bpy.data.scenes[0], "gltf2_KHR_materials_variants_variants", text="Variant")
+                row = layout.row()
+                row.operator("scene.gltf2_material_to_variant", text="Assign To Variant")
+            else:
+                row.label(text="Please Create a Variant First")
+        else:
+            if 'gltf2_KHR_materials_variants_variants' in bpy.data.scenes[0].keys() and len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) > 0:
+                row.operator("scene.gltf2_variants_slot_add", text="Add a new Variant Slot")
+            else:
+                row.label(text="Please Create a Variant First")
+
+
+class SCENE_OT_gltf2_variant_slot_add(bpy.types.Operator):
+    """Add a new Slot"""
+    bl_idname = "scene.gltf2_variants_slot_add"
+    bl_label = "Add new Slot"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return len(bpy.context.object.material_slots) > 0
+
+    def execute(self, context):
+        mesh = context.object.data
+        # Check if there is already a data for this slot_idx + material
+
+        found = False
+        for i in mesh.gltf2_variant_mesh_data:
+            if i.material_slot_index == context.object.active_material_index and i.material == context.object.material_slots[context.object.active_material_index].material:
+                found = True
+                variant_primitive = i
+
+        if found is False:
+            variant_primitive = mesh.gltf2_variant_mesh_data.add()
+            variant_primitive.material_slot_index = context.object.active_material_index
+            variant_primitive.material = context.object.material_slots[context.object.active_material_index].material
+
+        vari = variant_primitive.variants.add()
+        vari.variant.variant_idx = bpy.data.scenes[0].gltf2_active_variant
+
+        return {'FINISHED'}
+
+class SCENE_OT_gltf2_material_to_variant(bpy.types.Operator):
+    """Assign Variant to Slot"""
+    bl_idname = "scene.gltf2_material_to_variant"
+    bl_label = "Assign Material To Variant"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return len(bpy.context.object.material_slots) > 0 and context.object.data.gltf2_variant_pointer != ""
+
+    def execute(self, context):
+        mesh = context.object.data
+
+        found = False
+        for i in mesh.gltf2_variant_mesh_data:
+            if i.material_slot_index == context.object.active_material_index and i.material == context.object.material_slots[context.object.active_material_index].material:
+                found = True
+                variant_primitive = i
+
+        if found is False:
+            return {'CANCELLED'}
+
+        vari = variant_primitive.variants[variant_primitive.active_variant_idx]
+
+        # Retrieve variant idx
+        found = False
+        for v in bpy.data.scenes[0].gltf2_KHR_materials_variants_variants:
+            if v.name == context.object.data.gltf2_variant_pointer:
+                found = True
+                break
+
+        if found is False:
+            return {'CANCELLED'}
+
+        vari.variant.variant_idx = v.variant_idx
+
+        return {'FINISHED'}
+
+class SCENE_OT_gltf2_remove_material_variant(bpy.types.Operator):
+    """Remove a variant Slot"""
+    bl_idname = "scene.gltf2_remove_material_variant"
+    bl_label = "Remove a variant Slot"
+    bl_options = {'REGISTER'}
+
+    @classmethod
+    def poll(self, context):
+        return len(bpy.context.object.material_slots) > 0 and len(bpy.context.object.data.gltf2_variant_mesh_data) > 0
+
+    def execute(self, context):
+        mesh = context.object.data
+
+        found = False
+        found_idx = -1
+        for idx, i in enumerate(mesh.gltf2_variant_mesh_data):
+            if i.material_slot_index == context.object.active_material_index and i.material == context.object.material_slots[context.object.active_material_index].material:
+                found = True
+                variant_primitive = i
+                found_idx = idx
+
+        if found is False:
+            return {'CANCELLED'}
+
+        variant_primitive.variants.remove(variant_primitive.active_variant_idx)
+
+        if len(variant_primitive.variants) == 0:
+            mesh.gltf2_variant_mesh_data.remove(found_idx)
+
+        return {'FINISHED'}
+
+
+###############################################################################
 
 def register():
     bpy.utils.register_class(NODE_OT_GLTF_SETTINGS)
+    bpy.utils.register_class(NODE_OT_GLTF_PBR_NON_CONVERTED_EXTENSIONS)
     bpy.types.NODE_MT_category_SH_NEW_OUTPUT.append(add_gltf_settings_to_menu)
+    bpy.types.NODE_MT_category_SH_NEW_OUTPUT.append(add_gltf_pbr_non_converted_extensions_to_menu)
+
+def variant_register():
+    bpy.utils.register_class(SCENE_OT_gltf2_display_variant)
+    bpy.utils.register_class(SCENE_OT_gltf2_assign_to_variant)
+    bpy.utils.register_class(SCENE_OT_gltf2_reset_to_original)
+    bpy.utils.register_class(SCENE_OT_gltf2_assign_as_original)
+    bpy.utils.register_class(SCENE_OT_gltf2_remove_material_variant)
+    bpy.utils.register_class(gltf2_KHR_materials_variants_variant)
+    bpy.utils.register_class(gltf2_KHR_materials_variant_pointer)
+    bpy.utils.register_class(gltf2_KHR_materials_variants_primitive)
+    bpy.utils.register_class(gltf2_KHR_materials_variants_default_material)
+    bpy.utils.register_class(SCENE_UL_gltf2_variants)
+    bpy.utils.register_class(SCENE_PT_gltf2_variants)
+    bpy.utils.register_class(MESH_UL_gltf2_mesh_variants)
+    bpy.utils.register_class(MESH_PT_gltf2_mesh_variants)
+    bpy.utils.register_class(SCENE_OT_gltf2_variant_add)
+    bpy.utils.register_class(SCENE_OT_gltf2_variant_remove)
+    bpy.utils.register_class(SCENE_OT_gltf2_material_to_variant)
+    bpy.utils.register_class(SCENE_OT_gltf2_variant_slot_add)
+    bpy.types.Mesh.gltf2_variant_mesh_data = bpy.props.CollectionProperty(type=gltf2_KHR_materials_variants_primitive)
+    bpy.types.Mesh.gltf2_variant_default_materials = bpy.props.CollectionProperty(type=gltf2_KHR_materials_variants_default_material)
+    bpy.types.Mesh.gltf2_variant_pointer = bpy.props.StringProperty()
+    bpy.types.Scene.gltf2_KHR_materials_variants_variants = bpy.props.CollectionProperty(type=gltf2_KHR_materials_variants_variant)
+    bpy.types.Scene.gltf2_active_variant = bpy.props.IntProperty()
 
 def unregister():
     bpy.utils.unregister_class(NODE_OT_GLTF_SETTINGS)
+    bpy.utils.unregister_class(NODE_OT_GLTF_PBR_NON_CONVERTED_EXTENSIONS)
+
+def variant_unregister():
+    bpy.utils.unregister_class(SCENE_OT_gltf2_variant_add)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_variant_remove)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_material_to_variant)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_variant_slot_add)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_display_variant)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_assign_to_variant)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_reset_to_original)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_assign_as_original)
+    bpy.utils.unregister_class(SCENE_OT_gltf2_remove_material_variant)
+    bpy.utils.unregister_class(SCENE_PT_gltf2_variants)
+    bpy.utils.unregister_class(SCENE_UL_gltf2_variants)
+    bpy.utils.unregister_class(MESH_PT_gltf2_mesh_variants)
+    bpy.utils.unregister_class(MESH_UL_gltf2_mesh_variants)
+    bpy.utils.unregister_class(gltf2_KHR_materials_variants_default_material)
+    bpy.utils.unregister_class(gltf2_KHR_materials_variants_primitive)
+    bpy.utils.unregister_class(gltf2_KHR_materials_variants_variant)
+    bpy.utils.unregister_class(gltf2_KHR_materials_variant_pointer)
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py b/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
index 812db3f99..96f97af14 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
@@ -20,7 +20,6 @@ RENDERABLE = 'gltf_renderable'
 ACTIVE_COLLECTION = 'gltf_active_collection'
 SKINS = 'gltf_skins'
 DEF_BONES_ONLY = 'gltf_def_bones'
-DISPLACEMENT = 'gltf_displacement'
 FORCE_SAMPLING = 'gltf_force_sampling'
 FRAME_RANGE = 'gltf_frame_range'
 FRAME_STEP = 'gltf_frame_step'
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
index 4eb8baed4..721ef1157 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
@@ -59,6 +59,8 @@ def __gather_scene(blender_scene, export_settings):
     # Now, we can filter tree if needed
     vtree.filter()
 
+    vtree.variants_reset_to_original()
+
     export_user_extensions('vtree_after_filter_hook', export_settings, vtree)
 
     export_settings['vtree'] = vtree
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
index 0dfce9f9b..69900c1be 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
@@ -11,7 +11,7 @@ from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
 from io_scene_gltf2.io.exp import gltf2_io_binary_data
 from io_scene_gltf2.io.exp import gltf2_io_image_data
 from io_scene_gltf2.io.com import gltf2_io_debug
-from io_scene_gltf2.blender.exp.gltf2_blender_image import Channel, ExportImage, FillImage
+from io_scene_gltf2.blender.exp.gltf2_blender_image import Channel, ExportImage, FillImage, StoreImage, StoreData
 from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
 from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
 
@@ -21,26 +21,31 @@ def gather_image(
         blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
         export_settings):
     if not __filter_image(blender_shader_sockets, export_settings):
-        return None
+        return None, None
 
     image_data = __get_image_data(blender_shader_sockets, export_settings)
     if image_data.empty():
         # The export image has no data
-        return None
+        return None, None
 
     mime_type = __gather_mime_type(blender_shader_sockets, image_data, export_settings)
     name = __gather_name(image_data, export_settings)
 
+    factor = None
+
     if image_data.original is None:
-        uri = __gather_uri(image_data, mime_type, name, export_settings)
+        uri, factor_uri = __gather_uri(image_data, mime_type, name, export_settings)
     else:
         # Retrieve URI relative to exported glTF files
         uri = __gather_original_uri(image_data.original.filepath, export_settings)
         # In case we can't retrieve image (for example packed images, with original moved)
         # We don't create invalid image without uri
+        factor_uri = None
         if uri is None: return None
 
-    buffer_view = __gather_buffer_view(image_data, mime_type, name, export_settings)
+    buffer_view, factor_buffer_view = __gather_buffer_view(image_data, mime_type, name, export_settings)
+
+    factor = factor_uri if uri is not None else factor_buffer_view
 
     image = __make_image(
         buffer_view,
@@ -54,7 +59,7 @@ def gather_image(
 
     export_user_extensions('gather_image_hook', export_settings, image, blender_shader_sockets)
 
-    return image
+    return image, factor
 
 def __gather_original_uri(original_uri, export_settings):
 
@@ -98,8 +103,9 @@ def __filter_image(sockets, export_settings):
 @cached
 def __gather_buffer_view(image_data, mime_type, name, export_settings):
     if export_settings[gltf2_blender_export_keys.FORMAT] != 'GLTF_SEPARATE':
-        return gltf2_io_binary_data.BinaryData(data=image_data.encode(mime_type))
-    return None
+        data, factor = image_data.encode(mime_type)
+        return gltf2_io_binary_data.BinaryData(data=data), factor
+    return None, None
 
 
 def __gather_extensions(sockets, export_settings):
@@ -165,13 +171,14 @@ def __gather_name(export_image, export_settings):
 def __gather_uri(image_data, mime_type, name, export_settings):
     if export_settings[gltf2_blender_export_keys.FORMAT] == 'GLTF_SEPARATE':
         # as usual we just store the data in place instead of already resolving the references
+        data, factor = image_data.encode(mime_type=mime_type)
         return gltf2_io_image_data.ImageData(
-            data=image_data.encode(mime_type=mime_type),
+            data=data,
             mime_type=mime_type,
             name=name
-        )
+        ), factor
 
-    return None
+    return None, None
 
 
 def __get_image_data(sockets, export_settings) -> ExportImage:
@@ -179,6 +186,18 @@ def __get_image_data(sockets, export_settings) -> ExportImage:
     # in a helper class. During generation of the glTF in the exporter these will then be combined to actual binary
     # resources.
     results = [__get_tex_from_socket(socket, export_settings) for socket in sockets]
+
+    # Check if we need a simple mapping or more complex calculation
+    if any([socket.name == "Specular" for socket in sockets]):
+        return __get_image_data_specular(sockets, results, export_settings)
+    else:
+        return __get_image_data_mapping(sockets, results, export_settings)
+    
+def __get_image_data_mapping(sockets, results, export_settings) -> ExportImage:
+    """
+    Simple mapping
+    Will fit for most of exported textures : RoughnessMetallic, Basecolor, normal, ...
+    """
     composed_image = ExportImage()
     for result, socket in zip(results, sockets):
         # Assume that user know what he does, and that channels/images are already combined correctly for pbr
@@ -217,6 +236,12 @@ def __get_image_data(sockets, export_settings) -> ExportImage:
                 dst_chan = Channel.R
             elif socket.name == 'Clearcoat Roughness':
                 dst_chan = Channel.G
+            elif socket.name == 'Thickness': # For KHR_materials_volume
+                dst_chan = Channel.G
+            elif socket.name == "specular glTF": # For original KHR_material_specular
+                dst_chan = Channel.A
+            elif socket.name == "Sigma": # For KHR_materials_sheen
+                dst_chan = Channel.A
 
             if dst_chan is not None:
                 composed_image.fill_image(result.shader_node.image, dst_chan, src_chan)
@@ -243,6 +268,54 @@ def __get_image_data(sockets, export_settings) -> ExportImage:
     return composed_image
 
 
+def __get_image_data_specular(sockets, results, export_settings) -> ExportImage:
+    """
+    calculating Specular Texture, settings needed data
+    """   
+    from io_scene_gltf2.blender.exp.gltf2_blender_texture_specular import specular_calculation
+    composed_image = ExportImage()
+    composed_image.set_calc(specular_calculation)
+
+    composed_image.store_data("ior", sockets[4].default_value, type="Data")
+
+    results = [__get_tex_from_socket(socket, export_settings) for socket in sockets[:-1]] #Do not retrieve IOR --> No texture allowed
+
+    mapping = {
+        0: "specular",
+        1: "specular_tint",
+        2: "base_color",
+        3: "transmission"
+    }
+
+    for idx, result in enumerate(results):
+        if __get_tex_from_socket(sockets[idx], export_settings):
+
+            composed_image.store_data(mapping[idx], result.shader_node.image, type="Image")
+
+            # rudimentarily try follow the node tree to find the correct image data.
+            src_chan = None if idx == 2 else Channel.R
+            for elem in result.path:
+                if isinstance(elem.from_node, bpy.types.ShaderNodeSeparateColor):
+                    src_chan = {
+                        'Red': Channel.R,
+                        'Green': Channel.G,
+                        'Blue': Channel.B,
+                    }[elem.from_socket.name]
+                if elem.from_socket.name == 'Alpha':
+                    src_chan = Channel.A
+            # For base_color, keep all channels, as this is a Vec, not scalar
+            if idx != 2:
+                composed_image.store_data(mapping[idx] + "_channel", src_chan, type="Data")
+            else:
+                if src_chan is not None:
+                    composed_image.store_data(mapping[idx] + "_channel", src_chan, type="Data")
+
+        else:
+            composed_image.store_data(mapping[idx], sockets[idx].default_value, type="Data")
+
+    return composed_image
+
+# TODOExt deduplicate
 @cached
 def __get_tex_from_socket(blender_shader_socket: bpy.types.NodeSocket, export_settings):
     result = gltf2_blender_search_node_tree.from_socket(
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
index 56c3acff3..c0d17fd38 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
@@ -1,21 +1,27 @@
 # SPDX-License-Identifier: Apache-2.0
-# Copyright 2018-2021 The glTF-Blender-IO authors.
+# Copyright 2018-2022 The glTF-Blender-IO authors.
 
 from copy import deepcopy
 import bpy
 
 from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached, cached_by_key
 from io_scene_gltf2.io.com import gltf2_io
-from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials_unlit
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info, gltf2_blender_export_keys
-from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
-
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials_pbr_metallic_roughness
-from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials_unlit
 from ..com.gltf2_blender_extras import generate_extras
 from io_scene_gltf2.blender.exp import gltf2_blender_get
 from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
 from io_scene_gltf2.io.com.gltf2_io_debug import print_console
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_volume import export_volume
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_emission import export_emission_factor, \
+    export_emission_texture, export_emission_strength_extension
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_sheen import export_sheen
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_specular import export_specular
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_transmission import export_transmission
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_clearcoat import export_clearcoat
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials_ior import export_ior
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
 
 @cached
 def get_material_cache_key(blender_material, active_uvmap_index, export_settings):
@@ -39,24 +45,31 @@ def gather_material(blender_material, active_uvmap_index, export_settings):
     if not __filter_material(blender_material, export_settings):
         return None
 
-    mat_unlit = __gather_material_unlit(blender_material, active_uvmap_index, export_settings)
+    mat_unlit = __export_unlit(blender_material, active_uvmap_index, export_settings)
     if mat_unlit is not None:
         export_user_extensions('gather_material_hook', export_settings, mat_unlit, blender_material)
         return mat_unlit
 
     orm_texture = __gather_orm_texture(blender_material, export_settings)
 
+    emissive_factor = __gather_emissive_factor(blender_material, export_settings)
     emissive_texture, uvmap_actives_emissive_texture = __gather_emissive_texture(blender_material, export_settings)
-    extensions, uvmap_actives_extensions = __gather_extensions(blender_material, export_settings)
+    extensions, uvmap_actives_extensions = __gather_extensions(blender_material, emissive_factor, export_settings)
     normal_texture, uvmap_actives_normal_texture = __gather_normal_texture(blender_material, export_settings)
     occlusion_texture, uvmap_actives_occlusion_texture = __gather_occlusion_texture(blender_material, orm_texture, export_settings)
     pbr_metallic_roughness, uvmap_actives_pbr_metallic_roughness = __gather_pbr_metallic_roughness(blender_material, orm_texture, export_settings)
 
+    if any([i>1.0 for i in emissive_factor or []]) is True:
+        # Strength is set on extension
+        emission_strength = max(emissive_factor)
+        emissive_factor = [f / emission_strength for f in emissive_factor]
+
+
     base_material = gltf2_io.Material(
         alpha_cutoff=__gather_alpha_cutoff(blender_material, export_settings),
         alpha_mode=__gather_alpha_mode(blender_material, export_settings),
         double_sided=__gather_double_sided(blender_material, export_settings),
-        emissive_factor=__gather_emissive_factor(blender_material, export_settings),
+        emissive_factor=emissive_factor,
         emissive_texture=emissive_texture,
         extensions=extensions,
         extras=__gather_extras(blender_material, export_settings),
@@ -106,6 +119,14 @@ def gather_material(blender_material, active_uvmap_index, export_settings):
             material.extensions["KHR_materials_clearcoat"].extension['clearcoatNormalTexture'].tex_coord = active_uvmap_index
         elif tex == "transmissionTexture": #TODO not tested yet
             material.extensions["KHR_materials_transmission"].extension['transmissionTexture'].tex_coord = active_uvmap_index
+        elif tex == "specularTexture":
+            material.extensions["KHR_materials_specular"].extension['specularTexture'].tex_coord = active_uvmap_index
+        elif tex == "specularColorTexture":
+            material.extensions["KHR_materials_specular"].extension['specularColorTexture'].tex_coord = active_uvmap_index
+        elif tex == "sheenColorTexture":
+            material.extensions["KHR_materials_sheen"].extension['sheenColorTexture'].tex_coord = active_uvmap_index
+        elif tex == "sheenRoughnessTexture":
+            material.extensions["KHR_materials_sheen"].extension['sheenRoughnessTexture'].tex_coord = active_uvmap_index
 
     # If material is not using active UVMap, we need to return the same material,
     # Even if multiples meshes are using different active UVMap
@@ -188,72 +209,61 @@ def __gather_double_sided(blender_material, export_settings):
 
 
 def __gather_emissive_factor(blender_material, export_settings):
-    emissive_socket = gltf2_blender_get.get_socket(blender_material, "Emissive")
-    if emissive_socket is None:
-        emissive_socket = gltf2_blender_get.get_socket_old(blender_material, "EmissiveFactor")
-    if isinstance(emissive_socket, bpy.types.NodeSocket):
-        if export_settings['gltf_image_format'] != "NONE":
-            factor = gltf2_blender_get.get_factor_from_socket(emissive_socket, kind='RGB')
-        else:
-            factor = gltf2_blender_get.get_const_from_default_value_socket(emissive_socket, kind='RGB')
-
-        if factor is None and emissive_socket.is_linked:
-            # In glTF, the default emissiveFactor is all zeros, so if an emission texture is connected,
-            # we have to manually set it to all ones.
-            factor = [1.0, 1.0, 1.0]
-
-        if factor is None: factor = [0.0, 0.0, 0.0]
-
-        # Handle Emission Strength
-        strength_socket = None
-        if emissive_socket.node.type == 'EMISSION':
-            strength_socket = emissive_socket.node.inputs['Strength']
-        elif 'Emission Strength' in emissive_socket.node.inputs:
-            strength_socket = emissive_socket.node.inputs['Emission Strength']
-        strength = (
-            gltf2_blender_get.get_const_from_socket(strength_socket, kind='VALUE')
-            if strength_socket is not None
-            else None
-        )
-        if strength is not None:
-            factor = [f * strength for f in factor]
-
-        # Clamp to range [0,1]
-        factor = [min(1.0, f) for f in factor]
-
-        if factor == [0, 0, 0]: factor = None
-
-        return factor
-
-    return None
-
+    return export_emission_factor(blender_material, export_settings)
 
 def __gather_emissive_texture(blender_material, export_settings):
-    emissive = gltf2_blender_get.get_socket(blender_material, "Emissive")
-    if emissive is None:
-        emissive = gltf2_blender_get.get_socket_old(blender_material, "Emissive")
-    emissive_texture, use_actives_uvmap_emissive = gltf2_blender_gather_texture_info.gather_texture_info(emissive, (emissive,), export_settings)
-    return emissive_texture, ["emissiveTexture"] if use_actives_uvmap_emissive else None
+    return export_emission_texture(blender_material, export_settings)
 
 
-def __gather_extensions(blender_material, export_settings):
+def __gather_extensions(blender_material, emissive_factor, export_settings):
     extensions = {}
 
     # KHR_materials_clearcoat
     actives_uvmaps = []
 
-    clearcoat_extension, use_actives_uvmap_clearcoat = __gather_clearcoat_extension(blender_material, export_settings)
+    clearcoat_extension, use_actives_uvmap_clearcoat = export_clearcoat(blender_material, export_settings)
     if clearcoat_extension:
         extensions["KHR_materials_clearcoat"] = clearcoat_extension
         actives_uvmaps.extend(use_actives_uvmap_clearcoat)
 
     # KHR_materials_transmission
 
-    transmission_extension, use_actives_uvmap_transmission = __gather_transmission_extension(blender_material, export_settings)
+    transmission_extension, use_actives_uvmap_transmission = export_transmission(blender_material, export_settings)
     if transmission_extension:
         extensions["KHR_materials_transmission"] = transmission_extension
         actives_uvmaps.extend(use_actives_uvmap_transmission)
 
+    # KHR_materials_emissive_strength
+    if any([i>1.0 for i in emissive_factor or []]):
+        emissive_strength_extension = export_emission_strength_extension(emissive_factor, export_settings)
+        if emissive_strength_extension:
+            extensions["KHR_materials_emissive_strength"] = emissive_strength_extension
+
+    # KHR_materials_volume
+
+    volume_extension, use_actives_uvmap_volume_thickness  = export_volume(blender_material, export_settings)
+    if volume_extension:
+        extensions["KHR_materials_volume"] = volume_extension
+        actives_uvmaps.extend(use_actives_uvmap_volume_thickness)
+
+    # KHR_materials_specular
+    specular_extension, use_actives_uvmap_specular = export_specular(blender_material, export_settings)
+    if specular_extension:
+        extensions["KHR_materials_specular"] = specular_extension
+        actives_uvmaps.extend(use_actives_uvmap_specular)
+
+    # KHR_materials_sheen
+    sheen_extension, use_actives_uvmap_sheen = export_sheen(blender_material, export_settings)
+    if sheen_extension:
+        extensions["KHR_materials_sheen"] = sheen_extension
+        actives_uvmaps.extend(use_actives_uvmap_sheen)   
+
+    # KHR_materials_ior
+    # Keep this extension at the end, because we export it only if some others are exported
+    ior_extension = export_ior(blender_material, extensions, export_settings)
+    if ior_extension:
+        extensions["KHR_materials_ior"] = ior_extension
+
     return extensions, actives_uvmaps if extensions else None
 
 
@@ -271,7 +281,7 @@ def __gather_normal_texture(blender_material, export_settings):
     normal = gltf2_blender_get.get_socket(blender_material, "Normal")
     if normal is None:
         normal = gltf2_blender_get.get_socket_old(blender_material, "Normal")
-    normal_texture, use_active_uvmap_normal = gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
+    normal_texture, use_active_uvmap_normal, _ = gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
         normal,
         (normal,),
         export_settings)
@@ -283,20 +293,20 @@ def __gather_orm_texture(blender_material, export_settings):
     # If not fully shared, return None, so the images will be cached and processed separately.
 
     occlusion = gltf2_blender_get.get_socket(blender_material, "Occlusion")
-    if occlusion is None or not __has_image_node_from_socket(occlusion):
+    if occlusion is None or not gltf2_blender_get.has_image_node_from_socket(occlusion):
         occlusion = gltf2_blender_get.get_socket_old(blender_material, "Occlusion")
-        if occlusion is None or not __has_image_node_from_socket(occlusion):
+        if occlusion is None or not gltf2_blender_get.has_image_node_from_socket(occlusion):
             return None
 
     metallic_socket = gltf2_blender_get.get_socket(blender_material, "Metallic")
     roughness_socket = gltf2_blender_get.get_socket(blender_material, "Roughness")
 
-    hasMetal = metallic_socket is not None and __has_image_node_from_socket(metallic_socket)
-    hasRough = roughness_socket is not None and __has_image_node_from_socket(roughness_socket)
+    hasMetal = metallic_socket is not None and gltf2_blender_get.has_image_node_from_socket(metallic_socket)
+    hasRough = roughness_socket is not None and gltf2_blender_get.has_image_node_from_socket(roughness_socket)
 
     if not hasMetal and not hasRough:
         metallic_roughness = gltf2_blender_get.get_socket_old(blender_material, "MetallicRoughness")
-        if metallic_roughness is None or not __has_image_node_from_socket(metallic_roughness):
+        if metallic_roughness is None or not gltf2_blender_get.has_image_node_from_socket(metallic_roughness):
             return None
         result = (occlusion, metallic_roughness)
     elif not hasMetal:
@@ -313,7 +323,7 @@ def __gather_orm_texture(blender_material, export_settings):
         return None
 
     # Double-check this will past the filter in texture_info
-    info, info_use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(result[0], result, export_settings)
+    info, info_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(result[0], result, export_settings)
     if info is None:
         return None
 
@@ -323,7 +333,7 @@ def __gather_occlusion_texture(blender_material, orm_texture, export_settings):
     occlusion = gltf2_blender_get.get_socket(blender_material, "Occlusion")
     if occlusion is None:
         occlusion = gltf2_blender_get.get_socket_old(blender_material, "Occlusion")
-    occlusion_texture, use_active_uvmap_occlusion = gltf2_blender_gather_texture_info.gather_material_occlusion_texture_info_class(
+    occlusion_texture, use_active_uvmap_occlusion, _ = gltf2_blender_gather_texture_info.gather_material_occlusion_texture_info_class(
         occlusion,
         orm_texture or (occlusion,),
         export_settings)
@@ -336,129 +346,7 @@ def __gather_pbr_metallic_roughness(blender_material, orm_texture, export_settin
         orm_texture,
         export_settings)
 
-def __has_image_node_from_socket(socket):
-    result = gltf2_blender_search_node_tree.from_socket(
-        socket,
-        gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage))
-    if not result:
-        return False
-    return True
-
-def __gather_clearcoat_extension(blender_material, export_settings):
-    clearcoat_enabled = False
-    has_clearcoat_texture = False
-    has_clearcoat_roughness_texture = False
-
-    clearcoat_extension = {}
-    clearcoat_roughness_slots = ()
-
-    clearcoat_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat')
-    clearcoat_roughness_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat Roughness')
-    clearcoat_normal_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat Normal')
-
-    if isinstance(clearcoat_socket, bpy.types.NodeSocket) and not clearcoat_socket.is_linked:
-        clearcoat_extension['clearcoatFactor'] = clearcoat_socket.default_value
-        clearcoat_enabled = clearcoat_extension['clearcoatFactor'] > 0
-    elif __has_image_node_from_socket(clearcoat_socket):
-        fac = gltf2_blender_get.get_factor_from_socket(clearcoat_socket, kind='VALUE')
-        # default value in glTF is 0.0, but if there is a texture without factor, use 1
-        clearcoat_extension['clearcoatFactor'] = fac if fac != None else 1.0
-        has_clearcoat_texture = True
-        clearcoat_enabled = True
-
-    if not clearcoat_enabled:
-        return None, None
-
-    if isinstance(clearcoat_roughness_socket, bpy.types.NodeSocket) and not clearcoat_roughness_socket.is_linked:
-        clearcoat_extension['clearcoatRoughnessFactor'] = clearcoat_roughness_socket.default_value
-    elif __has_image_node_from_socket(clearcoat_roughness_socket):
-        fac = gltf2_blender_get.get_factor_from_socket(clearcoat_roughness_socket, kind='VALUE')
-        # default value in glTF is 0.0, but if there is a texture without factor, use 1
-        clearcoat_extension['clearcoatRoughnessFactor'] = fac if fac != None else 1.0
-        has_clearcoat_roughness_texture = True
-
-    # Pack clearcoat (R) and clearcoatRoughness (G) channels.
-    if has_clearcoat_texture and has_clearcoat_roughness_texture:
-        clearcoat_roughness_slots = (clearcoat_socket, clearcoat_roughness_socket,)
-    elif has_clearcoat_texture:
-        clearcoat_roughness_slots = (clearcoat_socket,)
-    elif has_clearcoat_roughness_texture:
-        clearcoat_roughness_slots = (clearcoat_roughness_socket,)
-
-    use_actives_uvmaps = []
-
-    if len(clearcoat_roughness_slots) > 0:
-        if has_clearcoat_texture:
-            clearcoat_texture, clearcoat_texture_use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(
-                clearcoat_socket,
-                clearcoat_roughness_slots,
-                export_settings,
-            )
-            clearcoat_extension['clearcoatTexture'] = clearcoat_texture
-            if clearcoat_texture_use_active_uvmap:
-                use_actives_uvmaps.append("clearcoatTexture")
-        if has_clearcoat_roughness_texture:
-            clearcoat_roughness_texture, clearcoat_roughness_texture_use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(
-                clearcoat_roughness_socket,
-                clearcoat_roughness_slots,
-                export_settings,
-            )
-            clearcoat_extension['clearcoatRoughnessTexture'] = clearcoat_roughness_texture
-            if clearcoat_roughness_texture_use_active_uvmap:
-                use_actives_uvmaps.append("clearcoatRoughnessTexture")
-    if __has_image_node_from_socket(clearcoat_normal_socket):
-        clearcoat_normal_texture, clearcoat_normal_texture_use_active_uvmap = gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
-            clearcoat_normal_socket,
-            (clearcoat_normal_socket,),
-            export_settings
-        )
-        clearcoat_extension['clearcoatNormalTexture'] = clearcoat_normal_texture
-        if clearcoat_normal_texture_use_active_uvmap:
-            use_actives_uvmaps.append("clearcoatNormalTexture")
-
-    return Extension('KHR_materials_clearcoat', clearcoat_extension, False), use_actives_uvmaps
-
-def __gather_transmission_extension(blender_material, export_settings):
-    transmission_enabled = False
-    has_transmission_texture = False
-
-    transmission_extension = {}
-    transmission_slots = ()
-
-    transmission_socket = gltf2_blender_get.get_socket(blender_material, 'Transmission')
-
-    if isinstance(transmission_socket, bpy.types.NodeSocket) and not transmission_socket.is_linked:
-        transmission_extension['transmissionFactor'] = transmission_socket.default_value
-        transmission_enabled = transmission_extension['transmissionFactor'] > 0
-    elif __has_image_node_from_socket(transmission_socket):
-        transmission_extension['transmissionFactor'] = 1.0
-        has_transmission_texture = True
-        transmission_enabled = True
-
-    if not transmission_enabled:
-        return None, None
-
-    # Pack transmission channel (R).
-    if has_transmission_texture:
-        transmission_slots = (transmission_socket,)
-
-    use_actives_uvmaps = []
-
-    if len(transmission_slots) > 0:
-        combined_texture, use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(
-            transmission_socket,
-            transmission_slots,
-            export_settings,
-        )
-        if has_transmission_texture:
-            transmission_extension['transmissionTexture'] = combined_texture
-        if use_active_uvmap:
-            use_actives_uvmaps.append("transmissionTexture")
-
-    return Extension('KHR_materials_transmission', transmission_extension, False), use_actives_uvmaps
-
-
-def __gather_material_unlit(blender_material, active_uvmap_index, export_settings):
+def __export_unlit(blender_material, active_uvmap_index, export_settings):
     gltf2_unlit = gltf2_blender_gather_materials_unlit
 
     info = gltf2_unlit.detect_shadeless_material(blender_material, export_settings)
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_clearcoat.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_clearcoat.py
new file mode 100644
index 000000000..65c164b4b
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_clearcoat.py
@@ -0,0 +1,81 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+def export_clearcoat(blender_material, export_settings):
+    clearcoat_enabled = False
+    has_clearcoat_texture = False
+    has_clearcoat_roughness_texture = False
+
+    clearcoat_extension = {}
+    clearcoat_roughness_slots = ()
+
+    clearcoat_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat')
+    clearcoat_roughness_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat Roughness')
+    clearcoat_normal_socket = gltf2_blender_get.get_socket(blender_material, 'Clearcoat Normal')
+
+    if isinstance(clearcoat_socket, bpy.types.NodeSocket) and not clearcoat_socket.is_linked:
+        clearcoat_extension['clearcoatFactor'] = clearcoat_socket.default_value
+        clearcoat_enabled = clearcoat_extension['clearcoatFactor'] > 0
+    elif gltf2_blender_get.has_image_node_from_socket(clearcoat_socket):
+        fac = gltf2_blender_get.get_factor_from_socket(clearcoat_socket, kind='VALUE')
+        # default value in glTF is 0.0, but if there is a texture without factor, use 1
+        clearcoat_extension['clearcoatFactor'] = fac if fac != None else 1.0
+        has_clearcoat_texture = True
+        clearcoat_enabled = True
+
+    if not clearcoat_enabled:
+        return None, None
+
+    if isinstance(clearcoat_roughness_socket, bpy.types.NodeSocket) and not clearcoat_roughness_socket.is_linked:
+        clearcoat_extension['clearcoatRoughnessFactor'] = clearcoat_roughness_socket.default_value
+    elif gltf2_blender_get.has_image_node_from_socket(clearcoat_roughness_socket):
+        fac = gltf2_blender_get.get_factor_from_socket(clearcoat_roughness_socket, kind='VALUE')
+        # default value in glTF is 0.0, but if there is a texture without factor, use 1
+        clearcoat_extension['clearcoatRoughnessFactor'] = fac if fac != None else 1.0
+        has_clearcoat_roughness_texture = True
+
+    # Pack clearcoat (R) and clearcoatRoughness (G) channels.
+    if has_clearcoat_texture and has_clearcoat_roughness_texture:
+        clearcoat_roughness_slots = (clearcoat_socket, clearcoat_roughness_socket,)
+    elif has_clearcoat_texture:
+        clearcoat_roughness_slots = (clearcoat_socket,)
+    elif has_clearcoat_roughness_texture:
+        clearcoat_roughness_slots = (clearcoat_roughness_socket,)
+
+    use_actives_uvmaps = []
+
+    if len(clearcoat_roughness_slots) > 0:
+        if has_clearcoat_texture:
+            clearcoat_texture, clearcoat_texture_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+                clearcoat_socket,
+                clearcoat_roughness_slots,
+                export_settings,
+            )
+            clearcoat_extension['clearcoatTexture'] = clearcoat_texture
+            if clearcoat_texture_use_active_uvmap:
+                use_actives_uvmaps.append("clearcoatTexture")
+        if has_clearcoat_roughness_texture:
+            clearcoat_roughness_texture, clearcoat_roughness_texture_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+                clearcoat_roughness_socket,
+                clearcoat_roughness_slots,
+                export_settings,
+            )
+            clearcoat_extension['clearcoatRoughnessTexture'] = clearcoat_roughness_texture
+            if clearcoat_roughness_texture_use_active_uvmap:
+                use_actives_uvmaps.append("clearcoatRoughnessTexture")
+    if gltf2_blender_get.has_image_node_from_socket(clearcoat_normal_socket):
+        clearcoat_normal_texture, clearcoat_normal_texture_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
+            clearcoat_normal_socket,
+            (clearcoat_normal_socket,),
+            export_settings
+        )
+        clearcoat_extension['clearcoatNormalTexture'] = clearcoat_normal_texture
+        if clearcoat_normal_texture_use_active_uvmap:
+            use_actives_uvmaps.append("clearcoatNormalTexture")
+
+    return Extension('KHR_materials_clearcoat', clearcoat_extension, False), use_actives_uvmaps
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_emission.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_emission.py
new file mode 100644
index 000000000..562fc19d9
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_emission.py
@@ -0,0 +1,61 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+def export_emission_factor(blender_material, export_settings):
+    emissive_socket = gltf2_blender_get.get_socket(blender_material, "Emissive")
+    if emissive_socket is None:
+        emissive_socket = gltf2_blender_get.get_socket_old(blender_material, "EmissiveFactor")
+    if isinstance(emissive_socket, bpy.types.NodeSocket):
+        if export_settings['gltf_image_format'] != "NONE":
+            factor = gltf2_blender_get.get_factor_from_socket(emissive_socket, kind='RGB')
+        else:
+            factor = gltf2_blender_get.get_const_from_default_value_socket(emissive_socket, kind='RGB')
+
+        if factor is None and emissive_socket.is_linked:
+            # In glTF, the default emissiveFactor is all zeros, so if an emission texture is connected,
+            # we have to manually set it to all ones.
+            factor = [1.0, 1.0, 1.0]
+
+        if factor is None: factor = [0.0, 0.0, 0.0]
+
+        # Handle Emission Strength
+        strength_socket = None
+        if emissive_socket.node.type == 'EMISSION':
+            strength_socket = emissive_socket.node.inputs['Strength']
+        elif 'Emission Strength' in emissive_socket.node.inputs:
+            strength_socket = emissive_socket.node.inputs['Emission Strength']
+        strength = (
+            gltf2_blender_get.get_const_from_socket(strength_socket, kind='VALUE')
+            if strength_socket is not None
+            else None
+        )
+        if strength is not None:
+            factor = [f * strength for f in factor]
+
+        # Clamp to range [0,1]
+        # Official glTF clamp to range [0,1]
+        # If we are outside, we need to use extension KHR_materials_emissive_strength
+
+        if factor == [0, 0, 0]: factor = None
+
+        return factor
+
+    return None
+
+def export_emission_texture(blender_material, export_settings):
+    emissive = gltf2_blender_get.get_socket(blender_material, "Emissive")
+    if emissive is None:
+        emissive = gltf2_blender_get.get_socket_old(blender_material, "Emissive")
+    emissive_texture, use_actives_uvmap_emissive, _ = gltf2_blender_gather_texture_info.gather_texture_info(emissive, (emissive,), export_settings)
+    return emissive_texture, ["emissiveTexture"] if use_actives_uvmap_emissive else None
+
+def export_emission_strength_extension(emissive_factor, export_settings):
+    emissive_strength_extension = {}
+    emissive_strength_extension['emissiveStrength'] = max(emissive_factor)
+
+    return Extension('KHR_materials_emissive_strength', emissive_strength_extension, False)
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_ior.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_ior.py
new file mode 100644
index 000000000..fc219c010
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_ior.py
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.io.com.gltf2_io_constants import GLTF_IOR
+
+def export_ior(blender_material, extensions, export_settings):
+    ior_socket = gltf2_blender_get.get_socket(blender_material, 'IOR')
+
+    if not ior_socket:
+        return None
+
+    # We don't manage case where socket is linked, always check default value
+    if ior_socket.is_linked:
+        # TODOExt: add warning?
+        return None
+
+    if ior_socket.default_value == GLTF_IOR:
+        return None
+
+    # Export only if the following extensions are exported:
+    need_to_export_ior = [
+        'KHR_materials_transmission',
+        'KHR_materials_volume',
+        'KHR_materials_specular'
+    ]
+
+    if not any([e in extensions.keys() for e in need_to_export_ior]):
+        return None
+
+    ior_extension = {}
+    ior_extension['ior'] = ior_socket.default_value
+
+    return Extension('KHR_materials_ior', ior_extension, False)
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py
index 0b40ffd60..a5929c05b 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py
@@ -15,8 +15,8 @@ def gather_material_pbr_metallic_roughness(blender_material, orm_texture, export
     if not __filter_pbr_material(blender_material, export_settings):
         return None, None
 
-    base_color_texture, use_active_uvmap_base_color_texture = __gather_base_color_texture(blender_material, export_settings)
-    metallic_roughness_texture, use_active_uvmap_metallic_roughness_texture = __gather_metallic_roughness_texture(blender_material, orm_texture, export_settings)
+    base_color_texture, use_active_uvmap_base_color_texture, _ = __gather_base_color_texture(blender_material, export_settings)
+    metallic_roughness_texture, use_active_uvmap_metallic_roughness_texture, _ = __gather_metallic_roughness_texture(blender_material, orm_texture, export_settings)
 
     material = gltf2_io.MaterialPBRMetallicRoughness(
         base_color_factor=__gather_base_color_factor(blender_material, export_settings),
@@ -92,7 +92,7 @@ def __gather_base_color_texture(blender_material, export_settings):
         if socket is not None and __has_image_node_from_socket(socket)
     )
     if not inputs:
-        return None, None
+        return None, None, None
 
     return gltf2_blender_gather_texture_info.gather_texture_info(inputs[0], inputs, export_settings)
 
@@ -128,7 +128,7 @@ def __gather_metallic_roughness_texture(blender_material, orm_texture, export_se
     if not hasMetal and not hasRough:
         metallic_roughness = gltf2_blender_get.get_socket_old(blender_material, "MetallicRoughness")
         if metallic_roughness is None or not __has_image_node_from_socket(metallic_roughness):
-            return None, None
+            return None, None, None
         texture_input = (metallic_roughness,)
     elif not hasMetal:
         texture_input = (roughness_socket,)
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_sheen.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_sheen.py
new file mode 100644
index 000000000..03625ecbc
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_sheen.py
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+
+def export_sheen(blender_material, export_settings):
+    sheen_extension = {}
+
+    sheenColor_socket = gltf2_blender_get.get_socket(blender_material, "sheenColor")
+    sheenRoughness_socket = gltf2_blender_get.get_socket(blender_material, "sheenRoughness")
+
+    if sheenColor_socket is None or sheenRoughness_socket is None:
+        return None, None
+
+    sheenColor_non_linked = isinstance(sheenColor_socket, bpy.types.NodeSocket) and not sheenColor_socket.is_linked
+    sheenRoughness_non_linked = isinstance(sheenRoughness_socket, bpy.types.NodeSocket) and not sheenRoughness_socket.is_linked
+
+
+    use_actives_uvmaps = []
+
+    if sheenColor_non_linked is True:
+        color = sheenColor_socket.default_value[:3]
+        if color != (0.0, 0.0, 0.0):
+            sheen_extension['sheenColorFactor'] = color
+    else:
+        # Factor
+        fac = gltf2_blender_get.get_factor_from_socket(sheenColor_socket, kind='RGB')
+        if fac is not None and fac != [0.0, 0.0, 0.0]:
+            sheen_extension['sheenColorFactor'] = fac
+        
+        # Texture
+        if gltf2_blender_get.has_image_node_from_socket(sheenColor_socket):
+            original_sheenColor_texture, original_sheenColor_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+                sheenColor_socket,
+                (sheenColor_socket,),
+                export_settings,
+            )
+            sheen_extension['sheenColorTexture'] = original_sheenColor_texture
+            if original_sheenColor_use_active_uvmap:
+                use_actives_uvmaps.append("sheenColorTexture")
+
+
+    if sheenRoughness_non_linked is True:
+        fac = sheenRoughness_socket.default_value
+        if fac != 0.0:
+            sheen_extension['sheenRoughnessFactor'] = fac
+    else:
+        # Factor
+        fac = gltf2_blender_get.get_factor_from_socket(sheenRoughness_socket, kind='VALUE')
+        if fac is not None and fac != 0.0:
+            sheen_extension['sheenRoughnessFactor'] = fac
+        
+        # Texture
+        if gltf2_blender_get.has_image_node_from_socket(sheenRoughness_socket):
+            original_sheenRoughness_texture, original_sheenRoughness_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+                sheenRoughness_socket,
+                (sheenRoughness_socket,),
+                export_settings,
+            )
+            sheen_extension['sheenRoughnessTexture'] = original_sheenRoughness_texture
+            if original_sheenRoughness_use_active_uvmap:
+                use_actives_uvmaps.append("sheenRoughnessTexture")
+    
+    return Extension('KHR_materials_sheen', sheen_extension, False), use_actives_uvmaps
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_specular.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_specular.py
new file mode 100644
index 000000000..30b321981
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_specular.py
@@ -0,0 +1,168 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.io.com.gltf2_io_constants import GLTF_IOR
+from io_scene_gltf2.blender.com.gltf2_blender_default import BLENDER_SPECULAR, BLENDER_SPECULAR_TINT
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+
+
+def export_original_specular(blender_material, export_settings):
+    specular_extension = {}
+
+    original_specular_socket = gltf2_blender_get.get_socket_original(blender_material, 'specular glTF')
+    original_specularcolor_socket = gltf2_blender_get.get_socket_original(blender_material, 'specularColor glTF')
+
+    if original_specular_socket is None or original_specularcolor_socket is None:
+        return None, None
+
+    specular_non_linked = isinstance(original_specular_socket, bpy.types.NodeSocket) and not original_specular_socket.is_linked
+    specularcolor_non_linked = isinstance(original_specularcolor_socket, bpy.types.NodeSocket) and not original_specularcolor_socket.is_linked
+
+
+    use_actives_uvmaps = []
+
+    if specular_non_linked is True:
+        fac = original_specular_socket.default_value
+        if fac != 1.0:
+            specular_extension['specularFactor'] = fac
+    else:
+        # Factor
+        fac = gltf2_blender_get.get_factor_from_socket(original_specular_socket, kind='VALUE')
+        if fac is not None and fac != 1.0:
+            specular_extension['specularFactor'] = fac
+        
+        # Texture
+        if gltf2_blender_get.has_image_node_from_socket(original_specular_socket):
+            original_specular_texture, original_specular_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+                original_specular_socket,
+                (original_specular_socket,),
+                export_settings,
+            )
+            specular_extension['specularTexture'] = original_specular_texture
+            if original_specular_use_active_uvmap:
+                use_actives_uvmaps.append("specularTexture")
+
+
+    if specularcolor_non_linked is True:
+        color = original_specularcolor_socket.default_value[:3]
+        if color != [1.0, 1.0, 1.0]:
+            specular_extension['specularColorFactor'] = color
+    else:
+        # Factor
+        fac = gltf2_blender_get.get_factor_from_socket(original_specularcolor_socket, kind='RGB')
+        if fac is not None and fac != [1.0, 1.0, 1.0]:
+            specular_extension['specularColorFactor'] = fac
+
+        # Texture
+        if gltf2_blender_get.has_image_node_from_socket(original_specularcolor_socket):
+            original_specularcolor_texture, original_specularcolor_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+                original_specularcolor_socket,
+                (original_specularcolor_socket,),
+                export_settings,
+            )
+            specular_extension['specularColorTexture'] = original_specularcolor_texture
+            if original_specularcolor_use_active_uvmap:
+                use_actives_uvmaps.append("specularColorTexture")
+    
+    return Extension('KHR_materials_specular', specular_extension, False), use_actives_uvmaps
+
+def export_specular(blender_material, export_settings):
+
+    if export_settings['gltf_original_specular'] is True:
+        return export_original_specular(blender_material, export_settings)
+
+    specular_extension = {}
+    specular_ext_enabled = False
+
+    specular_socket = gltf2_blender_get.get_socket(blender_material, 'Specular')
+    specular_tint_socket = gltf2_blender_get.get_socket(blender_material, 'Specular Tint')
+    base_color_socket = gltf2_blender_get.get_socket(blender_material, 'Base Color')
+    transmission_socket = gltf2_blender_get.get_socket(blender_material, 'Transmission')
+    ior_socket = gltf2_blender_get.get_socket(blender_material, 'IOR')
+
+    if base_color_socket is None:
+        return None, None
+
+    # TODOExt replace by __has_image_node_from_socket calls
+    specular_not_linked = isinstance(specular_socket, bpy.types.NodeSocket) and not specular_socket.is_linked
+    specular_tint_not_linked = isinstance(specular_tint_socket, bpy.types.NodeSocket) and not specular_tint_socket.is_linked
+    base_color_not_linked = isinstance(base_color_socket, bpy.types.NodeSocket) and not base_color_socket.is_linked
+    transmission_not_linked = isinstance(transmission_socket, bpy.types.NodeSocket) and not transmission_socket.is_linked
+    ior_not_linked = isinstance(ior_socket, bpy.types.NodeSocket) and not ior_socket.is_linked
+
+    specular = specular_socket.default_value if specular_not_linked else None
+    specular_tint = specular_tint_socket.default_value if specular_tint_not_linked else None
+    transmission = transmission_socket.default_value if transmission_not_linked else None
+    ior = ior_socket.default_value if ior_not_linked else GLTF_IOR   # textures not supported #TODOExt add warning?
+    base_color = base_color_socket.default_value[0:3]
+
+    no_texture = (transmission_not_linked and specular_not_linked and specular_tint_not_linked and
+        (specular_tint == 0.0 or (specular_tint != 0.0 and base_color_not_linked)))
+
+    use_actives_uvmaps = []
+
+    if no_texture:
+        if specular != BLENDER_SPECULAR or specular_tint != BLENDER_SPECULAR_TINT:
+            import numpy as np
+            # See https://gist.github.com/proog128/d627c692a6bbe584d66789a5a6437a33
+            specular_ext_enabled = True
+
+            def normalize(c):
+                luminance = lambda c: 0.3 * c[0] + 0.6 * c[1] + 0.1 * c[2]
+                assert(len(c) == 3)
+                l = luminance(c)
+                if l == 0:
+                    return np.array(c)
+                return np.array([c[0] / l, c[1] / l, c[2] / l])            
+
+            f0_from_ior = ((ior - 1)/(ior + 1))**2
+            tint_strength = (1 - specular_tint) + normalize(base_color) * specular_tint
+            specular_color = (1 - transmission) * (1 / f0_from_ior) * 0.08 * specular * tint_strength + transmission * tint_strength
+            specular_extension['specularColorFactor'] = list(specular_color)
+    else:
+        if specular_not_linked and specular == BLENDER_SPECULAR and specular_tint_not_linked and specular_tint == BLENDER_SPECULAR_TINT:
+            return None, None
+
+        # Trying to identify cases where exporting a texture will not be needed
+        if specular_not_linked and transmission_not_linked and \
+            specular == 0.0 and transmission == 0.0:
+
+            specular_extension['specularColorFactor'] = [0.0, 0.0, 0.0]
+            return specular_extension, []
+
+
+        # There will be a texture, with a complex calculation (no direct channel mapping)
+        sockets = (specular_socket, specular_tint_socket, base_color_socket, transmission_socket, ior_socket)
+        # Set primary socket having a texture
+        primary_socket = specular_socket
+        if specular_not_linked:
+            primary_socket = specular_tint_socket
+            if specular_tint_not_linked:
+                primary_socket = base_color_socket
+                if base_color_not_linked:
+                    primary_socket = transmission_socket
+
+        specularColorTexture, use_active_uvmap, specularColorFactor = gltf2_blender_gather_texture_info.gather_texture_info(
+            primary_socket, 
+            sockets, 
+            export_settings,
+            filter_type='ANY')
+        if specularColorTexture is None:
+            return None, None
+        if use_active_uvmap:
+            use_actives_uvmaps.append("specularColorTexture")
+
+        specular_ext_enabled = True
+        specular_extension['specularColorTexture'] = specularColorTexture
+
+
+        if specularColorFactor is not None:
+            specular_extension['specularColorFactor'] = specularColorFactor
+            
+
+    specular_extension = Extension('KHR_materials_specular', specular_extension, False) if specular_ext_enabled else None
+    return specular_extension, use_actives_uvmaps
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_transmission.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_transmission.py
new file mode 100644
index 000000000..fdc5d8c75
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_transmission.py
@@ -0,0 +1,47 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+def export_transmission(blender_material, export_settings):
+    transmission_enabled = False
+    has_transmission_texture = False
+
+    transmission_extension = {}
+    transmission_slots = ()
+
+    transmission_socket = gltf2_blender_get.get_socket(blender_material, 'Transmission')
+
+    if isinstance(transmission_socket, bpy.types.NodeSocket) and not transmission_socket.is_linked:
+        transmission_extension['transmissionFactor'] = transmission_socket.default_value
+        transmission_enabled = transmission_extension['transmissionFactor'] > 0
+    elif gltf2_blender_get.has_image_node_from_socket(transmission_socket):
+        fac = gltf2_blender_get.get_factor_from_socket(transmission_socket, kind='VALUE')
+        transmission_extension['transmissionFactor'] = fac if fac is not None else 1.0
+        has_transmission_texture = True
+        transmission_enabled = True
+
+    if not transmission_enabled:
+        return None, None
+
+    # Pack transmission channel (R).
+    if has_transmission_texture:
+        transmission_slots = (transmission_socket,)
+
+    use_actives_uvmaps = []
+
+    if len(transmission_slots) > 0:
+        combined_texture, use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+            transmission_socket,
+            transmission_slots,
+            export_settings,
+        )
+        if has_transmission_texture:
+            transmission_extension['transmissionTexture'] = combined_texture
+        if use_active_uvmap:
+            use_actives_uvmaps.append("transmissionTexture")
+
+    return Extension('KHR_materials_transmission', transmission_extension, False), use_actives_uvmaps
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_unlit.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_unlit.py
index e104b7f1f..b501f98f0 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_unlit.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_unlit.py
@@ -1,9 +1,9 @@
 # SPDX-License-Identifier: Apache-2.0
-# Copyright 2018-2021 The glTF-Blender-IO authors.
+# Copyright 2018-2022 The glTF-Blender-IO authors.
 
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
 from io_scene_gltf2.blender.exp import gltf2_blender_get
-
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
 
 def detect_shadeless_material(blender_material, export_settings):
     """Detect if this material is "shadeless" ie. should be exported
@@ -127,7 +127,7 @@ def gather_base_color_texture(info, export_settings):
         # because gather_image determines how to pack images based on the
         # names of sockets, and the names are hard-coded to a Principled
         # style graph.
-        unlit_texture, unlit_use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(
+        unlit_texture, unlit_use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
             sockets[0],
             sockets,
             export_settings,
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_variants.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_variants.py
new file mode 100644
index 000000000..4c452e6a6
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_variants.py
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from typing import Dict, Any
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.io.com import gltf2_io_variants
+
+
+@cached
+def gather_variant(variant_idx, export_settings) -> Dict[str, Any]:
+
+    variant = gltf2_io_variants.Variant(
+        name=bpy.data.scenes[0].gltf2_KHR_materials_variants_variants[variant_idx].name,
+        extensions=None,
+        extras=None
+    )
+    return variant.to_dict()
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_volume.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_volume.py
new file mode 100644
index 000000000..57b007000
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_volume.py
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+
+
+def export_volume(blender_material, export_settings):
+    # Implementation based on https://github.com/KhronosGroup/glTF-Blender-IO/issues/1454#issuecomment-928319444
+
+    # If no transmission --> No volume
+    transmission_enabled = False
+    transmission_socket = gltf2_blender_get.get_socket(blender_material, 'Transmission')
+    if isinstance(transmission_socket, bpy.types.NodeSocket) and not transmission_socket.is_linked:
+        transmission_enabled = transmission_socket.default_value > 0
+    elif gltf2_blender_get.has_image_node_from_socket(transmission_socket):
+        transmission_enabled = True
+
+    if transmission_enabled is False:
+        return None, None
+
+    volume_extension = {}
+    has_thickness_texture = False
+    thickness_slots = ()
+
+    thicknesss_socket = gltf2_blender_get.get_socket_old(blender_material, 'Thickness')
+    if thicknesss_socket is None:
+        volume_extension['thicknessFactor'] = 1.0
+
+    density_socket = gltf2_blender_get.get_socket(blender_material, 'Density', volume=True)
+    attenuation_color_socket = gltf2_blender_get.get_socket(blender_material, 'Color', volume=True)
+    if density_socket is None or attenuation_color_socket is None:
+        return None, None
+
+    if isinstance(attenuation_color_socket, bpy.types.NodeSocket):
+        rgb = gltf2_blender_get.get_const_from_default_value_socket(attenuation_color_socket, kind='RGB')
+        volume_extension['attenuationColor'] = rgb
+
+    if isinstance(density_socket, bpy.types.NodeSocket):
+        density = gltf2_blender_get.get_const_from_default_value_socket(density_socket, kind='VALUE')
+        volume_extension['attenuationDistance'] = 1.0 / density if density != 0 else None # infinity (Using None as glTF default)
+
+
+    if isinstance(thicknesss_socket, bpy.types.NodeSocket) and not thicknesss_socket.is_linked:
+        val = thicknesss_socket.default_value
+        if val == 0.0:
+            # If no thickness, no volume extension export 
+            return None, None
+        volume_extension['thicknessFactor'] = val
+    elif gltf2_blender_get.has_image_node_from_socket(thicknesss_socket):
+        fac = gltf2_blender_get.get_factor_from_socket(thicknesss_socket, kind='VALUE')
+        # default value in glTF is 0.0, but if there is a texture without factor, use 1
+        volume_extension['thicknessFactor'] = fac if fac != None else 1.0
+        has_thickness_texture = True
+
+       # Pack thickness channel (R).
+    if has_thickness_texture:
+        thickness_slots = (thicknesss_socket,)
+
+    use_actives_uvmaps = []
+
+    if len(thickness_slots) > 0:
+        combined_texture, use_active_uvmap, _ = gltf2_blender_gather_texture_info.gather_texture_info(
+            thicknesss_socket,
+            thickness_slots,
+            export_settings,
+        )
+        if has_thickness_texture:
+            volume_extension['thicknessTexture'] = combined_texture
+        if use_active_uvmap:
+            use_actives_uvmaps.append("thicknessTexture")
+
+    return Extension('KHR_materials_volume', volume_extension, False), use_actives_uvmaps
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
index 367c30f57..b2ffb6b33 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
@@ -12,10 +12,12 @@ from io_scene_gltf2.blender.exp import gltf2_blender_extract
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_accessors
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_primitive_attributes
 from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials_variants
 
 from io_scene_gltf2.io.com import gltf2_io
 from io_scene_gltf2.io.exp import gltf2_io_binary_data
 from io_scene_gltf2.io.com import gltf2_io_constants
+from io_scene_gltf2.io.com import gltf2_io_extensions
 from io_scene_gltf2.io.com.gltf2_io_debug import print_console
 
 
@@ -86,7 +88,7 @@ def gather_primitives(
 
         primitive = gltf2_io.MeshPrimitive(
             attributes=internal_primitive['attributes'],
-            extensions=None,
+            extensions=__gather_extensions(blender_mesh, material_idx, active_uvmap_idx, export_settings),
             extras=None,
             indices=internal_primitive['indices'],
             material=material,
@@ -214,3 +216,55 @@ def __gather_targets(blender_primitive, blender_mesh, modifiers, export_settings
                     morph_index += 1
         return targets
     return None
+
+def __gather_extensions(blender_mesh,
+                        material_idx: int,
+                        active_uvmap_idx,
+                        export_settings):
+    extensions = {}
+
+    if bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is False:
+        return None
+
+    if bpy.data.scenes[0].get('gltf2_KHR_materials_variants_variants') is None:
+        return None
+    if len(bpy.data.scenes[0]['gltf2_KHR_materials_variants_variants']) == 0:
+        return None
+
+    # Material idx is the slot idx. Retrieve associated variant, if any
+    mapping = []
+    for i in [v for v in blender_mesh.gltf2_variant_mesh_data if v.material_slot_index == material_idx]:
+        variants = []
+        for idx, v in enumerate(i.variants):
+            if v.variant.variant_idx in [o.variant.variant_idx for o in i.variants[:idx]]:
+                # Avoid duplicates
+                continue
+            vari = gltf2_blender_gather_materials_variants.gather_variant(v.variant.variant_idx, export_settings)
+            if vari is not None:
+                variant_extension = gltf2_io_extensions.ChildOfRootExtension(
+                name="KHR_materials_variants",
+                path=["variants"],
+                extension=vari
+            )
+            variants.append(variant_extension)
+        if len(variants) > 0:
+            if i.material:
+                mat = gltf2_blender_gather_materials.gather_material(
+                        i.material,
+                        active_uvmap_idx,
+                        export_settings
+                    )
+            else:
+                # empty slot
+                mat = None
+            mapping.append({'material': mat, 'variants': variants})
+
+    if len(mapping) > 0:
+        extensions["KHR_materials_variants"] = gltf2_io_extensions.Extension(
+            name="KHR_materials_variants",
+            extension={
+                "mappings": mapping
+            }
+        )
+
+    return extensions if extensions else None
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py
index e8c6baf18..ccfd42e52 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py
@@ -26,24 +26,26 @@ def gather_texture(
     """
 
     if not __filter_texture(blender_shader_sockets, export_settings):
-        return None
+        return None, None
+
+    source, factor = __gather_source(blender_shader_sockets, export_settings)
 
     texture = gltf2_io.Texture(
         extensions=__gather_extensions(blender_shader_sockets, export_settings),
         extras=__gather_extras(blender_shader_sockets, export_settings),
         name=__gather_name(blender_shader_sockets, export_settings),
         sampler=__gather_sampler(blender_shader_sockets, export_settings),
-        source=__gather_source(blender_shader_sockets, export_settings)
+        source= source
     )
 
     # although valid, most viewers can't handle missing source properties
     # This can have None source for "keep original", when original can't be found
     if texture.source is None:
-        return None
+        return None, None
 
     export_user_extensions('gather_texture_hook', export_settings, texture, blender_shader_sockets)
 
-    return texture
+    return texture, factor
 
 
 def __filter_texture(blender_shader_sockets, export_settings):
@@ -66,13 +68,14 @@ def __gather_name(blender_shader_sockets, export_settings):
 
 
 def __gather_sampler(blender_shader_sockets, export_settings):
-    shader_nodes = [__get_tex_from_socket(socket).shader_node for socket in blender_shader_sockets]
+    shader_nodes = [__get_tex_from_socket(socket) for socket in blender_shader_sockets]
     if len(shader_nodes) > 1:
         gltf2_io_debug.print_console("WARNING",
                                      "More than one shader node tex image used for a texture. "
                                      "The resulting glTF sampler will behave like the first shader node tex image.")
+    first_valid_shader_node = next(filter(lambda x: x is not None, shader_nodes)).shader_node
     return gltf2_blender_gather_sampler.gather_sampler(
-        shader_nodes[0],
+        first_valid_shader_node,
         export_settings)
 
 
@@ -81,7 +84,7 @@ def __gather_source(blender_shader_sockets, export_settings):
 
 # Helpers
 
-
+# TODOExt deduplicate
 def __get_tex_from_socket(socket):
     result = gltf2_blender_search_node_tree.from_socket(
         socket,
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py
index 15b101ade..5fe2da329 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py
@@ -19,14 +19,14 @@ from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extension
 # occlusion the primary_socket would be the occlusion socket, and
 # blender_shader_sockets would be the (O,R,M) sockets.
 
-def gather_texture_info(primary_socket, blender_shader_sockets, export_settings):
-    return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'DEFAULT', export_settings)
+def gather_texture_info(primary_socket, blender_shader_sockets, export_settings, filter_type='ALL'):
+    return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'DEFAULT', filter_type, export_settings)
 
-def gather_material_normal_texture_info_class(primary_socket, blender_shader_sockets, export_settings):
-    return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'NORMAL', export_settings)
+def gather_material_normal_texture_info_class(primary_socket, blender_shader_sockets, export_settings, filter_type='ALL'):
+    return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'NORMAL', filter_type, export_settings)
 
-def gather_material_occlusion_texture_info_class(primary_socket, blender_shader_sockets, export_settings):
-    return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'OCCLUSION', export_settings)
+def gather_material_occlusion_texture_info_class(primary_socket, blender_shader_sockets, export_settings, filter_type='ALL'):
+    return __gather_texture_info_helper(primary_socket, blender_shader_sockets, 'OCCLUSION', filter_type, export_settings)
 
 
 @cached
@@ -34,16 +34,19 @@ def __gather_texture_info_helper(
         primary_socket: bpy.types.NodeSocket,
         blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
         kind: str,
+        filter_type: str,
         export_settings):
-    if not __filter_texture_info(primary_socket, blender_shader_sockets, export_settings):
-        return None, None
+    if not __filter_texture_info(primary_socket, blender_shader_sockets, filter_type, export_settings):
+        return None, None, None
 
     tex_transform, tex_coord, use_active_uvmap = __gather_texture_transform_and_tex_coord(primary_socket, export_settings)
 
+    index, factor = __gather_index(blender_shader_sockets, export_settings)
+
     fields = {
         'extensions': __gather_extensions(tex_transform, export_settings),
         'extras': __gather_extras(blender_shader_sockets, export_settings),
-        'index': __gather_index(blender_shader_sockets, export_settings),
+        'index': index,
         'tex_coord': tex_coord
     }
 
@@ -59,14 +62,14 @@ def __gather_texture_info_helper(
         texture_info = gltf2_io.MaterialOcclusionTextureInfoClass(**fields)
 
     if texture_info.index is None:
-        return None, None
+        return None, None, None
 
     export_user_extensions('gather_texture_info_hook', export_settings, texture_info, blender_shader_sockets)
 
-    return texture_info, use_active_uvmap
+    return texture_info, use_active_uvmap, factor
 
 
-def __filter_texture_info(primary_socket, blender_shader_sockets, export_settings):
+def __filter_texture_info(primary_socket, blender_shader_sockets, filter_type, export_settings):
     if primary_socket is None:
         return False
     if __get_tex_from_socket(primary_socket) is None:
@@ -75,9 +78,18 @@ def __filter_texture_info(primary_socket, blender_shader_sockets, export_setting
         return False
     if not all([elem is not None for elem in blender_shader_sockets]):
         return False
-    if any([__get_tex_from_socket(socket) is None for socket in blender_shader_sockets]):
-        # sockets do not lead to a texture --> discard
-        return False
+    if filter_type == "ALL":
+        # Check that all sockets link to texture
+        if any([__get_tex_from_socket(socket) is None for socket in blender_shader_sockets]):
+            # sockets do not lead to a texture --> discard
+            return False
+    elif filter_type == "ANY":
+        # Check that at least one socket link to texture
+        if all([__get_tex_from_socket(socket) is None for socket in blender_shader_sockets]):
+            return False
+    elif filter_type == "NONE":
+        # No check 
+        pass
 
     return True
 
@@ -163,7 +175,7 @@ def __gather_texture_transform_and_tex_coord(primary_socket, export_settings):
 
     return texture_transform, texcoord_idx or None, use_active_uvmap
 
-
+# TODOExt deduplicate
 def __get_tex_from_socket(socket):
     result = gltf2_blender_search_node_tree.from_socket(
         socket,
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
index 0377a6b7c..367726678 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
@@ -468,3 +468,20 @@ class VExportTree:
                 skin = gather_skin(n.uuid, self.export_settings)
                 skins.append(skin)
         return skins
+
+    def variants_reset_to_original(self):
+        # Only if Variants are displayed and exported
+        if bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is False:
+            return
+        objects = [self.nodes[o].blender_object for o in self.get_all_node_of_type(VExportNode.OBJECT) if self.nodes[o].blender_object.type == "MESH" \
+            and self.nodes[o].blender_object.data.get('gltf2_variant_default_materials') is not None]
+        for obj in objects:
+            # loop on material slots ( primitives )
+            for mat_slot_idx, s in enumerate(obj.material_slots):
+                # Check if there is a default material for this slot
+                for i in obj.data.gltf2_variant_default_materials:
+                    if i.material_slot_index == mat_slot_idx:
+                        s.material = i.default_material
+                        break
+
+            # If not found, keep current material as default
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_get.py b/io_scene_gltf2/blender/exp/gltf2_blender_get.py
index e38906e64..17eacf860 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_get.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_get.py
@@ -4,9 +4,10 @@
 import bpy
 from mathutils import Vector, Matrix
 
-from ..com.gltf2_blender_material_helpers import get_gltf_node_name
+from ..com.gltf2_blender_material_helpers import get_gltf_node_name, get_gltf_pbr_non_converted_name
 from ...blender.com.gltf2_blender_conversion import texture_transform_blender_to_gltf
 from io_scene_gltf2.io.com import gltf2_io_debug
+from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
 
 
 def get_animation_target(action_group: bpy.types.ActionGroup):
@@ -47,7 +48,7 @@ def get_node_socket(blender_material, type, name):
     return None
 
 
-def get_socket(blender_material: bpy.types.Material, name: str):
+def get_socket(blender_material: bpy.types.Material, name: str, volume=False):
     """
     For a given material input name, retrieve the corresponding node tree socket.
 
@@ -70,8 +71,15 @@ def get_socket(blender_material: bpy.types.Material, name: str):
         elif name == "Background":
             type = bpy.types.ShaderNodeBackground
             name = "Color"
+        elif name == "sheenColor":
+            return get_node_socket(blender_material, bpy.types.ShaderNodeBsdfVelvet, "Color")
+        elif name == "sheenRoughness":
+            return get_node_socket(blender_material, bpy.types.ShaderNodeBsdfVelvet, "Sigma")
         else:
-            type = bpy.types.ShaderNodeBsdfPrincipled
+            if volume is False:
+                type = bpy.types.ShaderNodeBsdfPrincipled
+            else:
+                type = bpy.types.ShaderNodeVolumeAbsorption
 
         return get_node_socket(blender_material, type, name)
 
@@ -97,6 +105,24 @@ def get_socket_old(blender_material: bpy.types.Material, name: str):
 
     return None
 
+def get_socket_original(blender_material: bpy.types.Material, name: str):
+    """
+    For a given material input name, retrieve the corresponding node tree socket in the special glTF node group.
+
+    :param blender_material: a blender material for which to get the socket
+    :param name: the name of the socket
+    :return: a blender NodeSocket
+    """
+    gltf_node_group_name = get_gltf_pbr_non_converted_name().lower()
+    if blender_material.node_tree and blender_material.use_nodes:
+        nodes = [n for n in blender_material.node_tree.nodes if \
+            isinstance(n, bpy.types.ShaderNodeGroup) and  n.node_tree.name.lower() == gltf_node_group_name]
+        inputs = sum([[input for input in node.inputs if input.name == name] for node in nodes], [])
+        if inputs:
+            return inputs[0]
+
+    return None    
+
 def check_if_is_linked_to_active_output(shader_socket):
     for link in shader_socket.links:
         if isinstance(link.to_node, bpy.types.ShaderNodeOutputMaterial) and link.to_node.is_active_output is True:
@@ -297,3 +323,12 @@ def previous_node(socket):
     if prev_socket is not None:
         return prev_socket.node
     return None
+
+#TODOExt is this the same as __get_tex_from_socket from gather_image ?
+def has_image_node_from_socket(socket):
+    result = gltf2_blender_search_node_tree.from_socket(
+        socket,
+        gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage))
+    if not result:
+        return False
+    return True
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_image.py b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
index 8b9db89a3..6730f4791 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_image.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
@@ -27,6 +27,18 @@ class FillWhite:
     """Fills a channel with all ones (1.0)."""
     pass
 
+class StoreData:
+    def __init__(self, data):
+        """Store numeric data (not an image channel"""
+        self.data = data
+
+class StoreImage:
+    """
+    Store a channel with the channel src_chan from a Blender image.
+    This channel will be used for numpy calculation (no direct channel mapping)
+    """
+    def __init__(self, image: bpy.types.Image):
+        self.image = image
 
 class ExportImage:
     """Custom image class.
@@ -55,9 +67,13 @@ class ExportImage:
 
     def __init__(self, original=None):
         self.fills = {}
+        self.stored = {}
 
-        # In case of keeping original texture images
-        self.original = original
+        self.original = original # In case of keeping original texture images
+        self.numpy_calc = None
+
+    def set_calc(self, numpy_calc):
+        self.numpy_calc = numpy_calc # In case of numpy calculation (no direct channel mapping)
 
     @staticmethod
     def from_blender_image(image: bpy.types.Image):
@@ -73,6 +89,12 @@ class ExportImage:
     def fill_image(self, image: bpy.types.Image, dst_chan: Channel, src_chan: Channel):
         self.fills[dst_chan] = FillImage(image, src_chan)
 
+    def store_data(self, identifier, data, type='Image'):
+        if type == "Image": # This is an image
+            self.stored[identifier] = StoreImage(data)
+        else: # This is a numeric value
+            self.stored[identifier] = StoreData(data)
+
     def fill_white(self, dst_chan: Channel):
         self.fills[dst_chan] = FillWhite()
 
@@ -81,7 +103,7 @@ class ExportImage:
 
     def empty(self) -> bool:
         if self.original is None:
-            return not self.fills
+            return not (self.fills or self.stored)
         else:
             return False
 
@@ -103,7 +125,7 @@ class ExportImage:
             len(set(fill.image.name for fill in self.fills.values())) == 1
         )
 
-    def encode(self, mime_type: Optional[str]) -> bytes:
+    def encode(self, mime_type: Optional[str]) -> Tuple[bytes, bool]:
         self.file_format = {
             "image/jpeg": "JPEG",
             "image/png": "PNG"
@@ -111,10 +133,14 @@ class ExportImage:
 
         # Happy path = we can just use an existing Blender image
         if self.__on_happy_path():
-            return self.__encode_happy()
+            return self.__encode_happy(), None
 
-        # Unhappy path = we need to create the image self.fills describes.
-        return self.__encode_unhappy()
+        # Unhappy path = we need to create the image self.fills describes or self.stores describes
+        if self.numpy_calc is None:
+            return self.__encode_unhappy(), None
+        else:
+            pixels, width, height, factor = self.numpy_calc(self.stored)
+            return self.__encode_from_numpy_array(pixels, (width, height)), factor
 
     def __encode_happy(self) -> bytes:
         return self.__encode_from_image(self.blender_image())
@@ -147,7 +173,7 @@ class ExportImage:
             else:
                 # Image is the wrong size; make a temp copy and scale it.
                 with TmpImageGuard() as guard:
-                    _make_temp_image_copy(guard, src_image=image)
+                    make_temp_image_copy(guard, src_image=image)
                     tmp_image = guard.image
                     tmp_image.scale(width, height)
                     tmp_image.pixels.foreach_get(tmp_buf)
@@ -197,7 +223,7 @@ class ExportImage:
 
         # Copy to a temp image and save.
         with TmpImageGuard() as guard:
-            _make_temp_image_copy(guard, src_image=image)
+            make_temp_image_copy(guard, src_image=image)
             tmp_image = guard.image
             return _encode_temp_image(tmp_image, self.file_format)
 
@@ -228,7 +254,7 @@ class TmpImageGuard:
             bpy.data.images.remove(self.image, do_unlink=True)
 
 
-def _make_temp_image_copy(guard: TmpImageGuard, src_image: bpy.types.Image):
+def make_temp_image_copy(guard: TmpImageGuard, src_image: bpy.types.Image):
     """Makes a temporary copy of src_image. Will be cleaned up with guard."""
     guard.image = src_image.copy()
     tmp_image = guard.image
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_texture_specular.py b/io_scene_gltf2/blender/exp/gltf2_blender_texture_specular.py
new file mode 100644
index 000000000..6321f1285
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_texture_specular.py
@@ -0,0 +1,94 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+import bpy
+import numpy as np
+from .gltf2_blender_gather_image import StoreImage, StoreData
+from .gltf2_blender_image import TmpImageGuard, make_temp_image_copy
+
+def specular_calculation(stored):
+
+    # See https://gist.github.com/proog128/d627c692a6bbe584d66789a5a6437a33
+    
+    # Find all Blender images used
+    images = []
+    for fill in stored.values():
+        if isinstance(fill, StoreImage):
+            if fill.image not in images:
+                images.append(fill.image)
+
+    if not images:
+        # No ImageFills; use a 1x1 white pixel
+        pixels = np.array([1.0, 1.0, 1.0, 1.0], np.float32)
+        return pixels, 1, 1
+
+    width = max(image.size[0] for image in images)
+    height = max(image.size[1] for image in images)
+
+    buffers = {}
+
+    for identifier, image in [(ident, store.image) for (ident, store) in stored.items() if isinstance(store, StoreImage)]:
+        tmp_buf = np.empty(width * height * 4, np.float32)
+        
+        if image.size[0] == width and image.size[1] == height:
+            image.pixels.foreach_get(tmp_buf)
+        else:
+            # Image is the wrong size; make a temp copy and scale it.
+            with TmpImageGuard() as guard:
+                make_temp_image_copy(guard, src_image=image)
+                tmp_image = guard.image
+                tmp_image.scale(width, height)
+                tmp_image.pixels.foreach_get(tmp_buf)
+
+        buffers[identifier] = np.reshape(tmp_buf, [width, height, 4])
+
+    # keep only needed channels
+    ## scalar
+    for i in ['specular', 'specular_tint', 'transmission']:
+        if i in buffers.keys():
+            buffers[i] = buffers[i][:,:,stored[i + "_channel"].data]
+        else:
+            buffers[i] = np.full((width, height, 1), stored[i].data)
+
+    # Vector 3
+    for i in ['base_color']:
+        if i in buffers.keys():
+            if i + "_channel" not in stored.keys():
+                buffers[i] = buffers[i][:,:,:3]
+            else:
+                # keep only needed channel
+                for c in range(3):
+                    if c != stored[i+"_channel"].data:
+                        buffers[i][:, :, c] = 0.0
+                buffers[i] = buffers[i][:,:,:3]
+        else:
+            buffers[i] = np.full((width, height, 3), stored[i].data[0:3])
+
+    ior = stored['ior'].data
+
+    # calculation
+    stack3 = lambda v: np.dstack([v]*3)
+
+    def normalize(c):
+        luminance = lambda c: 0.3 * c[:,:,0] + 0.6 * c[:,:,1] + 0.1 * c[:,:,2]
+        l = luminance(c)
+        # TODOExt Manage all 0
+        return c / stack3(l)
+
+    
+    f0_from_ior = ((ior - 1)/(ior + 1))**2
+    tint_strength = (1 - stack3(buffers['specular_tint'])) + normalize(buffers['base_color']) * stack3(buffers['specular_tint'])
+    out_buf = (1 - stack3(buffers['transmission'])) * (1 / f0_from_ior) * 0.08 * stack3(buffers['specular']) * tint_strength + stack3(buffers['transmission']) * tint_strength
+
+    # Manage values > 1.0 -> Need to apply factor
+    factor = None
+    factors = [np.amax(out_buf[:, :, i]) for i in range(3)]
+
+    if any([f > 1.0 for f in factors]):
+        factor = [1.0 if f < 1.0 else f for f in factors]
+        out_buf /= factor
+
+    out_buf = np.dstack((out_buf, np.ones((width, height)))) # Set alpha (glTF specular) to 1
+    out_buf = np.reshape(out_buf, (width * height * 4))
+    
+    return np.float32(out_buf), width, height, [float(f) for f in factor] if factor else None
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_ior.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_ior.py
new file mode 100644
index 000000000..ab5b2e7e3
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_ior.py
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2021 The glTF-Blender-IO authors.
+
+from ...io.com.gltf2_io_constants import GLTF_IOR
+
+def ior(mh, ior_socket):
+    try:
+        ext = mh.pymat.extensions['KHR_materials_ior']
+    except Exception:
+        return
+    ior = ext.get('ior', GLTF_IOR)
+    ior_socket.default_value = ior
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py
index bed63f7f6..19a394b99 100755
--- a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py
@@ -22,19 +22,24 @@ def pbr_specular_glossiness(mh):
     mh.node_tree.links.new(add_node.inputs[0], glossy_node.outputs[0])
     mh.node_tree.links.new(add_node.inputs[1], diffuse_node.outputs[0])
 
-    emission_socket, alpha_socket = make_output_nodes(
+    emission_socket, alpha_socket, _, _ = make_output_nodes(
         mh,
         location=(370, 250),
+        additional_location=None, #No additional location needed for SpecGloss
         shader_socket=add_node.outputs[0],
         make_emission_socket=mh.needs_emissive(),
         make_alpha_socket=not mh.is_opaque(),
+        make_volume_socket=None, # No possible to have KHR_materials_volume with specular/glossiness
+        make_velvet_socket=None # No possible to have KHR_materials_volume with specular/glossiness
     )
 
-    emission(
-        mh,
-        location=(-200, 860),
-        color_socket=emission_socket,
-    )
+    if emission_socket:
+        emission(
+            mh,
+            location=(-200, 860),
+            color_socket=emission_socket,
+            strength_socket=emission_socket.node.inputs['Strength']
+        )
 
     base_color(
         mh,
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_sheen.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_sheen.py
new file mode 100644
index 000000000..3560d0949
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_sheen.py
@@ -0,0 +1,88 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+from ...io.com.gltf2_io import TextureInfo
+from .gltf2_blender_texture import texture
+from .gltf2_blender_image import BlenderImage
+from ..exp.gltf2_blender_image import TmpImageGuard, make_temp_image_copy
+import numpy as np
+import bpy
+
+def sheen(  mh,
+            location_sheenColor,
+            location_sheenRoughness,
+            sheenColor_socket,
+            sheenRoughness_socket
+            ):
+
+    x_sheenColor, y_sheenColor = location_sheenColor
+    x_sheenRoughness, y_sheenRoughness = location_sheenRoughness
+
+    try:
+        ext = mh.pymat.extensions['KHR_materials_sheen']
+    except Exception:
+        return
+
+    sheenColorFactor = ext.get('sheenColorFactor', [0.0, 0.0, 0.0])
+    tex_info_color = ext.get('sheenColorTexture')
+    if tex_info_color is not None:
+        tex_info_color = TextureInfo.from_dict(tex_info_color)
+
+    sheenRoughnessFactor = ext.get('sheenRoughnessFactor', 0.0)
+    tex_info_roughness = ext.get('sheenRoughnessTexture')
+    if tex_info_roughness is not None:
+        tex_info_roughness = TextureInfo.from_dict(tex_info_roughness)    
+
+    if tex_info_color is None:
+        sheenColorFactor.extend([1.0])
+        sheenColor_socket.default_value = sheenColorFactor
+    else:
+        # Mix sheenColor factor
+        sheenColorFactor = sheenColorFactor + [1.0]
+        if sheenColorFactor != [1.0, 1.0, 1.0, 1.0]:
+            node = mh.node_tree.nodes.new('ShaderNodeMixRGB')
+            node.label = 'sheenColor Factor'
+            node.location = x_sheenColor - 140, y_sheenColor
+            node.blend_type = 'MULTIPLY'
+            # Outputs
+            mh.node_tree.links.new(sheenColor_socket, node.outputs[0])
+            # Inputs
+            node.inputs['Fac'].default_value = 1.0
+            sheenColor_socket = node.inputs['Color1']
+            node.inputs['Color2'].default_value = sheenColorFactor
+            x_sheenColor -= 200
+
+        texture(
+            mh,
+            tex_info=tex_info_color,
+            label='SHEEN COLOR',
+            location=(x_sheenColor, y_sheenColor),
+            color_socket=sheenColor_socket
+            )
+
+    if tex_info_roughness is None:
+        sheenRoughness_socket.default_value = sheenRoughnessFactor
+    else:
+         # Mix sheenRoughness factor
+        if sheenRoughnessFactor != 1.0:
+            node = mh.node_tree.nodes.new('ShaderNodeMath')
+            node.label = 'shennRoughness Factor'
+            node.location = x_sheenRoughness - 140, y_sheenRoughness
+            node.operation = 'MULTIPLY'
+            # Outputs
+            mh.node_tree.links.new(sheenRoughness_socket, node.outputs[0])
+            # Inputs
+            sheenRoughness_socket = node.inputs[0]
+            node.inputs[1].default_value = sheenRoughnessFactor
+            x_sheenRoughness -= 200
+
+        texture(
+            mh,
+            tex_info=tex_info_roughness,
+            label='SHEEN ROUGHNESS',
+            location=(x_sheenRoughness, y_sheenRoughness),
+            is_data=True,
+            color_socket=None,
+            alpha_socket=sheenRoughness_socket
+            )
+    return
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_specular.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_specular.py
new file mode 100644
index 000000000..e4a0c52d6
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_specular.py
@@ -0,0 +1,350 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2021 The glTF-Blender-IO authors.
+
+import bpy
+from ...io.com.gltf2_io import TextureInfo
+from .gltf2_blender_texture import texture
+from io_scene_gltf2.io.com.gltf2_io_constants import GLTF_IOR
+from .gltf2_blender_image import BlenderImage
+from ..exp.gltf2_blender_image import TmpImageGuard, make_temp_image_copy
+
+
+def specular(mh, location_specular, 
+                 location_specular_tint, 
+                 specular_socket, 
+                 specular_tint_socket,
+                 original_specular_socket,
+                 original_specularcolor_socket,
+                 location_original_specular,
+                 location_original_specularcolor):
+    x_specular, y_specular = location_specular
+    x_tint, y_tint = location_specular_tint
+
+    if specular_socket is None:
+        return
+    if specular_tint_socket is None:
+        return
+
+    try:
+        ext = mh.pymat.extensions['KHR_materials_specular']
+    except Exception:
+        return
+
+    import numpy as np
+
+    # Retrieve image names
+    try:
+        tex_info = mh.pymat.pbr_metallic_roughness.base_color_texture
+        pytexture = mh.gltf.data.textures[tex_info.index]
+        pyimg = mh.gltf.data.images[pytexture.source]
+        base_color_image_name = pyimg.blender_image_name
+    except:
+        base_color_image_name =  None
+
+    # First check if we need a texture or not -> retrieve all info needed
+    specular_factor = ext.get('specularFactor', 1.0)
+    tex_specular_info = ext.get('specularTexture')
+    if tex_specular_info is not None:
+        tex_specular_info = TextureInfo.from_dict(tex_specular_info)
+
+    specular_color_factor = np.array(ext.get('specularColorFactor', [1.0, 1.0, 1.0])[:3])
+    tex_specular_color_info = ext.get('specularColorTexture')
+    if tex_specular_color_info is not None:
+        tex_specular_color_info = TextureInfo.from_dict(tex_specular_color_info)
+
+    base_color_not_linked = base_color_image_name is None
+    base_color = np.array(mh.pymat.pbr_metallic_roughness.base_color_factor or [1, 1, 1])
+    tex_base_color = mh.pymat.pbr_metallic_roughness.base_color_texture
+    base_color = base_color[:3]
+
+    try:
+        ext_transmission = mh.pymat.extensions['KHR_materials_transmission']
+        transmission_factor = ext_transmission.get('transmissionFactor', 0)
+        tex_transmission_info = ext_transmission.get('transmissionTexture')
+        if tex_transmission_info is not None:
+            tex_transmission_info = TextureInfo.from_dict(tex_transmission_info)
+            pytexture = mh.gltf.data.textures[tex_transmission_info.index]
+            pyimg = mh.gltf.data.images[pytexture.source]
+            transmission_image_name = pyimg.blender_image_name
+        else:
+            transmission_image_name = None
+    except Exception:
+        transmission_factor = 0
+        tex_transmission_info = None
+        transmission_image_name = None
+
+    transmission_not_linked = transmission_image_name is None
+
+    try:
+        ext_ior = mh.pymat.extensions['KHR_materials_ior']
+        ior = ext_ior.get('ior', GLTF_IOR)
+    except:
+        ior = GLTF_IOR
+
+    use_texture = tex_specular_info is not None or tex_specular_color_info is not None \
+        or transmission_not_linked is False or base_color_not_linked is False
+
+
+    # Before creating converted textures,
+    # Also plug non converted data into glTF PBR Non Converted Extensions node
+    original_specular(  mh,
+                        specular_factor, 
+                        tex_specular_info, 
+                        specular_color_factor, 
+                        tex_specular_color_info,
+                        original_specular_socket,
+                        original_specularcolor_socket,
+                        location_original_specular,
+                        location_original_specularcolor
+                        )
+
+    
+    if not use_texture:
+
+        def luminance(c):
+            return 0.3 * c[0] + 0.6 * c[1] + 0.1 * c[2]
+
+        def normalize(c):
+            assert(len(c) == 3)
+            l = luminance(c)
+            if l == 0:
+                return c
+            return np.array([c[0] / l, c[1] / l, c[2] / l])
+
+        f0_from_ior = ((ior - 1)/(ior + 1))**2
+        lum_specular_color = luminance(specular_color_factor)
+        blender_specular = ((lum_specular_color - transmission_factor) / (1 - transmission_factor)) * (1 / 0.08) * f0_from_ior
+        blender_specular_tint = luminance((normalize(specular_color_factor) - 1) / (normalize(base_color) - 1))
+        if blender_specular_tint < 0 or blender_specular_tint > 1:
+            # TODOExt Warning clamping
+            blender_specular_tint = np.maximum(np.minimum(blender_specular_tint, 1), 0)
+
+        specular_socket.default_value = blender_specular
+        specular_tint_socket.default_value = blender_specular_tint
+        # Note: blender_specular can be greater 1. The Blender documentation permits this.
+
+        return
+    else:
+        # Need to create a texture
+        # First, retrieve and create all images needed
+
+        # Base Color is already created
+        # Transmission is already created
+        # specularTexture is just created by original specular function
+        specular_image_name = None
+        try:
+            pytexture = mh.gltf.data.textures[tex_specular_info.index]
+            pyimg = mh.gltf.data.images[pytexture.source]
+            specular_image_name = pyimg.blender_image_name
+        except:
+            specular_image_name =  None
+
+
+        # specularColorTexture is just created by original specular function
+        specularcolor_image_name = None
+        try:
+            pytexture = mh.gltf.data.textures[tex_specular_color_info.index]
+            pyimg = mh.gltf.data.images[pytexture.source]
+            specularcolor_image_name = pyimg.blender_image_name
+        except:
+            specularcolor_image_name =  None
+
+        stack3 = lambda v: np.dstack([v]*3)
+
+        texts = {
+            base_color_image_name : 'basecolor',
+            transmission_image_name : 'transmission',
+            specularcolor_image_name : 'speccolor',
+            specular_image_name: 'spec'
+        }
+        images = [(name, bpy.data.images[name]) for name in [base_color_image_name, transmission_image_name, specularcolor_image_name, specular_image_name] if name is not None]
+        
+        width = max(image[1].size[0] for image in images)
+        height = max(image[1].size[1] for image in images)
+
+        buffers = {}
+        for name, image in images:
+            tmp_buf = np.empty(width * height * 4, np.float32)
+            
+            if image.size[0] == width and image.size[1] == height:
+                image.pixels.foreach_get(tmp_buf)
+            else:
+                # Image is the wrong size; make a temp copy and scale it.
+                with TmpImageGuard() as guard:
+                    make_temp_image_copy(guard, src_image=image)
+                    tmp_image = guard.image
+                    tmp_image.scale(width, height)
+                    tmp_image.pixels.foreach_get(tmp_buf)
+
+            buffers[texts[name]] = np.reshape(tmp_buf, [width, height, 4])
+            buffers[texts[name]] = buffers[texts[name]][:,:,:3]
+
+            # Manage factors
+            if name == transmission_image_name:
+                buffers[texts[name]] = stack3(buffers[texts[name]][:,:,0])  # Transmission : keep only R channel
+
+                buffers[texts[name]] *= stack3(transmission_factor)
+
+            elif name == base_color_image_name:
+                buffers[texts[name]] *= base_color
+
+            elif name == specularcolor_image_name:
+                buffers[texts[name]] *= specular_color_factor
+
+        # Create buffer if there is no image
+        if 'basecolor' not in buffers.keys():
+            buffers['basecolor'] = np.full((width, height, 3), base_color)
+        if 'transmission' not in buffers.keys():
+            buffers['transmission'] = np.full((width, height, 3), transmission_factor)
+        if 'speccolor' not in buffers.keys():
+            buffers['speccolor'] = np.full((width, height, 3), specular_color_factor)
+
+        # Calculation
+
+        luminance = lambda c: 0.3 * c[:,:,0] + 0.6 * c[:,:,1] + 0.1 * c[:,:,2]
+        def normalize(c):
+            l = luminance(c)
+            if np.all(l == 0.0):
+                return np.array(c)
+            return c / stack3(l)
+
+        f0_from_ior = ((ior - 1)/(ior + 1))**2
+        lum_specular_color = stack3(luminance(buffers['speccolor']))
+        blender_specular = ((lum_specular_color - buffers['transmission']) / (1 - buffers['transmission'])) * (1 / 0.08) * f0_from_ior
+        blender_specular_tint = luminance((normalize(buffers['speccolor']) - 1) / (normalize(buffers['basecolor']) - 1))
+        np.nan_to_num(blender_specular_tint, copy=False)
+        blender_specular_tint = np.clip(blender_specular_tint, 0.0, 1.0)
+        blender_specular_tint = stack3(blender_specular_tint)
+
+        blender_specular = np.dstack((blender_specular, np.ones((width, height)))) # Set alpha to 1
+        blender_specular_tint = np.dstack((blender_specular_tint, np.ones((width, height)))) # Set alpha to 1
+
+        # Check if we really need to create a texture
+        blender_specular_tex_not_needed = np.all(np.isclose(blender_specular, blender_specular[0][0]))
+        blender_specular_tint_tex_not_needed = np.all(np.isclose(blender_specular_tint, blender_specular_tint[0][0]))
+
+        if blender_specular_tex_not_needed == True:
+            lum = lambda c: 0.3 * c[0] + 0.6 * c[1] + 0.1 * c[2]
+            specular_socket.default_value = lum(blender_specular[0][0][:3])
+        else:
+            blender_specular = np.reshape(blender_specular, width * height * 4)
+            # Create images in Blender, width and height are dummy values, then set packed file data
+            blender_image_spec = bpy.data.images.new('Specular', width, height)
+            blender_image_spec.pixels.foreach_set(np.float32(blender_specular))
+            blender_image_spec.pack()
+
+            # Create Textures in Blender
+            tex_info = tex_specular_info
+            if tex_info is None:
+                tex_info = tex_specular_color_info
+            if tex_info is None:
+                tex_info = tex_transmission_info
+            if tex_info is None:
+                tex_info = tex_base_color
+
+            texture(
+                mh,
+                tex_info=tex_info,
+                label='SPECULAR',
+                location=(x_specular, y_specular),
+                is_data=True,
+                color_socket=specular_socket,
+                forced_image=blender_image_spec
+            )
+    
+        if blender_specular_tint_tex_not_needed == True:
+            lum = lambda c: 0.3 * c[0] + 0.6 * c[1] + 0.1 * c[2]
+            specular_tint_socket.default_value = lum(blender_specular_tint[0][0])
+        else:
+            blender_specular_tint = np.reshape(blender_specular_tint, width * height * 4)
+            # Create images in Blender, width and height are dummy values, then set packed file data
+            blender_image_tint = bpy.data.images.new('Specular Tint', width, height)
+            blender_image_tint.pixels.foreach_set(np.float32(blender_specular_tint))
+            blender_image_tint.pack()
+    
+            # Create Textures in Blender
+            tex_info = tex_specular_color_info
+            if tex_info is None:
+                tex_info = tex_specular_info
+            if tex_info is None:
+                tex_info = tex_transmission_info
+            if tex_info is None:
+                tex_info = tex_base_color
+
+            texture(
+                mh,
+                tex_info=tex_info,
+                label='SPECULAR TINT',
+                location=(x_tint, y_tint),
+                is_data=True,
+                color_socket=specular_tint_socket,
+                forced_image=blender_image_tint
+            )
+
+def original_specular(  mh,
+                        specular_factor,
+                        tex_specular_info, 
+                        specular_color_factor, 
+                        tex_specular_color_info,
+                        original_specular_socket,
+                        original_specularcolor_socket,
+                        location_original_specular,
+                        location_original_specularcolor
+                        ):
+
+    x_specular, y_specular = location_original_specular
+    x_specularcolor, y_specularcolor = location_original_specularcolor
+
+    if tex_specular_info is None:
+        original_specular_socket.default_value = specular_factor
+    else:
+        # Mix specular factor
+        if specular_factor != 1.0:
+            node = mh.node_tree.nodes.new('ShaderNodeMath')
+            node.label = 'Specular Factor'
+            node.location = x_specular - 140, y_specular
+            node.operation = 'MULTIPLY'
+            # Outputs
+            mh.node_tree.links.new(original_specular_socket, node.outputs[0])
+            # Inputs
+            original_specular_socket = node.inputs[0]
+            node.inputs[1].default_value = specular_factor
+            x_specular -= 200
+
+        texture(
+            mh,
+            tex_info=tex_specular_info,
+            label='SPECULAR',
+            location=(x_specular, y_specular),
+            is_data=True,
+            color_socket=None,
+            alpha_socket=original_specular_socket
+            )
+
+    if tex_specular_color_info is None:
+        specular_color_factor = list(specular_color_factor)
+        specular_color_factor.extend([1.0])
+        original_specularcolor_socket.default_value = specular_color_factor
+    else:
+            specular_color_factor = list(specular_color_factor) + [1.0]
+            if specular_color_factor != [1.0, 1.0, 1.0, 1.0]:
+                # Mix specularColorFactor
+                node = mh.node_tree.nodes.new('ShaderNodeMixRGB')
+                node.label = 'SpecularColor Factor'
+                node.location = x_specularcolor - 140, y_specularcolor
+                node.blend_type = 'MULTIPLY'
+                # Outputs
+                mh.node_tree.links.new(original_specularcolor_socket, node.outputs[0])
+                # Inputs
+                node.inputs['Fac'].default_value = 1.0
+                original_specularcolor_socket = node.inputs['Color1']
+                node.inputs['Color2'].default_value = specular_color_factor
+                x_specularcolor -= 200
+            
+            texture(
+                mh,
+                tex_info=tex_specular_color_info,
+                label='SPECULAR COLOR',
+                location=(x_specularcolor, y_specularcolor),
+                color_socket=original_specularcolor_socket,
+                )
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_transmission.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_transmission.py
new file mode 100644
index 000000000..dab25d146
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_transmission.py
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+
+from ...io.com.gltf2_io import TextureInfo, MaterialNormalTextureInfoClass
+from .gltf2_blender_texture import texture
+
+
+# [Texture] => [Separate R] => [Transmission Factor] =>
+def transmission(mh, location, transmission_socket):
+    x, y = location
+    try:
+        ext = mh.pymat.extensions['KHR_materials_transmission']
+    except Exception:
+        return
+    transmission_factor = ext.get('transmissionFactor', 0)
+
+    # Default value is 0, so no transmission
+    if transmission_factor == 0:
+        return
+
+    tex_info = ext.get('transmissionTexture')
+    if tex_info is not None:
+        tex_info = TextureInfo.from_dict(tex_info)
+
+    if transmission_socket is None:
+        return
+
+    if tex_info is None:
+        transmission_socket.default_value = transmission_factor
+        return
+
+    # Mix transmission factor
+    if transmission_factor != 1:
+        node = mh.node_tree.nodes.new('ShaderNodeMath')
+        node.label = 'Transmission Factor'
+        node.location = x - 140, y
+        node.operation = 'MULTIPLY'
+        # Outputs
+        mh.node_tree.links.new(transmission_socket, node.outputs[0])
+        # Inputs
+        transmission_socket = node.inputs[0]
+        node.inputs[1].default_value = transmission_factor
+
+        x -= 200
+
+    # Separate RGB
+    node = mh.node_tree.nodes.new('ShaderNodeSeparateColor')
+    node.location = x - 150, y - 75
+    # Outputs
+    mh.node_tree.links.new(transmission_socket, node.outputs['Red'])
+    # Inputs
+    transmission_socket = node.inputs[0]
+
+    x -= 200
+
+    texture(
+        mh,
+        tex_info=tex_info,
+        label='TRANSMISSION',
+        location=(x, y),
+        is_data=True,
+        color_socket=transmission_socket,
+    )
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_unlit.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_unlit.py
index 48ad46fdd..1ffdc7e41 100644
--- a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_unlit.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_unlit.py
@@ -24,12 +24,15 @@ def unlit(mh):
     mh.node_tree.links.new(mix_node.inputs[1], transparent_node.outputs[0])
     mh.node_tree.links.new(mix_node.inputs[2], emission_node.outputs[0])
 
-    _emission_socket, alpha_socket = make_output_nodes(
+    _emission_socket, alpha_socket, _, _ = make_output_nodes(
         mh,
         location=(420, 280) if mh.is_opaque() else (150, 130),
+        additional_location=None, #No additional location needed for Unlit
         shader_socket=mix_node.outputs[0],
         make_emission_socket=False,
         make_alpha_socket=not mh.is_opaque(),
+        make_volume_socket=None, # Not possible to have KHR_materials_volume with unlit
+        make_velvet_socket=None #Not possible to have KHR_materials_sheen with unlit
     )
 
     base_color(
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_volume.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_volume.py
new file mode 100644
index 000000000..f909c7f63
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_volume.py
@@ -0,0 +1,82 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2021 The glTF-Blender-IO authors.
+
+from ...io.com.gltf2_io import TextureInfo, MaterialNormalTextureInfoClass
+from .gltf2_blender_texture import texture
+
+def volume(mh, location, volume_socket, thickness_socket):
+    # implementation based on https://github.com/KhronosGroup/glTF-Blender-IO/issues/1454#issuecomment-928319444
+    try:
+        ext = mh.pymat.extensions['KHR_materials_volume']
+    except Exception:
+        return
+
+    # Attenuation Color
+    attenuationColor = \
+            mh.pymat.extensions['KHR_materials_volume'] \
+            .get('attenuationColor')
+    # glTF is color3, Blender adds alpha
+    if attenuationColor is None:
+        attenuationColor = [1.0, 1.0, 1.0, 1.0]
+    else:
+        attenuationColor.extend([1.0])
+    volume_socket.node.inputs[0].default_value = attenuationColor
+
+    # Attenuation Distance / Density
+    attenuationDistance = mh.pymat.extensions['KHR_materials_volume'].get('attenuationDistance')
+    if attenuationDistance is None:
+        density = 0
+    else:
+        density = 1.0 / attenuationDistance
+    volume_socket.node.inputs[1].default_value = density
+
+    # thicknessFactor / thicknessTexture
+    x, y = location
+    try:
+        ext = mh.pymat.extensions['KHR_materials_volume']
+    except Exception:
+        return
+    thickness_factor = ext.get('thicknessFactor', 0)
+    tex_info = ext.get('thicknessTexture')
+    if tex_info is not None:
+        tex_info = TextureInfo.from_dict(tex_info)
+
+    if thickness_socket is None:
+        return
+
+    if tex_info is None:
+        thickness_socket.default_value = thickness_factor
+        return
+
+    # Mix thickness factor
+    if thickness_factor != 1:
+        node = mh.node_tree.nodes.new('ShaderNodeMath')
+        node.label = 'Thickness Factor'
+        node.location = x - 140, y
+        node.operation = 'MULTIPLY'
+        # Outputs
+        mh.node_tree.links.new(thickness_socket, node.outputs[0])
+        # Inputs
+        thickness_socket = node.inputs[0]
+        node.inputs[1].default_value = thickness_factor
+
+        x -= 200
+
+    # Separate RGB
+    node = mh.node_tree.nodes.new('ShaderNodeSeparateColor')
+    node.location = x - 150, y - 75
+    # Outputs
+    mh.node_tree.links.new(thickness_socket, node.outputs['Green'])
+    # Inputs
+    thickness_socket = node.inputs[0]
+
+    x -= 200
+
+    texture(
+        mh,
+        tex_info=tex_info,
+        label='THICKNESS',
+        location=(x, y),
+        is_data=True,
+        color_socket=thickness_socket,
+    )
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py b/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py
index f25564653..33713b974 100755
--- a/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py
@@ -4,6 +4,8 @@
 import bpy
 from mathutils import Vector, Quaternion, Matrix
 from .gltf2_blender_scene import BlenderScene
+from ..com.gltf2_blender_ui import gltf2_KHR_materials_variants_variant, gltf2_KHR_materials_variants_primitive, gltf2_KHR_materials_variants_default_material
+from .gltf2_blender_material import BlenderMaterial
 
 
 class BlenderGlTF():
@@ -180,6 +182,10 @@ class BlenderGlTF():
 
                     mesh.shapekey_names.append(shapekey_name)
 
+        # Manage KHR_materials_variants
+        BlenderGlTF.manage_material_variants(gltf)
+
+
     @staticmethod
     def find_unused_name(haystack, desired_name):
         """Finds a name not in haystack and <= 63 UTF-8 bytes.
@@ -201,3 +207,25 @@ class BlenderGlTF():
 
             suffix = '.%03d' % cntr
             cntr += 1
+
+
+    @staticmethod
+    def manage_material_variants(gltf):
+        if not (gltf.data.extensions is not None and 'KHR_materials_variants' in gltf.data.extensions.keys()):
+            gltf.KHR_materials_variants = False
+            return
+
+        gltf.KHR_materials_variants = True
+        # If there is no KHR_materials_variants data in scene, create it
+        if bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is False:
+            bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui = True
+            # Setting preferences as dirty, to be sure that option is saved
+            bpy.context.preferences.is_dirty = True
+
+        if len(bpy.data.scenes[0].gltf2_KHR_materials_variants_variants) > 0:
+            bpy.data.scenes[0].gltf2_KHR_materials_variants_variants.clear()
+
+        for idx_variant, variant in enumerate(gltf.data.extensions['KHR_materials_variants']['variants']):
+            var = bpy.data.scenes[0].gltf2_KHR_materials_variants_variants.add()
+            var.name = variant['name']
+            var.variant_idx = idx_variant
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_material.py b/io_scene_gltf2/blender/imp/gltf2_blender_material.py
index 1d18c65d0..9a582f7e6 100755
--- a/io_scene_gltf2/blender/imp/gltf2_blender_material.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_material.py
@@ -48,6 +48,11 @@ class BlenderMaterial():
         else:
             pbr_metallic_roughness(mh)
 
+        # Manage KHR_materials_variants
+        # We need to store link between material idx in glTF and Blender Material id
+        if gltf.KHR_materials_variants is True:
+            gltf.variant_mapping[str(material_idx) + str(vertex_color)] = mat
+
         import_user_extensions('gather_import_material_after_hook', gltf, pymaterial, vertex_color, mat)
 
     @staticmethod
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py b/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py
index a3c1bd349..b886dd258 100755
--- a/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py
@@ -11,6 +11,7 @@ from .gltf2_blender_material import BlenderMaterial
 from ...io.com.gltf2_io_debug import print_console
 from .gltf2_io_draco_compression_extension import decode_primitive
 from io_scene_gltf2.io.imp.gltf2_io_user_extensions import import_user_extensions
+from ..com.gltf2_blender_ui import gltf2_KHR_materials_variants_primitive, gltf2_KHR_materials_variants_variant, gltf2_KHR_materials_variants_default_material
 
 
 class BlenderMesh():
@@ -343,12 +344,19 @@ def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob):
     # ----
     # Assign materials to faces
     has_materials = any(prim.material is not None for prim in pymesh.primitives)
+    # Even if no primitive have material, we need to create slots if some primitives have some variant
+    if has_materials is False:
+        has_materials = any(prim.extensions is not None and 'KHR_materials_variants' in prim.extensions.keys() for prim in pymesh.primitives)
+
+    has_variant = prim.extensions is not None and 'KHR_materials_variants' in prim.extensions.keys() \
+                and 'mappings' in prim.extensions['KHR_materials_variants'].keys()
+
     if has_materials:
         material_indices = np.empty(num_faces, dtype=np.uint32)
         empty_material_slot_index = None
         f = 0
 
-        for prim in pymesh.primitives:
+        for idx_prim, prim in enumerate(pymesh.primitives):
             if prim.material is not None:
                 # Get the material
                 pymaterial = gltf.data.materials[prim.material]
@@ -358,19 +366,55 @@ def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob):
                 material_name = pymaterial.blender_material[vertex_color]
 
                 # Put material in slot (if not there)
-                if material_name not in mesh.materials:
+                if not has_variant:
+                    if material_name not in mesh.materials:
+                        mesh.materials.append(bpy.data.materials[material_name])
+                    material_index = mesh.materials.find(material_name)
+                else:
+                    # In case of variant, do not merge slots
                     mesh.materials.append(bpy.data.materials[material_name])
-                material_index = mesh.materials.find(material_name)
+                    material_index = len(mesh.materials) - 1
             else:
-                if empty_material_slot_index is None:
+                if not has_variant:
+                    if empty_material_slot_index is None:
+                        mesh.materials.append(None)
+                        empty_material_slot_index = len(mesh.materials) - 1
+                    material_index = empty_material_slot_index
+                else:
+                    # In case of variant, do not merge slots
                     mesh.materials.append(None)
-                    empty_material_slot_index = len(mesh.materials) - 1
-                material_index = empty_material_slot_index
+                    material_index = len(mesh.materials) - 1
 
             material_indices[f:f + prim.num_faces].fill(material_index)
 
             f += prim.num_faces
 
+            # Manage variants
+            if has_variant:
+
+                # Store default material
+                default_mat = mesh.gltf2_variant_default_materials.add()
+                default_mat.material_slot_index = material_index
+                default_mat.default_material = bpy.data.materials[material_name] if prim.material is not None else None
+
+                for mapping in prim.extensions['KHR_materials_variants']['mappings']:
+                    # Store, for each variant, the material link to this primitive
+                    
+                    variant_primitive = mesh.gltf2_variant_mesh_data.add()
+                    variant_primitive.material_slot_index = material_index
+                    if 'material' not in mapping.keys():
+                        # Default material
+                        variant_primitive.material = None
+                    else:
+                        vertex_color = 'COLOR_0' if 'COLOR_0' in prim.attributes else None
+                        if str(mapping['material']) + str(vertex_color) not in gltf.variant_mapping.keys():
+                            BlenderMaterial.create(gltf, mapping['material'], vertex_color)
+                        variant_primitive.material = gltf.variant_mapping[str(mapping['material']) + str(vertex_color)]
+
+                    for variant in mapping['variants']:
+                        vari = variant_primitive.variants.add()
+                        vari.variant.variant_idx = variant
+
         mesh.polygons.foreach_set('material_index', material_indices)
 
     # ----
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py b/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py
index 4c32c35d4..6f6279dcf 100755
--- a/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py
@@ -1,13 +1,20 @@
 # SPDX-License-Identifier: Apache-2.0
 # Copyright 2018-2021 The glTF-Blender-IO authors.
 
+from re import M
 import bpy
 from ...io.com.gltf2_io import TextureInfo, MaterialPBRMetallicRoughness
 from ..com.gltf2_blender_material_helpers import get_gltf_node_name, create_settings_group
+from ..com.gltf2_blender_material_helpers import get_gltf_pbr_non_converted_name, create_gltf_pbr_non_converted_group
 from .gltf2_blender_texture import texture
 from .gltf2_blender_KHR_materials_clearcoat import \
     clearcoat, clearcoat_roughness, clearcoat_normal
-
+from .gltf2_blender_KHR_materials_transmission import transmission
+from .gltf2_blender_KHR_materials_ior import ior
+from .gltf2_blender_KHR_materials_volume import volume
+from .gltf2_blender_KHR_materials_specular import specular
+from .gltf2_blender_KHR_materials_sheen import sheen
+from ...io.com.gltf2_io_constants import GLTF_IOR
 
 class MaterialHelper:
     """Helper class. Stores material stuff to be passed around everywhere."""
@@ -20,6 +27,8 @@ class MaterialHelper:
         if pymat.pbr_metallic_roughness is None:
             pymat.pbr_metallic_roughness = \
                 MaterialPBRMetallicRoughness.from_dict({})
+        self.settings_node = None
+        self.original_pbr_node = None
 
     def is_opaque(self):
         alpha_mode = self.pymat.alpha_mode
@@ -36,15 +45,58 @@ def pbr_metallic_roughness(mh: MaterialHelper):
     """Creates node tree for pbrMetallicRoughness materials."""
     pbr_node = mh.node_tree.nodes.new('ShaderNodeBsdfPrincipled')
     pbr_node.location = 10, 300
+    additional_location = 40, -370 # For occlusion and/or volume / original PBR extensions
+
+    # Set IOR to 1.5, this is the default in glTF
+    # This value may be overidden later if IOR extension is set on file
+    pbr_node.inputs['IOR'].default_value = GLTF_IOR
 
-    make_output_nodes(
+    if mh.pymat.occlusion_texture is not None:
+        if mh.settings_node is None:
+            mh.settings_node = make_settings_node(mh)
+            mh.settings_node.location = additional_location
+            mh.settings_node.width = 180
+            additional_location = additional_location[0], additional_location[1] - 150
+
+    need_volume_node = False
+    if mh.pymat.extensions and 'KHR_materials_volume' in mh.pymat.extensions:
+        if 'thicknessFactor' in mh.pymat.extensions['KHR_materials_volume'] \
+            and mh.pymat.extensions['KHR_materials_volume']['thicknessFactor'] != 0.0:
+
+            need_volume_node = True
+
+            # We also need glTF Settings Node, to set thicknessFactor and thicknessTexture
+            mh.settings_node = make_settings_node(mh)
+            mh.settings_node.location = additional_location
+            mh.settings_node.width = 180
+            volume_location = additional_location
+            additional_location = additional_location[0], additional_location[1] - 150
+
+    need_velvet_node = False
+    if mh.pymat.extensions and 'KHR_materials_sheen' in mh.pymat.extensions:
+        need_velvet_node = True
+
+    _, _, volume_socket, velvet_node = make_output_nodes(
         mh,
         location=(250, 260),
+        additional_location=additional_location,
         shader_socket=pbr_node.outputs[0],
-        make_emission_socket=False,
-        make_alpha_socket=False,
+        make_emission_socket=False, # is managed by Principled shader node
+        make_alpha_socket=False, # is managed by Principled shader node
+        make_volume_socket=need_volume_node,
+        make_velvet_socket=need_velvet_node
     )
 
+    if mh.pymat.extensions and 'KHR_materials_specular' in mh.pymat.extensions:
+        # We need glTF PBR Non Converted Extensions Node
+        mh.original_pbr_node = make_pbr_non_converted_extensions_node(mh)
+        mh.original_pbr_node.location = additional_location
+        mh.original_pbr_node.width = 180
+        additional_location = additional_location[0], additional_location[1] - 150
+
+    if mh.pymat.extensions and 'KHR_materials_sheen':
+        pass #TOTOEXT     
+
     locs = calc_locations(mh)
 
     emission(
@@ -75,13 +127,10 @@ def pbr_metallic_roughness(mh: MaterialHelper):
     )
 
     if mh.pymat.occlusion_texture is not None:
-        node = make_settings_node(mh)
-        node.location = 40, -370
-        node.width = 180
         occlusion(
             mh,
             location=locs['occlusion'],
-            occlusion_socket=node.inputs['Occlusion'],
+            occlusion_socket=mh.settings_node.inputs['Occlusion'],
         )
 
     clearcoat(
@@ -102,6 +151,46 @@ def pbr_metallic_roughness(mh: MaterialHelper):
         normal_socket=pbr_node.inputs['Clearcoat Normal'],
     )
 
+    transmission(
+        mh,
+        location=locs['transmission'],
+        transmission_socket=pbr_node.inputs['Transmission']
+    )
+
+    if need_volume_node:
+        volume(
+            mh,
+            location=locs['volume_thickness'],
+            volume_socket=volume_socket,
+            thickness_socket=mh.settings_node.inputs[1] if mh.settings_node else None
+        )
+
+    specular(
+        mh,
+        location_specular=locs['specularTexture'],
+        location_specular_tint=locs['specularColorTexture'],
+        specular_socket=pbr_node.inputs['Specular'],
+        specular_tint_socket=pbr_node.inputs['Specular Tint'],
+        original_specular_socket=mh.original_pbr_node.inputs[0] if mh.original_pbr_node else None,
+        original_specularcolor_socket=mh.original_pbr_node.inputs[1] if mh.original_pbr_node else None,
+        location_original_specular=locs['original_specularTexture'],
+        location_original_specularcolor=locs['original_specularColorTexture']
+    )
+
+    if need_velvet_node:
+        sheen(
+            mh,
+            location_sheenColor=locs['sheenColorTexture'],
+            location_sheenRoughness=locs['sheenRoughnessTexture'],
+            sheenColor_socket=velvet_node.inputs[0],
+            sheenRoughness_socket=velvet_node.inputs[1]
+        )
+
+    ior(
+        mh,
+        ior_socket=pbr_node.inputs['IOR']
+    )
+
 
 def calc_locations(mh):
     """Calculate locations to place each bit of the node graph at."""
@@ -116,18 +205,53 @@ def calc_locations(mh):
     except Exception:
         clearcoat_ext = {}
 
+    try:
+        transmission_ext = mh.pymat.exntesions['KHR_materials_transmission']
+    except:
+        transmission_ext = {}
+
+    try:
+        volume_ext = mh.pymat.extensions['KHR_materials_volume']
+    except Exception:
+        volume_ext = {}
+
+    try:
+        specular_ext = mh.pymat.extensions['KHR_materials_specular']
+    except:
+        specular_ext = {}
+
+    try:
+        sheen_ext = mh.pymat.extensions['KHR_materials_sheen']
+    except:
+        sheen_ext = {}
+
+    locs['sheenColorTexture'] = (x, y)
+    if 'sheenColorTexture' in sheen_ext:
+        y -= height
+    locs['sheenRoughnessTexture'] = (x, y)
+    if 'sheenRoughnessTexture' in sheen_ext:
+        y -= height
     locs['base_color'] = (x, y)
     if mh.pymat.pbr_metallic_roughness.base_color_texture is not None or mh.vertex_color:
         y -= height
     locs['metallic_roughness'] = (x, y)
     if mh.pymat.pbr_metallic_roughness.metallic_roughness_texture is not None:
         y -= height
+    locs['specularTexture'] = (x, y)
+    if 'specularTexture' in specular_ext:
+        y -= height
+    locs['specularColorTexture'] = (x, y)
+    if 'specularColorTexture' in specular_ext:
+        y -= height
     locs['clearcoat'] = (x, y)
     if 'clearcoatTexture' in clearcoat_ext:
         y -= height
     locs['clearcoat_roughness'] = (x, y)
     if 'clearcoatRoughnessTexture' in clearcoat_ext:
         y -= height
+    locs['transmission'] = (x, y)
+    if 'transmissionTexture' in transmission_ext:
+        y -= height
     locs['emission'] = (x, y)
     if mh.pymat.emissive_texture is not None:
         y -= height
@@ -140,6 +264,22 @@ def calc_locations(mh):
     locs['occlusion'] = (x, y)
     if mh.pymat.occlusion_texture is not None:
         y -= height
+    locs['volume_thickness'] = (x, y)
+    if 'thicknessTexture' in volume_ext:
+        y -= height
+    locs['original_specularTexture'] = (x, y)
+    if 'specularTexture' in specular_ext:
+        y -= height
+    locs['original_specularColorTexture'] = (x, y)
+    if 'specularColorTexture' in specular_ext:
+        y -= height
+    locs['original_sheenColorTexture'] = (x, y)
+    if 'sheenColorTexture' in sheen_ext:
+        y -= height
+    locs['original_sheenRoughnessTexture'] = (x, y)
+    if 'sheenRoughnessTexture' in sheen_ext:
+        y -= height
+
 
     # Center things
     total_height = -y
@@ -157,21 +297,29 @@ def calc_locations(mh):
 
 
 # [Texture] => [Emissive Factor] =>
-def emission(mh: MaterialHelper, location, color_socket, strength_socket=None):
+def emission(mh: MaterialHelper, location, color_socket, strength_socket):
     x, y = location
     emissive_factor = mh.pymat.emissive_factor or [0, 0, 0]
 
+    strength = 1
+    try:
+        # Get strength from KHR_materials_emissive_strength if exists
+        strength = mh.pymat.extensions['KHR_materials_emissive_strength']['emissiveStrength']
+    except Exception:
+        pass
+
     if color_socket is None:
         return
 
     if mh.pymat.emissive_texture is None:
         color_socket.default_value = emissive_factor + [1]
+        strength_socket.default_value = strength
         return
 
     # Put grayscale emissive factors into the Emission Strength
     e0, e1, e2 = emissive_factor
     if strength_socket and e0 == e1 == e2:
-        strength_socket.default_value = e0
+        strength_socket.default_value = e0 * strength
 
     # Otherwise, use a multiply node for it
     else:
@@ -189,6 +337,8 @@ def emission(mh: MaterialHelper, location, color_socket, strength_socket=None):
 
             x -= 200
 
+        strength_socket.default_value = strength
+
     texture(
         mh,
         tex_info=mh.pymat.emissive_texture,
@@ -470,13 +620,16 @@ def occlusion(mh: MaterialHelper, location, occlusion_socket):
 def make_output_nodes(
     mh: MaterialHelper,
     location,
+    additional_location,
     shader_socket,
     make_emission_socket,
     make_alpha_socket,
+    make_volume_socket,
+    make_velvet_socket, # For sheen
 ):
     """
     Creates the Material Output node and connects shader_socket to it.
-    If requested, it can also create places to hookup the emission/alpha
+    If requested, it can also create places to hookup the emission/alpha.sheen
     in between shader_socket and the Output node too.
 
     :return: a pair containing the sockets you should put emission and alpha
@@ -484,6 +637,7 @@ def make_output_nodes(
     """
     x, y = location
     emission_socket = None
+    velvet_node = None
     alpha_socket = None
 
     # Create an Emission node and add it to the shader.
@@ -512,6 +666,31 @@ def make_output_nodes(
             x += 380
             y += 125
 
+    # Create an Velvet node add add it to the shader
+    # Note that you can not have Emission & Velvet at the same time
+    if make_velvet_socket:
+        # Velvet
+        node = mh.node_tree.nodes.new("ShaderNodeBsdfVelvet")
+        node.location = x + 50, y + 250
+        # Node
+        velvet_node = node
+        # Outputs
+        velvet_output = node.outputs[0]
+
+        # Add
+        node = mh.node_tree.nodes.new('ShaderNodeAddShader')
+        node.location = x + 250, y + 160
+        # Inputs
+        mh.node_tree.links.new(node.inputs[0], velvet_output)
+        mh.node_tree.links.new(node.inputs[1], shader_socket)
+        # Outputs
+        shader_socket = node.outputs[0]
+
+
+        x += 380
+        y += 125
+
+
     # Mix with a Transparent BSDF. Mixing factor is the alpha value.
     if make_alpha_socket:
         # Transparent BSDF
@@ -535,12 +714,23 @@ def make_output_nodes(
         y -= 210
 
     # Material output
-    node = mh.node_tree.nodes.new('ShaderNodeOutputMaterial')
-    node.location = x + 70, y + 10
+    node_output = mh.node_tree.nodes.new('ShaderNodeOutputMaterial')
+    node_output.location = x + 70, y + 10
+
     # Outputs
-    mh.node_tree.links.new(node.inputs[0], shader_socket)
+    mh.node_tree.links.new(node_output.inputs[0], shader_socket)
+
+    # Volume Node
+    volume_socket = None
+    if make_volume_socket:
+        node = mh.node_tree.nodes.new('ShaderNodeVolumeAbsorption')
+        node.location = additional_location
+        # Outputs
+        mh.node_tree.links.new(node_output.inputs[1], node.outputs[0])
+        volume_socket = node.outputs[0]
 
-    return emission_socket, alpha_socket
+
+    return emission_socket, alpha_socket, volume_socket, velvet_node
 
 
 def make_settings_node(mh):
@@ -560,3 +750,21 @@ def get_settings_group():
         # Create a new node group
         gltf_node_group = create_settings_group(gltf_node_group_name)
     return gltf_node_group
+
+def make_pbr_non_converted_extensions_node(mh):
+    """
+    Make a Group node with a hookup for PBR Non Converted Extensions. No effect in Blender, but
+    used to tell the exporter what the original map(s) should be.
+    """
+    node = mh.node_tree.nodes.new('ShaderNodeGroup')
+    node.node_tree = get_pbr_non_converted_extensions_group()
+    return node
+
+def get_pbr_non_converted_extensions_group():
+    gltf_node_group_name = get_gltf_pbr_non_converted_name()
+    if gltf_node_group_name in bpy.data.node_groups:
+        gltf_node_group = bpy.data.node_groups[gltf_node_group_name]
+    else:
+        # Create a new node group
+        gltf_node_group = create_gltf_pbr_non_converted_group(gltf_node_group_name)
+    return gltf_node_group
\ No newline at end of file
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_texture.py b/io_scene_gltf2/blender/imp/gltf2_blender_texture.py
index 24c9df7c5..12e6d5949 100644
--- a/io_scene_gltf2/blender/imp/gltf2_blender_texture.py
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_texture.py
@@ -17,6 +17,7 @@ def texture(
     color_socket,
     alpha_socket=None,
     is_data=False,
+    forced_image=None
 ):
     """Creates nodes for a TextureInfo and hooks up the color/alpha outputs."""
     x, y = location
@@ -36,12 +37,15 @@ def texture(
     tex_img.location = x - 240, y
     tex_img.label = label
     # Get image
-    if pytexture.source is not None:
-        BlenderImage.create(mh.gltf, pytexture.source)
-        pyimg = mh.gltf.data.images[pytexture.source]
-        blender_image_name = pyimg.blender_image_name
-        if blender_image_name:
-            tex_img.image = bpy.data.images[blender_image_name]
+    if forced_image is None:
+        if pytexture.source is not None:
+            BlenderImage.create(mh.gltf, pytexture.source)
+            pyimg = mh.gltf.data.images[pytexture.source]
+            blender_image_name = pyimg.blender_image_name
+            if blender_image_name:
+                tex_img.image = bpy.data.images[blender_image_name]
+    else:
+        tex_img.image = forced_image
     # Set colorspace for data images
     if is_data:
         if tex_img.image:
@@ -49,7 +53,8 @@ def texture(
     # Set filtering
     set_filtering(tex_img, pysampler)
     # Outputs
-    mh.node_tree.links.new(color_socket, tex_img.outputs['Color'])
+    if color_socket is not None:
+        mh.node_tree.links.new(color_socket, tex_img.outputs['Color'])
     if alpha_socket is not None:
         mh.node_tree.links.new(alpha_socket, tex_img.outputs['Alpha'])
     # Inputs
diff --git a/io_scene_gltf2/io/com/gltf2_io_constants.py b/io_scene_gltf2/io/com/gltf2_io_constants.py
index 19ead516e..175804a37 100755
--- a/io_scene_gltf2/io/com/gltf2_io_constants.py
+++ b/io_scene_gltf2/io/com/gltf2_io_constants.py
@@ -145,3 +145,5 @@ GLTF_DATA_TYPE_VEC4 = "VEC4"
 GLTF_DATA_TYPE_MAT2 = "MAT2"
 GLTF_DATA_TYPE_MAT3 = "MAT3"
 GLTF_DATA_TYPE_MAT4 = "MAT4"
+
+GLTF_IOR = 1.5
\ No newline at end of file
diff --git a/io_scene_gltf2/io/com/gltf2_io_variants.py b/io_scene_gltf2/io/com/gltf2_io_variants.py
new file mode 100644
index 000000000..3824fee40
--- /dev/null
+++ b/io_scene_gltf2/io/com/gltf2_io_variants.py
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2018-2022 The glTF-Blender-IO authors.
+
+from io_scene_gltf2.io.com.gltf2_io import from_dict, from_union, from_none, from_float, from_str, from_list
+from io_scene_gltf2.io.com.gltf2_io import to_float, to_class
+
+class Variant:
+    """defines variant for use with glTF 2.0."""
+    def __init__(self, name, extensions, extras):
+        self.name = name
+        self.extensions = extensions
+        self.extras = extras
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        name = from_union([from_str, from_none], obj.get("name"))
+        extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+                                obj.get("extensions"))
+        extras = obj.get("extras")
+        return Variant(name, extensions, extras)
+
+    def to_dict(self):
+        result = {}
+        result["name"] = from_union([from_str, from_none], self.name)
+        result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+                                          self.extensions)
+        result["extras"] = self.extras
+        return result
diff --git a/io_scene_gltf2/io/imp/gltf2_io_gltf.py b/io_scene_gltf2/io/imp/gltf2_io_gltf.py
index 9f096e691..75ce7265b 100755
--- a/io_scene_gltf2/io/imp/gltf2_io_gltf.py
+++ b/io_scene_gltf2/io/imp/gltf2_io_gltf.py
@@ -28,6 +28,7 @@ class glTFImporter():
         self.accessor_cache = {}
         self.decode_accessor_cache = {}
         self.import_user_extensions = import_settings['import_user_extensions']
+        self.variant_mapping = {} # Used to map between mgltf material idx and blender material, for Variants
 
         if 'loglevel' not in self.import_settings.keys():
             self.import_settings['loglevel'] = logging.ERROR
@@ -44,7 +45,13 @@ class glTFImporter():
             'KHR_texture_transform',
             'KHR_materials_clearcoat',
             'KHR_mesh_quantization',
-            'KHR_draco_mesh_compression'
+            'KHR_draco_mesh_compression',
+            'KHR_materials_variants',
+            'KHR_materials_emissive_strength',
+            'KHR_materials_transmission',
+            'KHR_materials_specular',
+            'KHR_materials_sheen',
+            'KHR_materials_ior'
         ]
 
         # Add extensions required supported by custom import extensions
-- 
GitLab