diff options
Diffstat (limited to 'io_scene_gltf2/blender/imp')
13 files changed, 914 insertions, 38 deletions
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 00000000..ab5b2e7e --- /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 bed63f7f..19a394b9 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 00000000..aa5cef75 --- /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 +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 00000000..3441b5ad --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_specular.py @@ -0,0 +1,356 @@ +# 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 + if not all([i == 0 for i in normalize(base_color) - 1]): + 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) + else: + 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 + if not np.all(normalize(buffers['basecolor']) - 1 == 0.0): + 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) + else: + blender_specular_tint = stack3(np.ones((width, height))) + + 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 00000000..dab25d14 --- /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 48ad46fd..1ffdc7e4 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 00000000..f909c7f6 --- /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 f2556465..33713b97 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_image.py b/io_scene_gltf2/blender/imp/gltf2_blender_image.py index 4f9af799..abce0354 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_image.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_image.py @@ -83,7 +83,7 @@ def create_from_data(gltf, img_idx): img_data = BinaryData.get_image_data(gltf, img_idx) if img_data is None: return - img_name = 'Image_%d' % img_idx + img_name = gltf.data.images[img_idx].name or 'Image_%d' % img_idx # Create image, width and height are dummy values blender_image = bpy.data.images.new(img_name, 8, 8) diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_material.py b/io_scene_gltf2/blender/imp/gltf2_blender_material.py index 1d18c65d..9a582f7e 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 a3c1bd34..b886dd25 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 4c32c35d..b6b8e19f 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py @@ -1,13 +1,19 @@ # 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 .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 +26,7 @@ class MaterialHelper: if pymat.pbr_metallic_roughness is None: pymat.pbr_metallic_roughness = \ MaterialPBRMetallicRoughness.from_dict({}) + self.settings_node = None def is_opaque(self): alpha_mode = self.pymat.alpha_mode @@ -36,15 +43,52 @@ 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 - - make_output_nodes( + 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 overridden later if IOR extension is set on file + pbr_node.inputs['IOR'].default_value = GLTF_IOR + + if mh.pymat.occlusion_texture is not None or (mh.pymat.extensions and 'KHR_materials_specular' in mh.pymat.extensions): + 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 Material Output 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_sheen': + pass #TOTOEXT + locs = calc_locations(mh) emission( @@ -75,13 +119,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 +143,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.settings_node.inputs[2] if mh.settings_node else None, + original_specularcolor_socket=mh.settings_node.inputs[3] if mh.settings_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 +197,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 +256,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 +289,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 +329,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, @@ -466,17 +608,22 @@ def occlusion(mh: MaterialHelper, location, occlusion_socket): ) -# => [Add Emission] => [Mix Alpha] => [Material Output] +# => [Add Emission] => [Mix Alpha] => [Material Output] if needed, only for SpecGlossiness +# => [Volume] => [Add Shader] => [Material Output] if needed +# => [Velvet] => [Add Shader] => [Material Output] if needed 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 +631,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 +660,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 +708,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): diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_texture.py b/io_scene_gltf2/blender/imp/gltf2_blender_texture.py index 24c9df7c..12e6d594 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 |