From ea464f37c07d31d534b510a7e78454ae62a46761 Mon Sep 17 00:00:00 2001 From: Tobias Haeussler Date: Sat, 25 Jul 2020 13:47:08 +0200 Subject: [PATCH 1/7] Add initial support for KHR_materials_specular and KHR_materials_ior. --- .../blender/exp/gltf2_blender_gather_image.py | 63 ++++++++---- .../exp/gltf2_blender_gather_materials.py | 76 +++++++++++++++ .../exp/gltf2_blender_gather_texture.py | 3 +- .../exp/gltf2_blender_gather_texture_info.py | 9 +- .../blender/exp/gltf2_blender_image.py | 96 +++++++++++++++++++ 5 files changed, 224 insertions(+), 23 deletions(-) diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py index 7c1fa8610..82a4b7b37 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py @@ -22,7 +22,7 @@ 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, SpecularColorSource, ExportImage, FillImage 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 @@ -153,27 +153,29 @@ 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] + + is_specular_color = False + for socket in sockets: + if socket.name == 'Specular' or socket.name == 'Specular Tint': + is_specular_color = True + composed_image = ExportImage() for result, socket in zip(results, sockets): - if result.shader_node.image.channels == 0: - gltf2_io_debug.print_console("WARNING", - "Image '{}' has no color channels and cannot be exported.".format( - result.shader_node.image)) - continue - # rudimentarily try follow the node tree to find the correct image data. - src_chan = Channel.R - for elem in result.path: - if isinstance(elem.from_node, bpy.types.ShaderNodeSeparateRGB): - src_chan = { - 'R': Channel.R, - 'G': Channel.G, - 'B': Channel.B, - }[elem.from_socket.name] - if elem.from_socket.name == 'Alpha': - src_chan = Channel.A + if result: + src_chan = Channel.R + for elem in result.path: + if isinstance(elem.from_node, bpy.types.ShaderNodeSeparateRGB): + src_chan = { + 'R': Channel.R, + 'G': Channel.G, + 'B': Channel.B, + }[elem.from_socket.name] + if elem.from_socket.name == 'Alpha': + src_chan = Channel.A dst_chan = None + specular_color_src_type = None # some sockets need channel rewriting (gltf pbr defines fixed channels for some attributes) if socket.name == 'Metallic': @@ -188,7 +190,23 @@ def __get_image_data(sockets, export_settings) -> ExportImage: dst_chan = Channel.R elif socket.name == 'Clearcoat Roughness': dst_chan = Channel.G - + elif is_specular_color and socket.name == 'Specular': + specular_color_src_type = SpecularColorSource.Specular + elif is_specular_color and socket.name == 'Specular Tint': + specular_color_src_type = SpecularColorSource.SpecularTint + elif is_specular_color and socket.name == 'Base Color': + specular_color_src_type = SpecularColorSource.BaseColor + elif is_specular_color and socket.name == 'Transmission': + specular_color_src_type = SpecularColorSource.Transmission + elif is_specular_color and socket.name == 'IOR': + specular_color_src_type = SpecularColorSource.IOR + + if specular_color_src_type is None and result.shader_node.image.channels == 0: + gltf2_io_debug.print_console("WARNING", + "Image '{}' has no color channels and cannot be exported.".format( + result.shader_node.image)) + continue + if dst_chan is not None: composed_image.fill_image(result.shader_node.image, dst_chan, src_chan) @@ -199,8 +217,13 @@ def __get_image_data(sockets, export_settings) -> ExportImage: elif socket.name == 'Roughness' and not composed_image.is_filled(Channel.B): composed_image.fill_white(Channel.B) else: - # copy full image...eventually following sockets might overwrite things - composed_image = ExportImage.from_blender_image(result.shader_node.image) + if specular_color_src_type is not None: + # bake specular color texture + composed_image.fill_specular_color(result.shader_node.image if result else None, specular_color_src_type, socket.default_value) + else: + # copy full image...eventually following sockets might overwrite things + composed_image = ExportImage.from_blender_image(result.shader_node.image) + return composed_image diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py index ea597f90a..3294d3ef3 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py @@ -141,6 +141,13 @@ def __gather_extensions(blender_material, export_settings): if clearcoat_extension: extensions["KHR_materials_clearcoat"] = clearcoat_extension + # KHR_materials_specular and KHR_materials_ior + ior_extension, specular_extension = __gather_ior_and_specular_extensions(blender_material, export_settings) + if ior_extension: + extensions["KHR_materials_ior"] = ior_extension + if specular_extension: + extensions["KHR_materials_specular"] = specular_extension + # TODO KHR_materials_pbrSpecularGlossiness return extensions if extensions else None @@ -284,3 +291,72 @@ def __gather_clearcoat_extension(blender_material, export_settings): ) return Extension('KHR_materials_clearcoat', clearcoat_extension, False) + +def __gather_ior_and_specular_extensions(blender_material, export_settings): + lerp = lambda a, b, v: (1-v)*a + v*b + luminance = lambda c: 0.3 * c[0] + 0.6 * c[1] + 0.1 * c[2] + + specular_ext_enabled = False + ior_ext_enabled = False + + specular_extension = {} + ior_extension = {} + + 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') + + 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 + base_color = base_color_socket.default_value[0:3] if base_color_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 1.0 # textures not supported + + no_texture = (specular_not_linked and specular_tint_not_linked and + (specular_tint == 0.0 or (specular_tint != 0.0 and base_color_not_linked))) + + has_transmission = (transmission_not_linked and transmission > 0) or not transmission_not_linked + if has_transmission: + ior_ext_enabled = True + ior_extension['ior'] = ior + else: + # If there is no transmission/refraction, we are free to choose + # any value for glTF's IOR. Therefore, if specular is overshooting, + # we increase the IOR and adjust specular color accordingly in the + # next step. + if specular_not_linked and specular > 0.5: + ior_ext_enabled = True + ior_extension['ior'] = 1.788789 + + if no_texture: + normalized_base_color = [bc / luminance(base_color) if luminance(base_color) > 0 else 0 for bc in base_color] + if specular != 0.5 or specular_tint != 0.0: + specular_ext_enabled = True + specular_color = [min(lerp(1, bc, specular_tint), 1) for bc in normalized_base_color] + + # The IOR dictates the maximal reflection strength, therefore we need to clamp + # reflection strenth of non-transmissive (aka plastic) fraction (if any) + plastic = [min(1/((ior - 1) / (ior + 1))**2 * 0.08 * specular * sc, 1) for sc in specular_color] + glass = specular_color + specular_extension['specularColorFactor'] = [lerp(plastic[c], glass[c], transmission) for c in range(0,3)] + else: + sockets = (specular_socket, specular_tint_socket, base_color_socket, transmission_socket, ior_socket) + info = gltf2_blender_gather_texture_info.gather_texture_info(sockets, export_settings) + if info is None: + return None + + specular_ext_enabled = True + specular_extension['specularColorTexture'] = info + + ior_extension = Extension('KHR_materials_ior', ior_extension, False) if ior_ext_enabled else None + specular_extension = Extension('KHR_materials_specular', specular_extension, False) if specular_ext_enabled else None + + return ior_extension, specular_extension diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py index 52c2e8b3d..1a7ae204a 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py @@ -72,7 +72,8 @@ 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] + shader_nodes = [tex.shader_node if tex is not None else None for tex in shader_nodes] if len(shader_nodes) > 1: gltf2_io_debug.print_console("WARNING", "More than one shader node tex image used for a texture. " diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py index f42ee0369..6cf2b5614 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py @@ -27,11 +27,16 @@ def gather_texture_info( blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket], export_settings): - if not __filter_texture_info(blender_shader_sockets, export_settings): + is_specular_color = False + for socket in blender_shader_sockets: + if socket.name == 'Specular' or socket.name == 'Specular Tint': + is_specular_color = True + + if not is_specular_color and not __filter_texture_info(blender_shader_sockets, export_settings): return None texture_info = gltf2_io.TextureInfo( - extensions=__gather_extensions(blender_shader_sockets, export_settings), + extensions=None if is_specular_color else __gather_extensions(blender_shader_sockets, export_settings), extras=__gather_extras(blender_shader_sockets, export_settings), index=__gather_index(blender_shader_sockets, export_settings), tex_coord=__gather_tex_coord(blender_shader_sockets, export_settings) diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py index ee45784c5..09231f35c 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py @@ -18,6 +18,7 @@ import numpy as np import tempfile import enum +from io_scene_gltf2.io.com.gltf2_io_debug import print_console class Channel(enum.IntEnum): @@ -26,6 +27,13 @@ class Channel(enum.IntEnum): B = 2 A = 3 +class SpecularColorSource(enum.IntEnum): + Specular = 0 + SpecularTint = 1 + BaseColor = 2 + Transmission = 3 + IOR = 4 + # These describe how an ExportImage's channels should be filled. class FillImage: @@ -38,6 +46,54 @@ class FillWhite: """Fills a channel with all ones (1.0).""" pass +class FillSpecularColor: + """Fills a channel of the specular color from a Blender image.""" + def __init__(self): + self.size = None + self.images = {} + self.src_default_values = {} # used if image is None + + def append(self, image: bpy.types.Image, src_type: SpecularColorSource, src_default_value): + self.images[src_type] = image + self.src_default_values[src_type] = src_default_value + + if src_type is SpecularColorSource.IOR and image is not None: + # IOR textures not supported + self.images[SpecularColorSource.IOR] = None + self.src_default_values = 1.0 + + if image is not None: + if self.size is None: + # first image determines size + self.size = image.size + else: + # all other images must have the same size + if self.size[0] != image.size[0] or self.size[1] != image.size[1]: + print_console("WARNING", + "Specular, specular tint, transmission and/or base color textures have different " + "sizes. Textures will be ignored.") + self.images[src_type] = None + + def has_image(self, src_type: SpecularColorSource): + return src_type in self.images and self.images[src_type] is not None + + def image_size(self): + return (self.size[0], self.size[1]) + + def as_buffer(self, src_type: SpecularColorSource): + width, height = self.image_size() + if self.has_image(src_type): + out_buf = np.ones(height * width * 4, np.float32) + self.images[src_type].pixels.foreach_get(out_buf) + out_buf = np.reshape(out_buf, (height, width, 4)) + if src_type == SpecularColorSource.BaseColor: + out_buf = out_buf[:,:,0:3] + else: + out_buf = out_buf[:,:,0] + else: + channels = 3 if src_type == SpecularColorSource.BaseColor else 1 + out_buf = np.full((height, width, channels), self.src_default_values[src_type]) + return out_buf class ExportImage: """Custom image class. @@ -66,6 +122,7 @@ class ExportImage: def __init__(self): self.fills = {} + self.specular_color_fill = None @staticmethod def from_blender_image(image: bpy.types.Image): @@ -80,6 +137,12 @@ def fill_image(self, image: bpy.types.Image, dst_chan: Channel, src_chan: Channe def fill_white(self, dst_chan: Channel): self.fills[dst_chan] = FillWhite() + def fill_specular_color(self, image: bpy.types.Image, src_type: SpecularColorSource, src_default_value): + self.fills[Channel.A] = FillWhite() + if self.specular_color_fill is None: + self.specular_color_fill = FillSpecularColor() + self.specular_color_fill.append(image, src_type, src_default_value) + def is_filled(self, chan: Channel) -> bool: return chan in self.fills @@ -110,6 +173,10 @@ def encode(self, mime_type: Optional[str]) -> bytes: "image/png": "PNG" }.get(mime_type, "PNG") + # Specular color path = we need to bake the specular color image + if self.specular_color_fill is not None: + return self.__encode_specular_color() + # Happy path = we can just use an existing Blender image if self.__on_happy_path(): return self.__encode_happy() @@ -203,6 +270,35 @@ def __encode_from_image(self, image: bpy.types.Image) -> bytes: tmp_image = guard.image return _encode_temp_image(tmp_image, self.file_format) + def __encode_specular_color(self) -> bytes: + lerp = lambda a, b, v: (1-v)*a + v*b + luminance = lambda c: 0.3 * c[:,:,0] + 0.6 * c[:,:,1] + 0.1 * c[:,:,2] + stack3 = lambda v: np.dstack([v]*3) + + ior = self.specular_color_fill.src_default_values[SpecularColorSource.IOR] + + specular_buf = self.specular_color_fill.as_buffer(SpecularColorSource.Specular) + specular_tint_buf = self.specular_color_fill.as_buffer(SpecularColorSource.SpecularTint) + base_color_buf = self.specular_color_fill.as_buffer(SpecularColorSource.BaseColor) + transmission_buf = self.specular_color_fill.as_buffer(SpecularColorSource.Transmission) + + width, height = self.specular_color_fill.image_size() + + normalized_base_color_buf = base_color_buf / stack3(luminance(base_color_buf)) + + has_transmission = np.amax(transmission_buf) > 0 + if not has_transmission and np.amax(specular_buf) > 0.5: + # material is not transmissive, extend specular range by using a high number for IOR + ior = 1.788789 + + sc = np.minimum(lerp(1, normalized_base_color_buf, stack3(specular_tint_buf)), 1) + plastic = np.minimum(1/((ior - 1) / (ior + 1))**2 * 0.08 * stack3(specular_buf) * sc, 1) + glass = sc + out_buf = lerp(plastic, glass, stack3(transmission_buf)) + + out_buf = np.dstack((out_buf, np.ones((height, width)))) + out_buf = np.reshape(out_buf, (width * height * 4)) + return self.__encode_from_numpy_array(out_buf.astype(np.float32), (width, height)) def _encode_temp_image(tmp_image: bpy.types.Image, file_format: str) -> bytes: with tempfile.TemporaryDirectory() as tmpdirname: From 97e240c6856af2f1d70d794fff9b4702c5b03a4e Mon Sep 17 00:00:00 2001 From: Tobias Haeussler Date: Sun, 9 Aug 2020 21:48:08 +0200 Subject: [PATCH 2/7] Fix unit tests. --- .../blender/exp/gltf2_blender_gather_materials.py | 4 ++-- .../blender/exp/gltf2_blender_gather_texture_info.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py index 3294d3ef3..88a7335bd 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py @@ -337,9 +337,9 @@ def __gather_ior_and_specular_extensions(blender_material, export_settings): ior_extension['ior'] = 1.788789 if no_texture: - normalized_base_color = [bc / luminance(base_color) if luminance(base_color) > 0 else 0 for bc in base_color] if specular != 0.5 or specular_tint != 0.0: specular_ext_enabled = True + normalized_base_color = [bc / luminance(base_color) if luminance(base_color) > 0 else 0 for bc in base_color] specular_color = [min(lerp(1, bc, specular_tint), 1) for bc in normalized_base_color] # The IOR dictates the maximal reflection strength, therefore we need to clamp @@ -351,7 +351,7 @@ def __gather_ior_and_specular_extensions(blender_material, export_settings): sockets = (specular_socket, specular_tint_socket, base_color_socket, transmission_socket, ior_socket) info = gltf2_blender_gather_texture_info.gather_texture_info(sockets, export_settings) if info is None: - return None + return None, None specular_ext_enabled = True specular_extension['specularColorTexture'] = info diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py index 6cf2b5614..c2daef17e 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py @@ -29,7 +29,7 @@ def gather_texture_info( export_settings): is_specular_color = False for socket in blender_shader_sockets: - if socket.name == 'Specular' or socket.name == 'Specular Tint': + if socket and (socket.name == 'Specular' or socket.name == 'Specular Tint'): is_specular_color = True if not is_specular_color and not __filter_texture_info(blender_shader_sockets, export_settings): From e4080eb086f7396ab6c3ac3f749b9b547a1ff3dd Mon Sep 17 00:00:00 2001 From: Tobias Haeussler Date: Tue, 3 Nov 2020 19:17:54 +0100 Subject: [PATCH 3/7] Replace NaNs in base color texture with 0. --- addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py index 09231f35c..a521c87f0 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py @@ -285,6 +285,7 @@ def __encode_specular_color(self) -> bytes: width, height = self.specular_color_fill.image_size() normalized_base_color_buf = base_color_buf / stack3(luminance(base_color_buf)) + np.nan_to_num(normalized_base_color_buf, copy=False, nan=0.0) # if luminance in a pixel was zero has_transmission = np.amax(transmission_buf) > 0 if not has_transmission and np.amax(specular_buf) > 0.5: From caf1c2c38791789bb4fbaadb10dc0561488a9442 Mon Sep 17 00:00:00 2001 From: Tobias Haeussler Date: Tue, 3 Nov 2020 19:19:26 +0100 Subject: [PATCH 4/7] Skip invalid texture nodes. This issue occurs if base color is a texture and specular and specular tint are greater 0. --- .../blender/exp/gltf2_blender_gather_texture.py | 4 ++-- .../blender/exp/gltf2_blender_gather_texture_info.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py index 1a7ae204a..4852e4155 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py @@ -73,13 +73,13 @@ def __gather_name(blender_shader_sockets, export_settings): def __gather_sampler(blender_shader_sockets, export_settings): shader_nodes = [__get_tex_from_socket(socket) for socket in blender_shader_sockets] - shader_nodes = [tex.shader_node if tex is not None else None for tex in shader_nodes] 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) diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py index c2daef17e..a834e57f2 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py @@ -88,7 +88,8 @@ def __gather_index(blender_shader_sockets, export_settings): def __gather_tex_coord(blender_shader_sockets, export_settings): - blender_shader_node = __get_tex_from_socket(blender_shader_sockets[0]).shader_node + blender_shader_nodes = [__get_tex_from_socket(socket) for socket in blender_shader_sockets] + blender_shader_node = next(filter(lambda x: x is not None, blender_shader_nodes)).shader_node if len(blender_shader_node.inputs['Vector'].links) == 0: return 0 From 43c6ffbc080c712c88b3eaacda4a0fc862e15bc7 Mon Sep 17 00:00:00 2001 From: Tobias Haeussler Date: Tue, 3 Nov 2020 19:22:12 +0100 Subject: [PATCH 5/7] Fix base color normalization if specular tint is 0. --- .../blender/exp/gltf2_blender_gather_materials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py index 88a7335bd..ab6288c31 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py @@ -316,7 +316,6 @@ def __gather_ior_and_specular_extensions(blender_material, export_settings): 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 - base_color = base_color_socket.default_value[0:3] if base_color_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 1.0 # textures not supported @@ -339,6 +338,7 @@ def __gather_ior_and_specular_extensions(blender_material, export_settings): if no_texture: if specular != 0.5 or specular_tint != 0.0: specular_ext_enabled = True + base_color = base_color_socket.default_value[0:3] if base_color_not_linked else [0, 0, 0] normalized_base_color = [bc / luminance(base_color) if luminance(base_color) > 0 else 0 for bc in base_color] specular_color = [min(lerp(1, bc, specular_tint), 1) for bc in normalized_base_color] From 9c97fb38dc13958fcde006267adffe284d7b1018 Mon Sep 17 00:00:00 2001 From: Tobias Haeussler Date: Sun, 28 Mar 2021 11:22:03 +0200 Subject: [PATCH 6/7] Adapt to recent changes in specular extension: specular color may be greater than 1. --- .../exp/gltf2_blender_gather_materials.py | 21 +++++++------------ .../blender/exp/gltf2_blender_image.py | 10 +++------ 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py index ab6288c31..6ec51288c 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py @@ -317,34 +317,25 @@ def __gather_ior_and_specular_extensions(blender_material, export_settings): 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 1.0 # textures not supported + ior = ior_socket.default_value if ior_not_linked else 1.5 # textures not supported no_texture = (specular_not_linked and specular_tint_not_linked and (specular_tint == 0.0 or (specular_tint != 0.0 and base_color_not_linked))) - has_transmission = (transmission_not_linked and transmission > 0) or not transmission_not_linked - if has_transmission: + if ior != 1.5: ior_ext_enabled = True ior_extension['ior'] = ior - else: - # If there is no transmission/refraction, we are free to choose - # any value for glTF's IOR. Therefore, if specular is overshooting, - # we increase the IOR and adjust specular color accordingly in the - # next step. - if specular_not_linked and specular > 0.5: - ior_ext_enabled = True - ior_extension['ior'] = 1.788789 if no_texture: if specular != 0.5 or specular_tint != 0.0: specular_ext_enabled = True base_color = base_color_socket.default_value[0:3] if base_color_not_linked else [0, 0, 0] normalized_base_color = [bc / luminance(base_color) if luminance(base_color) > 0 else 0 for bc in base_color] - specular_color = [min(lerp(1, bc, specular_tint), 1) for bc in normalized_base_color] + specular_color = [lerp(1, bc, specular_tint) for bc in normalized_base_color] # The IOR dictates the maximal reflection strength, therefore we need to clamp # reflection strenth of non-transmissive (aka plastic) fraction (if any) - plastic = [min(1/((ior - 1) / (ior + 1))**2 * 0.08 * specular * sc, 1) for sc in specular_color] + plastic = [1/((ior - 1) / (ior + 1))**2 * 0.08 * specular * sc for sc in specular_color] glass = specular_color specular_extension['specularColorFactor'] = [lerp(plastic[c], glass[c], transmission) for c in range(0,3)] else: @@ -356,6 +347,10 @@ def __gather_ior_and_specular_extensions(blender_material, export_settings): specular_ext_enabled = True specular_extension['specularColorTexture'] = info + # If specular>0.5, specular color may be >1. As we cannot store values >1 in a byte texture, + # we use the specular color factor to rescale the value range. + specular_extension['specularColorFactor'] = [2, 2, 2] + ior_extension = Extension('KHR_materials_ior', ior_extension, False) if ior_ext_enabled else None specular_extension = Extension('KHR_materials_specular', specular_extension, False) if specular_ext_enabled else None diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py index a521c87f0..e9562d020 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py @@ -286,19 +286,15 @@ def __encode_specular_color(self) -> bytes: normalized_base_color_buf = base_color_buf / stack3(luminance(base_color_buf)) np.nan_to_num(normalized_base_color_buf, copy=False, nan=0.0) # if luminance in a pixel was zero - - has_transmission = np.amax(transmission_buf) > 0 - if not has_transmission and np.amax(specular_buf) > 0.5: - # material is not transmissive, extend specular range by using a high number for IOR - ior = 1.788789 - sc = np.minimum(lerp(1, normalized_base_color_buf, stack3(specular_tint_buf)), 1) - plastic = np.minimum(1/((ior - 1) / (ior + 1))**2 * 0.08 * stack3(specular_buf) * sc, 1) + sc = lerp(1, normalized_base_color_buf, stack3(specular_tint_buf)) + plastic = 1/((ior - 1) / (ior + 1))**2 * 0.08 * stack3(specular_buf) * sc glass = sc out_buf = lerp(plastic, glass, stack3(transmission_buf)) out_buf = np.dstack((out_buf, np.ones((height, width)))) out_buf = np.reshape(out_buf, (width * height * 4)) + out_buf *= 0.5 # specularColorFactor is 2 return self.__encode_from_numpy_array(out_buf.astype(np.float32), (width, height)) def _encode_temp_image(tmp_image: bpy.types.Image, file_format: str) -> bytes: From ec9eb682a622d886eb2dc19881e7783e5b9bc5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=A4u=C3=9Fler?= Date: Sat, 30 Oct 2021 21:33:12 +0200 Subject: [PATCH 7/7] Fix crash when only specular and specular tint are textured. --- .../blender/exp/gltf2_blender_gather_materials.py | 8 ++++---- .../io_scene_gltf2/blender/exp/gltf2_blender_image.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py index b25b82c87..5dfb25046 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py @@ -446,10 +446,10 @@ def __gather_ior_and_specular_extensions(blender_material, export_settings): 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 + if specular_tint_not_linked: + primary_socket = base_color_socket + if base_color_not_linked: + primary_socket = transmission_socket info = gltf2_blender_gather_texture_info.gather_texture_info(primary_socket, sockets, export_settings) if info is None: return None, None diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py index 5a38d7198..325c5ceda 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_image.py @@ -91,8 +91,14 @@ def as_buffer(self, src_type: SpecularColorSource): else: out_buf = out_buf[:,:,0] else: - channels = 3 if src_type == SpecularColorSource.BaseColor else 1 - out_buf = np.full((height, width, channels), self.src_default_values[src_type]) + if src_type == SpecularColorSource.BaseColor: + rgb = [self.src_default_values[src_type][0], + self.src_default_values[src_type][1], + self.src_default_values[src_type][2]] + out_buf = np.full((height, width, 3), rgb) + else: + f = self.src_default_values[src_type] + out_buf = np.full((height, width, 1), f) return out_buf class ExportImage: