diff options
author | Julien Duroure <julien.duroure@gmail.com> | 2019-04-20 00:06:18 +0300 |
---|---|---|
committer | Julien Duroure <julien.duroure@gmail.com> | 2019-04-20 00:06:18 +0300 |
commit | e49613a5f9c879b1b525e8ac3e3c8af31c3eabcd (patch) | |
tree | d8e799111a5878e86eff2906daeb45d513e02446 | |
parent | e4165cee8b34b216db5ff41b20b9a05991a4b4b7 (diff) |
glTF: better occlusion map management
At import, create a node group where you can link an image texture
At export, use this node group to create correctly the material
TODO: doc update
9 files changed, 142 insertions, 23 deletions
diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index b53db24c..d79acc32 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -15,7 +15,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (0, 9, 1), + "version": (0, 9, 2), 'blender': (2, 80, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', 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 05f35954..b475c3ed 100755 --- a/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py +++ b/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py @@ -57,3 +57,6 @@ def get_base_color_node(node_tree): return None +def get_gltf_node_name(): + return "glTF Settings" + 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 dbee622e..3e905a7d 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py @@ -170,10 +170,12 @@ def __get_image_data(sockets_or_slots, export_settings) -> gltf2_blender_image.E composed_image = gltf2_blender_image.ExportImage.white_image(image.width, image.height) # Change target channel for metallic and roughness. - if elem.to_socket.name == 'Metallic': + if socket.name == 'Metallic': composed_image[2] = image[source_channel] - elif elem.to_socket.name == 'Roughness': + elif socket.name == 'Roughness': composed_image[1] = image[source_channel] + elif socket.name == 'Occlusion' and len(sockets_or_slots) > 2: + composed_image[0] = image[source_channel] else: composed_image.update(image) 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 5fff246a..1242c08d 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py @@ -20,6 +20,7 @@ from io_scene_gltf2.io.com.gltf2_io_extensions import Extension 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_gather_material_normal_texture_info_class from io_scene_gltf2.blender.exp import gltf2_blender_gather_material_occlusion_texture_info_class +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_generate_extras @@ -38,6 +39,8 @@ def gather_material(blender_material, mesh_double_sided, export_settings): if not __filter_material(blender_material, export_settings): return None + orm_texture = __gather_orm_texture(blender_material, export_settings) + material = gltf2_io.Material( alpha_cutoff=__gather_alpha_cutoff(blender_material, export_settings), alpha_mode=__gather_alpha_mode(blender_material, export_settings), @@ -48,8 +51,8 @@ def gather_material(blender_material, mesh_double_sided, export_settings): extras=__gather_extras(blender_material, export_settings), name=__gather_name(blender_material, export_settings), normal_texture=__gather_normal_texture(blender_material, export_settings), - occlusion_texture=__gather_occlusion_texture(blender_material, export_settings), - pbr_metallic_roughness=__gather_pbr_metallic_roughness(blender_material, export_settings) + occlusion_texture=__gather_occlusion_texture(blender_material, orm_texture, export_settings), + pbr_metallic_roughness=__gather_pbr_metallic_roughness(blender_material, orm_texture, export_settings) ) return material @@ -150,7 +153,33 @@ def __gather_normal_texture(blender_material, export_settings): export_settings) -def __gather_occlusion_texture(blender_material, export_settings): +def __gather_orm_texture(blender_material, export_settings): + # Check for the presence of Occlusion, Roughness, Metallic sharing a single image. + # If not fully shared, return None, so the images will be cached and processed separately. + + occlusion = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Occlusion") + if occlusion is None or not __has_image_node_from_socket(occlusion): + occlusion = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "Occlusion") + if occlusion is None or not __has_image_node_from_socket(occlusion): + return None + + metallic_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Metallic") + roughness_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Roughness") + if metallic_socket is None or roughness_socket is None\ + or not __has_image_node_from_socket(metallic_socket)\ + or not __has_image_node_from_socket(roughness_socket): + metallic_roughness = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "MetallicRoughness") + if metallic_roughness is None or not __has_image_node_from_socket(metallic_roughness): + return None + return (occlusion, metallic_roughness, metallic_roughness) + + return (occlusion, roughness_socket, metallic_socket) + +def __gather_occlusion_texture(blender_material, orm_texture, export_settings): + if orm_texture is not None: + return gltf2_blender_gather_material_occlusion_texture_info_class.gather_material_occlusion_texture_info_class( + orm_texture, + export_settings) occlusion = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Occlusion") if occlusion is None: occlusion = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "Occlusion") @@ -159,8 +188,17 @@ def __gather_occlusion_texture(blender_material, export_settings): export_settings) -def __gather_pbr_metallic_roughness(blender_material, export_settings): +def __gather_pbr_metallic_roughness(blender_material, orm_texture, export_settings): return gltf2_blender_gather_materials_pbr_metallic_roughness.gather_material_pbr_metallic_roughness( blender_material, + 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 + 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 67e7fa12..ffbf9914 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 @@ -23,7 +23,7 @@ from io_scene_gltf2.io.com.gltf2_io_debug import print_console @cached -def gather_material_pbr_metallic_roughness(blender_material, export_settings): +def gather_material_pbr_metallic_roughness(blender_material, orm_texture, export_settings): if not __filter_pbr_material(blender_material, export_settings): return None @@ -33,7 +33,7 @@ def gather_material_pbr_metallic_roughness(blender_material, export_settings): extensions=__gather_extensions(blender_material, export_settings), extras=__gather_extras(blender_material, export_settings), metallic_factor=__gather_metallic_factor(blender_material, export_settings), - metallic_roughness_texture=__gather_metallic_roughness_texture(blender_material, export_settings), + metallic_roughness_texture=__gather_metallic_roughness_texture(blender_material, orm_texture, export_settings), roughness_factor=__gather_roughness_factor(blender_material, export_settings) ) @@ -123,17 +123,20 @@ def __gather_metallic_factor(blender_material, export_settings): return None -def __gather_metallic_roughness_texture(blender_material, export_settings): - metallic_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Metallic") - roughness_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Roughness") - - if metallic_socket is None and roughness_socket is None: - metallic_roughness = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "MetallicRoughness") - if metallic_roughness is None: - metallic_roughness = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "MetallicRoughness") - texture_input = (metallic_roughness,) +def __gather_metallic_roughness_texture(blender_material, orm_texture, export_settings): + if orm_texture is not None: + texture_input = orm_texture else: - texture_input = (metallic_socket, roughness_socket) + metallic_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Metallic") + roughness_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Roughness") + + if metallic_socket is None and roughness_socket is None: + metallic_roughness = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "MetallicRoughness") + if metallic_roughness is None: + metallic_roughness = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "MetallicRoughness") + texture_input = (metallic_roughness,) + else: + texture_input = (metallic_socket, roughness_socket) return gltf2_blender_gather_texture_info.gather_texture_info(texture_input, export_settings) diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_get.py b/io_scene_gltf2/blender/exp/gltf2_blender_get.py index 27d8a341..fef2e684 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_get.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_get.py @@ -15,6 +15,7 @@ import bpy from mathutils import Vector, Matrix +from ..com.gltf2_blender_material_helpers import get_gltf_node_name from ...blender.com.gltf2_blender_conversion import texture_transform_blender_to_gltf from io_scene_gltf2.io.com import gltf2_io_debug @@ -73,16 +74,17 @@ def get_socket_or_texture_slot(blender_material: bpy.types.Material, name: str): def get_socket_or_texture_slot_old(blender_material: bpy.types.Material, name: str): """ - For a given material input name, retrieve the corresponding node tree socket in the special glTF Metallic Roughness nodes (which might be deprecated?). + 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/slot :param name: the name of the socket/slot :return: either a blender NodeSocket, if the material is a node tree or a blender Texture otherwise """ + gltf_node_group_name = get_gltf_node_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.startswith('glTF Metallic Roughness')] + (n.node_tree.name.startswith('glTF Metallic Roughness') or 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] diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py b/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py index 7acbb81e..625b5917 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py @@ -169,6 +169,11 @@ class BlenderGlTF(): if 'glossinessFactor' not in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys(): material.extensions['KHR_materials_pbrSpecularGlossiness']['glossinessFactor'] = 1.0 + # images + if gltf.data.images is not None: + for img in gltf.data.images: + img.blender_image_name = None + if gltf.data.nodes is None: # Something is wrong in file, there is no nodes return diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_image.py b/io_scene_gltf2/blender/imp/gltf2_blender_image.py index ed34123e..6e0a9617 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_image.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_image.py @@ -56,7 +56,9 @@ class BlenderImage(): """Image creation.""" img = gltf.data.images[img_idx] - img.blender_image_name = None + if img.blender_image_name is not None: + # Image is already used somewhere + return if gltf.import_settings['import_pack_images'] is False: diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_map_occlusion.py b/io_scene_gltf2/blender/imp/gltf2_blender_map_occlusion.py index 055c2daa..59600c55 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_map_occlusion.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_map_occlusion.py @@ -14,6 +14,7 @@ import bpy from .gltf2_blender_texture import BlenderTextureInfo +from ..com.gltf2_blender_material_helpers import get_gltf_node_name class BlenderOcclusionMap(): @@ -32,9 +33,12 @@ class BlenderOcclusionMap(): """Nodetree creation.""" pymaterial = gltf.data.materials[material_idx] + material = bpy.data.materials[pymaterial.blender_material[vertex_color]] + node_tree = material.node_tree + BlenderTextureInfo.create(gltf, pymaterial.occlusion_texture) - # Pack texture, but doesn't use it for now. Occlusion is calculated from Cycles. + # Pack texture. Occlusion is calculated from Cycles or Eevee, so do nothing if gltf.data.images[gltf.data.textures[ pymaterial.occlusion_texture.index ].source].blender_image_name is not None: @@ -42,3 +46,63 @@ class BlenderOcclusionMap(): pymaterial.occlusion_texture.index ].source].blender_image_name].use_fake_user = True + # Create a fake node group for exporter + gltf_node_group_name = get_gltf_node_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 = bpy.data.node_groups.new(gltf_node_group_name, 'ShaderNodeTree') + gltf_node_group.inputs.new("NodeSocketFloat", "Occlusion") + gltf_node_group.nodes.new('NodeGroupOutput') + gltf_node_group_input = gltf_node_group.nodes.new('NodeGroupInput') + gltf_node_group_input.location = -200, 0 + + # Set the node group inside material node tree + ao_node = node_tree.nodes.new('ShaderNodeGroup') + ao_node.node_tree = gltf_node_group + ao_node.location = 0, 200 + + # Check if the texture node already exists (if used by other parameter metal / roughness) + found = False + for node in [node for node in node_tree.nodes if node.type == "TEX_IMAGE"]: + if gltf.data.images[gltf.data.textures[ + pymaterial.occlusion_texture.index + ].source].blender_image_name == node.image.name: + # This is our image ! + # Retrieve separate node if found + if node.outputs['Color'].is_linked: + for link in node.outputs['Color'].links: + if link.to_node.type == 'SEPRGB': + node_tree.links.new(ao_node.inputs[0], link.to_node.outputs[0]) + found = True + break + + if found == True: + break + + if found == False: + # Need to create the texture node & separate node + mapping = node_tree.nodes.new('ShaderNodeMapping') + uvmap = node_tree.nodes.new('ShaderNodeUVMap') + separate = node_tree.nodes.new('ShaderNodeSeparateRGB') + if pymaterial.occlusion_texture.tex_coord is not None: + uvmap["gltf2_texcoord"] = pymaterial.occlusion_texture.tex_coord # Set custom flag to retrieve TexCoord + else: + uvmap["gltf2_texcoord"] = 0 # TODO set in pre_compute instead of here + + text = node_tree.nodes.new('ShaderNodeTexImage') + if gltf.data.images[ + gltf.data.textures[pymaterial.occlusion_texture.index].source + ].blender_image_name is not None: + text.image = bpy.data.images[gltf.data.images[ + gltf.data.textures[pymaterial.occlusion_texture.index].source + ].blender_image_name] + text.label = 'OCCLUSION' + + # Links + node_tree.links.new(mapping.inputs[0], uvmap.outputs[0]) + node_tree.links.new(text.inputs[0], mapping.outputs[0]) + node_tree.links.new(separate.inputs[0], text.outputs[0]) + node_tree.links.new(ao_node.inputs[0], separate.outputs[0]) + |