diff options
6 files changed, 206 insertions, 24 deletions
diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index 3e09c58f..ab73a475 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, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (1, 4, 30), + "version": (1, 4, 31), 'blender': (2, 90, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', 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 c346a831..f1260940 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py @@ -21,6 +21,7 @@ from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info, gltf2_ 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 @@ -28,7 +29,7 @@ from io_scene_gltf2.io.com.gltf2_io_debug import print_console @cached -def gather_material(blender_material, mesh_double_sided, export_settings): +def gather_material(blender_material, export_settings): """ Gather the material used by the blender primitive. @@ -39,12 +40,16 @@ def gather_material(blender_material, mesh_double_sided, export_settings): if not __filter_material(blender_material, export_settings): return None + mat_unlit = __gather_material_unlit(blender_material, export_settings) + if mat_unlit is not None: + return mat_unlit + 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), - double_sided=__gather_double_sided(blender_material, mesh_double_sided, export_settings), + double_sided=__gather_double_sided(blender_material, export_settings), emissive_factor=__gather_emissive_factor(blender_material, export_settings), emissive_texture=__gather_emissive_texture(blender_material, export_settings), extensions=__gather_extensions(blender_material, export_settings), @@ -92,8 +97,8 @@ def __gather_alpha_mode(blender_material, export_settings): return None -def __gather_double_sided(blender_material, mesh_double_sided, export_settings): - if mesh_double_sided: +def __gather_double_sided(blender_material, export_settings): + if not blender_material.use_backface_culling: return True old_double_sided_socket = gltf2_blender_get.get_socket_old(blender_material, "DoubleSided") @@ -152,11 +157,6 @@ def __gather_emissive_texture(blender_material, export_settings): def __gather_extensions(blender_material, export_settings): extensions = {} - # KHR_materials_unlit - - if gltf2_blender_get.get_socket(blender_material, "Background") is not None: - extensions["KHR_materials_unlit"] = Extension("KHR_materials_unlit", {}, False) - # KHR_materials_clearcoat clearcoat_extension = __gather_clearcoat_extension(blender_material, export_settings) @@ -351,3 +351,38 @@ def __gather_transmission_extension(blender_material, export_settings): transmission_extension['transmissionTexture'] = combined_texture return Extension('KHR_materials_transmission', transmission_extension, False) + + +def __gather_material_unlit(blender_material, export_settings): + gltf2_unlit = gltf2_blender_gather_materials_unlit + + info = gltf2_unlit.detect_shadeless_material(blender_material, export_settings) + if info is None: + return None + + 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), + extensions={"KHR_materials_unlit": Extension("KHR_materials_unlit", {}, required=False)}, + extras=__gather_extras(blender_material, export_settings), + name=__gather_name(blender_material, export_settings), + emissive_factor=None, + emissive_texture=None, + normal_texture=None, + occlusion_texture=None, + + pbr_metallic_roughness=gltf2_io.MaterialPBRMetallicRoughness( + base_color_factor=gltf2_unlit.gather_base_color_factor(info, export_settings), + base_color_texture=gltf2_unlit.gather_base_color_texture(info, export_settings), + metallic_factor=0.0, + roughness_factor=0.9, + metallic_roughness_texture=None, + extensions=None, + extras=None, + ) + ) + + export_user_extensions('gather_material_unlit_hook', export_settings, material, blender_material) + + return material 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 a89b0ca1..a6a28fc4 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 @@ -61,8 +61,6 @@ def __gather_base_color_factor(blender_material, export_settings): base_color_socket = gltf2_blender_get.get_socket(blender_material, "BaseColor") if base_color_socket is None: base_color_socket = gltf2_blender_get.get_socket_old(blender_material, "BaseColorFactor") - if base_color_socket is None: - base_color_socket = gltf2_blender_get.get_socket(blender_material, "Background") if isinstance(base_color_socket, bpy.types.NodeSocket): rgb = gltf2_blender_get.get_factor_from_socket(base_color_socket, kind='RGB') @@ -81,8 +79,6 @@ def __gather_base_color_texture(blender_material, export_settings): base_color_socket = gltf2_blender_get.get_socket(blender_material, "BaseColor") if base_color_socket is None: base_color_socket = gltf2_blender_get.get_socket_old(blender_material, "BaseColor") - if base_color_socket is None: - base_color_socket = gltf2_blender_get.get_socket(blender_material, "Background") alpha_socket = gltf2_blender_get.get_socket(blender_material, "Alpha") if alpha_socket is not None and alpha_socket.is_linked: 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 new file mode 100644 index 00000000..b3012ea0 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_unlit.py @@ -0,0 +1,147 @@ +# Copyright 2018-2019 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info +from io_scene_gltf2.blender.exp import gltf2_blender_get + + +def detect_shadeless_material(blender_material, export_settings): + """Detect if this material is "shadeless" ie. should be exported + with KHR_materials_unlit. Returns None if not. Otherwise, returns + a dict with info from parsing the node tree. + """ + if not blender_material.use_nodes: return None + + # Old Background node detection (unlikely to happen) + bg_socket = gltf2_blender_get.get_socket(blender_material, "Background") + if bg_socket is not None: + return {'rgb_socket': bg_socket} + + # Look for + # * any color socket, connected to... + # * optionally, the lightpath trick, connected to... + # * optionally, a mix-with-transparent (for alpha), connected to... + # * the output node + + info = {} + + for node in blender_material.node_tree.nodes: + if node.type == 'OUTPUT_MATERIAL': + socket = node.inputs[0] + break + else: + return None + + # Be careful not to misidentify a lightpath trick as mix-alpha. + result = __detect_lightpath_trick(socket) + if result is not None: + socket = result['next_socket'] + else: + result = __detect_mix_alpha(socket) + if result is not None: + socket = result['next_socket'] + info['alpha_socket'] = result['alpha_socket'] + + result = __detect_lightpath_trick(socket) + if result is not None: + socket = result['next_socket'] + + # Check if a color socket, or connected to a color socket + if socket.type != 'RGBA': + from_socket = gltf2_blender_get.previous_socket(socket) + if from_socket is None: return None + if from_socket.type != 'RGBA': return None + + info['rgb_socket'] = socket + return info + + +def __detect_mix_alpha(socket): + # Detects this (used for an alpha hookup) + # + # [ Mix ] + # alpha_socket => [Factor ] => socket + # [Transparent] => [Shader ] + # next_socket => [Shader ] + # + # Returns None if not detected. Otherwise, a dict containing alpha_socket + # and next_socket. + prev = gltf2_blender_get.previous_node(socket) + if prev is None or prev.type != 'MIX_SHADER': return None + in1 = gltf2_blender_get.previous_node(prev.inputs[1]) + if in1 is None or in1.type != 'BSDF_TRANSPARENT': return None + return { + 'alpha_socket': prev.inputs[0], + 'next_socket': prev.inputs[2], + } + + +def __detect_lightpath_trick(socket): + # Detects this (used to prevent casting light on other objects) See ex. + # https://blender.stackexchange.com/a/21535/88681 + # + # [ Lightpath ] [ Mix ] + # [ Is Camera Ray] => [Factor ] => socket + # (don't care) => [Shader ] + # next_socket => [ Emission ] => [Shader ] + # + # The Emission node can be omitted. + # Returns None if not detected. Otherwise, a dict containing + # next_socket. + prev = gltf2_blender_get.previous_node(socket) + if prev is None or prev.type != 'MIX_SHADER': return None + in0 = gltf2_blender_get.previous_socket(prev.inputs[0]) + if in0 is None or in0.node.type != 'LIGHT_PATH': return None + if in0.name != 'Is Camera Ray': return None + next_socket = prev.inputs[2] + + # Detect emission + prev = gltf2_blender_get.previous_node(next_socket) + if prev is not None and prev.type == 'EMISSION': + next_socket = prev.inputs[0] + + return {'next_socket': next_socket} + + +def gather_base_color_factor(info, export_settings): + rgb, alpha = None, None + + if 'rgb_socket' in info: + rgb = gltf2_blender_get.get_factor_from_socket(info['rgb_socket'], kind='RGB') + if 'alpha_socket' in info: + alpha = gltf2_blender_get.get_factor_from_socket(info['alpha_socket'], kind='VALUE') + + if rgb is None: rgb = [1.0, 1.0, 1.0] + if alpha is None: alpha = 1.0 + + rgba = [*rgb, alpha] + if rgba == [1, 1, 1, 1]: return None + return rgba + + +def gather_base_color_texture(info, export_settings): + sockets = (info.get('rgb_socket'), info.get('alpha_socket')) + sockets = tuple(s for s in sockets if s is not None) + if sockets: + # NOTE: separate RGB and Alpha textures will not get combined + # 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. + return gltf2_blender_gather_texture_info.gather_texture_info( + sockets[0], + sockets, + export_settings, + ) + return None 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 fd325d48..a87a9db3 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py @@ -52,15 +52,12 @@ def gather_primitives( for internal_primitive in blender_primitives: material_idx = internal_primitive['material'] - double_sided = False material = None if export_settings['gltf_materials'] == "EXPORT": try: blender_material = bpy.data.materials[material_names[material_idx]] - double_sided = not blender_material.use_backface_culling material = gltf2_blender_gather_materials.gather_material(blender_material, - double_sided, export_settings) except IndexError: # no material at that index diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_get.py b/io_scene_gltf2/blender/exp/gltf2_blender_get.py index 6a5152a6..58b835c0 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_get.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_get.py @@ -231,7 +231,7 @@ def get_factor_from_socket(socket, kind): if fac is not None: return fac - node = __previous_node(socket) + node = previous_node(socket) if node is not None: x1, x2 = None, None if kind == 'RGB': @@ -259,7 +259,7 @@ def get_const_from_socket(socket, kind): return socket.default_value # Handle connection to a constant RGB/Value node - prev_node = __previous_node(socket) + prev_node = previous_node(socket) if prev_node is not None: if kind == 'RGB' and prev_node.type == 'RGB': return list(prev_node.outputs[0].default_value)[:3] @@ -269,16 +269,23 @@ def get_const_from_socket(socket, kind): return None -def __previous_node(socket): +def previous_socket(socket): while True: if not socket.is_linked: return None - node = socket.links[0].from_node + from_socket = socket.links[0].from_socket # Skip over reroute nodes - if node.type == 'REROUTE': - socket = node.inputs[0] + if from_socket.node.type == 'REROUTE': + socket = from_socket.node.inputs[0] continue - return node + return from_socket + + +def previous_node(socket): + prev_socket = previous_socket(socket) + if prev_socket is not None: + return prev_socket.node + return None |