From a034edd5179ac0881ce196dfb5d5c8f3e4658311 Mon Sep 17 00:00:00 2001 From: knn217 Date: Tue, 7 Nov 2023 19:31:02 +0700 Subject: [PATCH 1/7] Improved Alpha channel and Transparency for exporting JSON material _Images plugged into "Principled BSDF" 's Alpha will combine with Images at BaseColor as the alpha channel (Since Ogre use 1 image for both color and alpha). _More options for texture's BlendBlock, controlled by Blender's 3 Blend modes: * Alpha clip = just Blend Type: REPLACE * Alpha Blend = a BlendBlock with custom setting, very transparent * Alpha Hashed = a BlendBlock with custom setting but less transparent than Alpha Blend _There was plan for supporting other texture nodes aside from image, but I couldn't find a way to get the node's color matrices --- io_ogre/ogre/material.py | 48 +++++++++++ io_ogre/ogre/materialv2json.py | 150 ++++++++++++++++++++++++--------- 2 files changed, 159 insertions(+), 39 deletions(-) diff --git a/io_ogre/ogre/material.py b/io_ogre/ogre/material.py index 6c42422..df225ab 100644 --- a/io_ogre/ogre/material.py +++ b/io_ogre/ogre/material.py @@ -837,3 +837,51 @@ def gather_metallic_roughness_texture(mat_wrapper): return None return ShaderImageTextureWrapper(node_image) + +def gather_alpha_texture(mat_wrapper): + + material = mat_wrapper.material + + logger.debug("Getting Alpha texture of material: '%s'" % material.name) + + input_name = 'Alpha' + + base_return = (None, 1) + + logger.debug(" + Processing input: '%s'" % input_name) + + if material.use_nodes == False: + logger.warn("Material: '%s' does not use nodes" % material.name) + return base_return + + if 'Principled BSDF' not in material.node_tree.nodes: + logger.warn("Material: '%s' does not have a 'Principled BSDF' node" % material.name) + return base_return + + input = material.node_tree.nodes['Principled BSDF'].inputs[input_name] + + # Check that input is connected to a node + if len(input.links) > 0: + alpha_node = input.links[0].from_node + else: + logger.warn("%s input is not connected" % input_name) + return base_return + + # Check that connected node is of type 'TEX_IMAGE' + if alpha_node.type in ['TEX_IMAGE']: + node_image = alpha_node + alpha = 1.0 + elif alpha_node.type in ['MAP_RANGE']: + # Check that input is connected to an image texture + node_image = alpha_node.inputs[0].links[0].from_node + if node_image.type not in ['TEX_IMAGE']: + logger.warn("map range node has no input texture") + return base_return + alpha = alpha_node.inputs["To Max"].default_value + else: + logger.warn("Connected node '%s' is not of type 'TEX_IMAGE' or 'MAP_RANGE'" % alpha_node.name) + return base_return + + return (ShaderImageTextureWrapper(node_image), alpha) + + diff --git a/io_ogre/ogre/materialv2json.py b/io_ogre/ogre/materialv2json.py index b718f19..bbcfc87 100644 --- a/io_ogre/ogre/materialv2json.py +++ b/io_ogre/ogre/materialv2json.py @@ -18,9 +18,10 @@ from .. import config from .. import util from ..report import Report -from .material import material_name, ShaderImageTextureWrapper, gather_metallic_roughness_texture +from .material import material_name, ShaderImageTextureWrapper, gather_metallic_roughness_texture, gather_alpha_texture from bpy_extras import node_shader_utils import bpy.path +import subprocess logger = logging.getLogger('materialv2json') @@ -52,12 +53,13 @@ def process_materials(self): """Process all the materials, create the output json and copy textures""" if self.separate_files: for mat in self.materials: - datablock = self.generate_pbs_datablock(mat) + datablock, blendblocks = self.generate_pbs_datablock(mat) dst_filename = os.path.join(self.target_path, "{}.material.json".format(material_name(mat))) logger.info("Writing material '{}'".format(dst_filename)) try: with open(dst_filename, 'w') as fp: - json.dump({"pbs": {material_name(mat): datablock}}, fp, indent=2, sort_keys=True) + json.dump({"pbs": {material_name(mat): datablock}, "blendblocks": blendblocks}, fp, indent=2, sort_keys=True) + #json.dump({"pbs": {"blendblocks": blendblocks}}, fp, indent=2, sort_keys=True) Report.materials.append(material_name(mat)) except Exception as e: logger.error("Unable to create material file '{}'".format(dst_filename)) @@ -68,7 +70,7 @@ def process_materials(self): fileblock = {"pbs": {}} for mat in self.materials: logger.info("Preparing material '{}' for file '{}".format(material_name(mat), dst_filename)) - fileblock["pbs"][material_name(mat)] = self.generate_pbs_datablock(mat) + fileblock["pbs"][material_name(mat)], fileblock["blendblocks"] = self.generate_pbs_datablock(mat) try: with open(dst_filename, 'w') as fp: json.dump(fileblock, fp, indent=2, sort_keys=True) @@ -159,82 +161,150 @@ def generate_pbs_datablock(self, material): datablock["diffuse"] = { "value": bsdf.base_color[0:3] } - tex_filename = self.prepare_texture(bsdf.base_color_texture) + diffuse_tex = bsdf.base_color_texture + tex_filename, diffuse_tex_dst = self.prepare_texture(diffuse_tex) if tex_filename: - datablock["diffuse"]["texture"] = tex_filename + datablock["diffuse"]["texture"] = os.path.split(tex_filename)[-1] + datablock["diffuse"]["value"] = [1.0, 1.0, 1.0, 1.0] + diffuse_tex_src = tex_filename + # Set up emissive parameters - tex_filename = self.prepare_texture(bsdf.emission_color_texture) + tex_filename = self.prepare_texture(bsdf.emission_color_texture)[0] if tex_filename: logger.debug("Emissive params") datablock["emissive"] = { "lightmap": False, # bsdf.emission_strength_texture not supported in Blender < 2.9.0 "value": bsdf.emission_color[0:3] } - datablock["emissive"]["texture"] = tex_filename + datablock["emissive"]["texture"] = os.path.split(tex_filename)[-1] # Set up metalness parameters - tex_filename = self.prepare_texture(gather_metallic_roughness_texture(bsdf), channel=2) + tex_filename = self.prepare_texture(gather_metallic_roughness_texture(bsdf), channel=2)[0] logger.debug("Metallic params") datablock["metalness"] = { "value": bsdf.metallic } if tex_filename: - datablock["metalness"]["texture"] = tex_filename + datablock["metalness"]["texture"] = os.path.split(tex_filename)[-1] # Set up normalmap parameters, only if texture is present - tex_filename = self.prepare_texture(bsdf.normalmap_texture) + tex_filename = self.prepare_texture(bsdf.normalmap_texture)[0] if tex_filename: logger.debug("Normalmap params") datablock["normal"] = { "value": bsdf.normalmap_strength } - datablock["normal"]["texture"] = tex_filename + datablock["normal"]["texture"] = os.path.split(tex_filename)[-1] # Set up roughness parameters - tex_filename = self.prepare_texture(gather_metallic_roughness_texture(bsdf), channel=1) + tex_filename = self.prepare_texture(gather_metallic_roughness_texture(bsdf), channel=1)[0] logger.debug("Roughness params") datablock["roughness"] = { "value": bsdf.roughness } if tex_filename: - datablock["roughness"]["texture"] = tex_filename + datablock["roughness"]["texture"] = os.path.split(tex_filename)[-1] # Set up specular parameters logger.debug("Specular params") datablock["specular"] = { "value": material.specular_color[0:3] } - tex_filename = self.prepare_texture(bsdf.specular_texture) + tex_filename = self.prepare_texture(bsdf.specular_texture)[0] if tex_filename: - datablock["specular"]["texture"] = tex_filename + datablock["specular"]["texture"] = os.path.split(tex_filename)[-1] # Set up transparency parameters, only if texture is present logger.debug("Transparency params") - tex_filename = self.prepare_texture(bsdf.alpha_texture) + # Initialize blendblock + blendblocks = {} + alpha_tex, alpha_strength = gather_alpha_texture(bsdf) + tex_filename, alpha_tex_dst = self.prepare_texture(alpha_tex) if tex_filename: - if tex_filename != datablock.get("diffuse", {}).get("texture", None): - logger.warning("Alpha texture on material '{}' is not the same as " - "the diffuse texture! Probably will not work as expected.".format( - material.name)) - datablock["alpha_test"] = ["greater_equal", material.alpha_threshold] - if bsdf.alpha != 1.0: - transparency_mode = "None" # NOTE: This is an arbitrary mapping - if material.blend_method == "CLIP": - transparency_mode = "Fade" - elif material.blend_method == "HASHED": - transparency_mode = "Fade" - elif material.blend_method == "BLEND": - transparency_mode = "Transparent" - - datablock["transparency"] = { - "mode": transparency_mode, - "use_alpha_from_textures": True, # DEFAULT - "value": bsdf.alpha - } + alpha_tex_src = tex_filename + datablock["alpha_test"] = ["greater_equal", material.alpha_threshold, False] + + # Give blendblock common settings + datablock["blendblock"] = ["blendblock_name", "blendblock_name_for_shadows"] + blendblocks["blendblock_name"] = {} + blendblocks["blendblock_name"]["alpha_to_coverage"] = False + blendblocks["blendblock_name"]["blendmask"] = "rgba" + blendblocks["blendblock_name"]["separate_blend"] = False + blendblocks["blendblock_name"]["blend_operation"] = "add" + blendblocks["blendblock_name"]["blend_operation_alpha"] = "add" + # Give blendblock specific settings + if material.blend_method == "CLIP": # CLIP is equivalent to the Ogre BlendBlock of Blend Type: REPLACE + blendblocks["blendblock_name"]["src_blend_factor"] = "one" + blendblocks["blendblock_name"]["dst_blend_factor"] = "zero" + blendblocks["blendblock_name"]["src_alpha_blend_factor"] = "one" + blendblocks["blendblock_name"]["dst_alpha_blend_factor"] = "zero" + elif material.blend_method == "HASHED": # Currently HASHED has no equivalent in Ogre, so we treat this as a stronger Blend + blendblocks["blendblock_name"]["src_blend_factor"] = "one" + blendblocks["blendblock_name"]["dst_blend_factor"] = "dst_colour" # using "dst_colour" give an even clearer result than BLEND + blendblocks["blendblock_name"]["src_alpha_blend_factor"] = "one" + blendblocks["blendblock_name"]["dst_alpha_blend_factor"] = "dst_colour" + elif material.blend_method == "BLEND": # BLEND is equivalent to the Ogre BlendBlock of Blend Type: Transparent Colour + blendblocks["blendblock_name"]["src_blend_factor"] = "one" # "src_colour" give almost invisible result, this setting is clearer + blendblocks["blendblock_name"]["dst_blend_factor"] = "one_minus_src_colour" + blendblocks["blendblock_name"]["src_alpha_blend_factor"] = "one" + blendblocks["blendblock_name"]["dst_alpha_blend_factor"] = "one_minus_src_colour" + + # Add Alpha texture as the alpha channel of the diffuse texure + if ("texture" in datablock["diffuse"]): + if alpha_tex_src != datablock["diffuse"]["texture"]: + logger.warning("The Alpha texture used on material '{}' is not from the same file as " + "the diffuse texture! This is supported, but make sure you used the right Alpha texture!.".format( + material.name)) + + exe = config.get('IMAGE_MAGICK_CONVERT') + diffuse_tex_dst = diffuse_tex_dst.replace(os.path.split(diffuse_tex_dst)[-1], "new_" + os.path.split(diffuse_tex_dst)[-1]) + + cmd = [exe, diffuse_tex_src] + x,y = diffuse_tex.image.size + + cmd.append(alpha_tex_src) + cmd.append('-set') + cmd.append('-channel') + cmd.append('rgb') + #cmd.append('-separate') + cmd.append('+channel') + #cmd.append('-alpha') + #cmd.append('off') + cmd.append('-compose') + cmd.append('copy-opacity') + cmd.append('-composite') + + + if x > config.get('MAX_TEXTURE_SIZE') or y > config.get('MAX_TEXTURE_SIZE'): + cmd.append( '-resize' ) + cmd.append( str(config.get('MAX_TEXTURE_SIZE')) ) + + if diffuse_tex_dst.endswith('.dds'): + cmd.append('-define') + cmd.append('dds:mipmaps={}'.format(config.get('DDS_MIPS'))) + + cmd.append(diffuse_tex_dst) + + logger.debug('image magick: "%s"', ' '.join(cmd)) + subprocess.run(cmd) + + # Point the diffuse texture to the new image + datablock["diffuse"]["texture"] = os.path.split(diffuse_tex_dst)[-1] + else: + logger.debug("Base color and Alpha channel both came from the same image") + + else: + logger.warn("No Alpha texture found, the output will not have an Alpha channel") # UNSUSED IN OGRE datablock["transparency"]["texture"] = tex_filename + datablock["transparency"] = { + "mode": "Transparent", # "Transparent" mode can adjust transparency with "value" + "use_alpha_from_textures": True, # DEFAULT + "value": max(0, min(alpha_strength, 1)) # Transparency strength clamped between [0,1] (0 for fully transparent and 1 for fully opaque) + } + # Backface culling datablock["two_sided"] = not material.use_backface_culling @@ -245,15 +315,16 @@ def generate_pbs_datablock(self, material): datablock.pop("fresnel") # No fresnel if workflow is metallic except KeyError: pass - return datablock + return datablock, blendblocks def prepare_texture(self, tex, channel=None): """Prepare a texture for use channel is None=all channels, 0=red 1=green 2=blue """ + base_return = (None, None) if not (tex and tex.image): - return None + return base_return src_filename = bpy.path.abspath(tex.image.filepath or tex.image.name) dst_filename = bpy.path.basename(src_filename) @@ -289,7 +360,8 @@ def prepare_texture(self, tex, channel=None): else: self.copy_set.add((src_filename, dst_filename)) - return os.path.split(dst_filename)[-1] + #return os.path.split(dst_filename)[-1] + return src_filename, dst_filename def copy_textures(self): """Copy and/or convert textures from previous prepare_texture() calls""" From cdc9d0663cec1075bc544c8fca9f32fc0cf1bd77 Mon Sep 17 00:00:00 2001 From: knn217 Date: Wed, 8 Nov 2023 18:06:19 +0700 Subject: [PATCH 2/7] quick fix some changes in prepare_texture affected the JSON config for metalness and roughness's texture paths --- io_ogre/ogre/materialv2json.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/io_ogre/ogre/materialv2json.py b/io_ogre/ogre/materialv2json.py index bbcfc87..a1159cd 100644 --- a/io_ogre/ogre/materialv2json.py +++ b/io_ogre/ogre/materialv2json.py @@ -162,11 +162,11 @@ def generate_pbs_datablock(self, material): "value": bsdf.base_color[0:3] } diffuse_tex = bsdf.base_color_texture - tex_filename, diffuse_tex_dst = self.prepare_texture(diffuse_tex) + tex_filename, diffuse_tex_src = self.prepare_texture(diffuse_tex) if tex_filename: datablock["diffuse"]["texture"] = os.path.split(tex_filename)[-1] datablock["diffuse"]["value"] = [1.0, 1.0, 1.0, 1.0] - diffuse_tex_src = tex_filename + diffuse_tex_dst = tex_filename # Set up emissive parameters @@ -221,9 +221,8 @@ def generate_pbs_datablock(self, material): # Initialize blendblock blendblocks = {} alpha_tex, alpha_strength = gather_alpha_texture(bsdf) - tex_filename, alpha_tex_dst = self.prepare_texture(alpha_tex) + tex_filename, alpha_tex_src = self.prepare_texture(alpha_tex) if tex_filename: - alpha_tex_src = tex_filename datablock["alpha_test"] = ["greater_equal", material.alpha_threshold, False] # Give blendblock common settings @@ -361,7 +360,7 @@ def prepare_texture(self, tex, channel=None): self.copy_set.add((src_filename, dst_filename)) #return os.path.split(dst_filename)[-1] - return src_filename, dst_filename + return dst_filename, src_filename def copy_textures(self): """Copy and/or convert textures from previous prepare_texture() calls""" From 253ebbbc8f0cceb7faa993d30498082d23f00b68 Mon Sep 17 00:00:00 2001 From: knn217 Date: Wed, 8 Nov 2023 18:53:01 +0700 Subject: [PATCH 3/7] Added support for standalone metalness and roughness textures Users now have another option to plug metalness and roughness textures into the corresponding channels, aside from using the same texture and separate RGB --- io_ogre/ogre/materialv2json.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/io_ogre/ogre/materialv2json.py b/io_ogre/ogre/materialv2json.py index a1159cd..f12f1e6 100644 --- a/io_ogre/ogre/materialv2json.py +++ b/io_ogre/ogre/materialv2json.py @@ -188,6 +188,12 @@ def generate_pbs_datablock(self, material): } if tex_filename: datablock["metalness"]["texture"] = os.path.split(tex_filename)[-1] + datablock["metalness"]["value"] = 0.818 # default mtallic value according to the docs + else: # Support for standalone metallic texture + tex_filename = self.prepare_texture(bsdf.metallic_texture)[0] + if tex_filename: + datablock["metalness"]["texture"] = os.path.split(tex_filename)[-1] + datablock["metalness"]["value"] = 0.818 # Set up normalmap parameters, only if texture is present tex_filename = self.prepare_texture(bsdf.normalmap_texture)[0] @@ -206,6 +212,12 @@ def generate_pbs_datablock(self, material): } if tex_filename: datablock["roughness"]["texture"] = os.path.split(tex_filename)[-1] + datablock["roughness"]["value"] = 1.0 # default roughness value according to the docs + else: # Support for standalone roughness texture + tex_filename = self.prepare_texture(bsdf.roughness_texture)[0] + if tex_filename: + datablock["roughness"]["texture"] = os.path.split(tex_filename)[-1] + datablock["roughness"]["value"] = 1.0 # Set up specular parameters logger.debug("Specular params") From fa57a0ae99b4f68f62239063f7cd0bcdf89ee3a9 Mon Sep 17 00:00:00 2001 From: knn217 Date: Thu, 9 Nov 2023 08:26:13 +0700 Subject: [PATCH 4/7] Changed some Blend settings for CLIP, BLEND and HASHED, also added OPAQUE _BLEND and HASHED generate the ssame settings now _CLIP only enable "alpha_test" _OPAQUE doesn't have alpha_test or transparency --- io_ogre/ogre/materialv2json.py | 50 +++++++++++++++------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/io_ogre/ogre/materialv2json.py b/io_ogre/ogre/materialv2json.py index f12f1e6..149b6fe 100644 --- a/io_ogre/ogre/materialv2json.py +++ b/io_ogre/ogre/materialv2json.py @@ -235,36 +235,34 @@ def generate_pbs_datablock(self, material): alpha_tex, alpha_strength = gather_alpha_texture(bsdf) tex_filename, alpha_tex_src = self.prepare_texture(alpha_tex) if tex_filename: - datablock["alpha_test"] = ["greater_equal", material.alpha_threshold, False] - - # Give blendblock common settings - datablock["blendblock"] = ["blendblock_name", "blendblock_name_for_shadows"] - blendblocks["blendblock_name"] = {} - blendblocks["blendblock_name"]["alpha_to_coverage"] = False - blendblocks["blendblock_name"]["blendmask"] = "rgba" - blendblocks["blendblock_name"]["separate_blend"] = False - blendblocks["blendblock_name"]["blend_operation"] = "add" - blendblocks["blendblock_name"]["blend_operation_alpha"] = "add" + #datablock["alpha_test"] = ["greater_equal", material.alpha_threshold, False] # Give blendblock specific settings - if material.blend_method == "CLIP": # CLIP is equivalent to the Ogre BlendBlock of Blend Type: REPLACE + if material.blend_method == "OPAQUE": # OPAQUE will pass for now + pass + elif material.blend_method == "CLIP": # CLIP enables alpha_test (alpha rejection) + datablock["alpha_test"] = ["greater_equal", material.alpha_threshold, False] + elif material.blend_method in ["HASHED", "BLEND"]: + datablock["transparency"] = { + "mode": "Transparent", + "use_alpha_from_textures": True, # DEFAULT + "value": max(0, min(alpha_strength, 1)) + } + # Give blendblock common settings + datablock["blendblock"] = ["blendblock_name", "blendblock_name_for_shadows"] + blendblocks["blendblock_name"] = {} + blendblocks["blendblock_name"]["alpha_to_coverage"] = False + blendblocks["blendblock_name"]["blendmask"] = "rgba" + blendblocks["blendblock_name"]["separate_blend"] = False + blendblocks["blendblock_name"]["blend_operation"] = "add" + blendblocks["blendblock_name"]["blend_operation_alpha"] = "add" blendblocks["blendblock_name"]["src_blend_factor"] = "one" - blendblocks["blendblock_name"]["dst_blend_factor"] = "zero" + blendblocks["blendblock_name"]["dst_blend_factor"] = "one_minus_src_colour" # using "dst_colour" give an even clearer result than BLEND blendblocks["blendblock_name"]["src_alpha_blend_factor"] = "one" - blendblocks["blendblock_name"]["dst_alpha_blend_factor"] = "zero" - elif material.blend_method == "HASHED": # Currently HASHED has no equivalent in Ogre, so we treat this as a stronger Blend - blendblocks["blendblock_name"]["src_blend_factor"] = "one" - blendblocks["blendblock_name"]["dst_blend_factor"] = "dst_colour" # using "dst_colour" give an even clearer result than BLEND - blendblocks["blendblock_name"]["src_alpha_blend_factor"] = "one" - blendblocks["blendblock_name"]["dst_alpha_blend_factor"] = "dst_colour" - elif material.blend_method == "BLEND": # BLEND is equivalent to the Ogre BlendBlock of Blend Type: Transparent Colour - blendblocks["blendblock_name"]["src_blend_factor"] = "one" # "src_colour" give almost invisible result, this setting is clearer - blendblocks["blendblock_name"]["dst_blend_factor"] = "one_minus_src_colour" - blendblocks["blendblock_name"]["src_alpha_blend_factor"] = "one" blendblocks["blendblock_name"]["dst_alpha_blend_factor"] = "one_minus_src_colour" # Add Alpha texture as the alpha channel of the diffuse texure if ("texture" in datablock["diffuse"]): - if alpha_tex_src != datablock["diffuse"]["texture"]: + if os.path.split(alpha_tex_src)[-1] != datablock["diffuse"]["texture"]: logger.warning("The Alpha texture used on material '{}' is not from the same file as " "the diffuse texture! This is supported, but make sure you used the right Alpha texture!.".format( material.name)) @@ -310,11 +308,7 @@ def generate_pbs_datablock(self, material): logger.warn("No Alpha texture found, the output will not have an Alpha channel") # UNSUSED IN OGRE datablock["transparency"]["texture"] = tex_filename - datablock["transparency"] = { - "mode": "Transparent", # "Transparent" mode can adjust transparency with "value" - "use_alpha_from_textures": True, # DEFAULT - "value": max(0, min(alpha_strength, 1)) # Transparency strength clamped between [0,1] (0 for fully transparent and 1 for fully opaque) - } + # Backface culling datablock["two_sided"] = not material.use_backface_culling From c173d9a5990338d292b6f22529a60f2d56fca674 Mon Sep 17 00:00:00 2001 From: knn217 Date: Thu, 9 Nov 2023 10:05:53 +0700 Subject: [PATCH 5/7] quick fix diffuse value is a vector3 --- io_ogre/ogre/materialv2json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_ogre/ogre/materialv2json.py b/io_ogre/ogre/materialv2json.py index 149b6fe..35fa7aa 100644 --- a/io_ogre/ogre/materialv2json.py +++ b/io_ogre/ogre/materialv2json.py @@ -165,7 +165,7 @@ def generate_pbs_datablock(self, material): tex_filename, diffuse_tex_src = self.prepare_texture(diffuse_tex) if tex_filename: datablock["diffuse"]["texture"] = os.path.split(tex_filename)[-1] - datablock["diffuse"]["value"] = [1.0, 1.0, 1.0, 1.0] + datablock["diffuse"]["value"] = [1.0, 1.0, 1.0] diffuse_tex_dst = tex_filename From a5bf9bf3f518d0c808c4416dfa0be171a6a604c8 Mon Sep 17 00:00:00 2001 From: knn217 Date: Thu, 9 Nov 2023 17:26:13 +0700 Subject: [PATCH 6/7] Added support for combining Alpha texture with Base Color's default value _If you plugged in an Alpha texture, but Principled BSDF's "Base Color" is not plugged in, a new diffuse image will be exported, with color channels from "Base color" values and Alpha channel from the alpha texture. _The new image will only have as many details as your Alpha texture, so it should be used on stuff with detailed Alphas but simple color channels - like hair cards _Quick fix: change the condition of combining diffuse texture and alpha texture (from different file names to different paths) --- io_ogre/ogre/materialv2json.py | 46 ++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/io_ogre/ogre/materialv2json.py b/io_ogre/ogre/materialv2json.py index 35fa7aa..46eb462 100644 --- a/io_ogre/ogre/materialv2json.py +++ b/io_ogre/ogre/materialv2json.py @@ -262,7 +262,7 @@ def generate_pbs_datablock(self, material): # Add Alpha texture as the alpha channel of the diffuse texure if ("texture" in datablock["diffuse"]): - if os.path.split(alpha_tex_src)[-1] != datablock["diffuse"]["texture"]: + if alpha_tex_src != diffuse_tex_src: logger.warning("The Alpha texture used on material '{}' is not from the same file as " "the diffuse texture! This is supported, but make sure you used the right Alpha texture!.".format( material.name)) @@ -285,7 +285,6 @@ def generate_pbs_datablock(self, material): cmd.append('copy-opacity') cmd.append('-composite') - if x > config.get('MAX_TEXTURE_SIZE') or y > config.get('MAX_TEXTURE_SIZE'): cmd.append( '-resize' ) cmd.append( str(config.get('MAX_TEXTURE_SIZE')) ) @@ -303,7 +302,50 @@ def generate_pbs_datablock(self, material): datablock["diffuse"]["texture"] = os.path.split(diffuse_tex_dst)[-1] else: logger.debug("Base color and Alpha channel both came from the same image") + else: + logger.debug("No diffuse texture found, combining alpha channel with Principled BSDF's base color value") + exe = config.get('IMAGE_MAGICK_CONVERT') + alpha_tex_dst = tex_filename + alpha_tex_dst = alpha_tex_dst.replace(os.path.split(alpha_tex_dst)[-1], "new_" + os.path.split(alpha_tex_dst)[-1]) + + cmd = [exe, alpha_tex_src] + x,y = alpha_tex.image.size + cmd.append(alpha_tex_src) + cmd.append('-set') + cmd.append('-channel') + cmd.append('rgb') + #cmd.append('-separate') + cmd.append('+channel') + #cmd.append('-alpha') + #cmd.append('off') + cmd.append('-compose') + cmd.append('copy-opacity') + cmd.append('-composite') + cmd.append('-fill') + cmd.append( + 'rgb(' + str(int(bsdf.base_color[0] * 255)) + + ',' + str(int(bsdf.base_color[1] * 255)) + + ',' + str(int(bsdf.base_color[2] * 255)) + + ')') + cmd.append('-colorize') + cmd.append('100') + + if x > config.get('MAX_TEXTURE_SIZE') or y > config.get('MAX_TEXTURE_SIZE'): + cmd.append( '-resize' ) + cmd.append( str(config.get('MAX_TEXTURE_SIZE')) ) + + if alpha_tex_dst.endswith('.dds'): + cmd.append('-define') + cmd.append('dds:mipmaps={}'.format(config.get('DDS_MIPS'))) + + cmd.append(alpha_tex_dst) + + logger.debug('image magick: "%s"', ' '.join(cmd)) + subprocess.run(cmd) + + # Point the diffuse texture to the new image + datablock["diffuse"]["texture"] = os.path.split(alpha_tex_dst)[-1] else: logger.warn("No Alpha texture found, the output will not have an Alpha channel") # UNSUSED IN OGRE datablock["transparency"]["texture"] = tex_filename From 2ab60abae86c1ef71b32f879b14ba7f5c9290f2c Mon Sep 17 00:00:00 2001 From: knn217 Date: Thu, 9 Nov 2023 23:35:30 +0700 Subject: [PATCH 7/7] Removed Alpha texture utilities The remaining utils are: _Blendblocks types support _Standalone metallic and roughness texture support --- io_ogre/ogre/material.py | 45 -------- io_ogre/ogre/materialv2json.py | 195 ++++++++------------------------- 2 files changed, 44 insertions(+), 196 deletions(-) diff --git a/io_ogre/ogre/material.py b/io_ogre/ogre/material.py index df225ab..d52e0a5 100644 --- a/io_ogre/ogre/material.py +++ b/io_ogre/ogre/material.py @@ -838,50 +838,5 @@ def gather_metallic_roughness_texture(mat_wrapper): return ShaderImageTextureWrapper(node_image) -def gather_alpha_texture(mat_wrapper): - - material = mat_wrapper.material - - logger.debug("Getting Alpha texture of material: '%s'" % material.name) - - input_name = 'Alpha' - - base_return = (None, 1) - - logger.debug(" + Processing input: '%s'" % input_name) - - if material.use_nodes == False: - logger.warn("Material: '%s' does not use nodes" % material.name) - return base_return - - if 'Principled BSDF' not in material.node_tree.nodes: - logger.warn("Material: '%s' does not have a 'Principled BSDF' node" % material.name) - return base_return - - input = material.node_tree.nodes['Principled BSDF'].inputs[input_name] - - # Check that input is connected to a node - if len(input.links) > 0: - alpha_node = input.links[0].from_node - else: - logger.warn("%s input is not connected" % input_name) - return base_return - - # Check that connected node is of type 'TEX_IMAGE' - if alpha_node.type in ['TEX_IMAGE']: - node_image = alpha_node - alpha = 1.0 - elif alpha_node.type in ['MAP_RANGE']: - # Check that input is connected to an image texture - node_image = alpha_node.inputs[0].links[0].from_node - if node_image.type not in ['TEX_IMAGE']: - logger.warn("map range node has no input texture") - return base_return - alpha = alpha_node.inputs["To Max"].default_value - else: - logger.warn("Connected node '%s' is not of type 'TEX_IMAGE' or 'MAP_RANGE'" % alpha_node.name) - return base_return - - return (ShaderImageTextureWrapper(node_image), alpha) diff --git a/io_ogre/ogre/materialv2json.py b/io_ogre/ogre/materialv2json.py index 46eb462..15fe9b4 100644 --- a/io_ogre/ogre/materialv2json.py +++ b/io_ogre/ogre/materialv2json.py @@ -18,10 +18,9 @@ from .. import config from .. import util from ..report import Report -from .material import material_name, ShaderImageTextureWrapper, gather_metallic_roughness_texture, gather_alpha_texture +from .material import material_name, ShaderImageTextureWrapper, gather_metallic_roughness_texture from bpy_extras import node_shader_utils import bpy.path -import subprocess logger = logging.getLogger('materialv2json') @@ -161,196 +160,92 @@ def generate_pbs_datablock(self, material): datablock["diffuse"] = { "value": bsdf.base_color[0:3] } - diffuse_tex = bsdf.base_color_texture - tex_filename, diffuse_tex_src = self.prepare_texture(diffuse_tex) + tex_filename = self.prepare_texture(bsdf.base_color_texture) if tex_filename: - datablock["diffuse"]["texture"] = os.path.split(tex_filename)[-1] - datablock["diffuse"]["value"] = [1.0, 1.0, 1.0] - diffuse_tex_dst = tex_filename - + datablock["diffuse"]["texture"] = tex_filename # Set up emissive parameters - tex_filename = self.prepare_texture(bsdf.emission_color_texture)[0] + tex_filename = self.prepare_texture(bsdf.emission_color_texture) if tex_filename: logger.debug("Emissive params") datablock["emissive"] = { "lightmap": False, # bsdf.emission_strength_texture not supported in Blender < 2.9.0 "value": bsdf.emission_color[0:3] } - datablock["emissive"]["texture"] = os.path.split(tex_filename)[-1] - + datablock["emissive"]["texture"] = tex_filename # Set up metalness parameters - tex_filename = self.prepare_texture(gather_metallic_roughness_texture(bsdf), channel=2)[0] + tex_filename = self.prepare_texture(gather_metallic_roughness_texture(bsdf), channel=2) logger.debug("Metallic params") datablock["metalness"] = { "value": bsdf.metallic } if tex_filename: - datablock["metalness"]["texture"] = os.path.split(tex_filename)[-1] - datablock["metalness"]["value"] = 0.818 # default mtallic value according to the docs + datablock["metalness"]["texture"] = tex_filename else: # Support for standalone metallic texture - tex_filename = self.prepare_texture(bsdf.metallic_texture)[0] + tex_filename = self.prepare_texture(bsdf.metallic_texture) if tex_filename: - datablock["metalness"]["texture"] = os.path.split(tex_filename)[-1] - datablock["metalness"]["value"] = 0.818 + datablock["metalness"]["texture"] = tex_filename # Set up normalmap parameters, only if texture is present - tex_filename = self.prepare_texture(bsdf.normalmap_texture)[0] + tex_filename = self.prepare_texture(bsdf.normalmap_texture) if tex_filename: logger.debug("Normalmap params") datablock["normal"] = { "value": bsdf.normalmap_strength } - datablock["normal"]["texture"] = os.path.split(tex_filename)[-1] + datablock["normal"]["texture"] = tex_filename # Set up roughness parameters - tex_filename = self.prepare_texture(gather_metallic_roughness_texture(bsdf), channel=1)[0] + tex_filename = self.prepare_texture(gather_metallic_roughness_texture(bsdf), channel=1) logger.debug("Roughness params") datablock["roughness"] = { "value": bsdf.roughness } if tex_filename: - datablock["roughness"]["texture"] = os.path.split(tex_filename)[-1] - datablock["roughness"]["value"] = 1.0 # default roughness value according to the docs + datablock["roughness"]["texture"] = tex_filename else: # Support for standalone roughness texture - tex_filename = self.prepare_texture(bsdf.roughness_texture)[0] + tex_filename = self.prepare_texture(bsdf.roughness_texture) if tex_filename: - datablock["roughness"]["texture"] = os.path.split(tex_filename)[-1] - datablock["roughness"]["value"] = 1.0 + datablock["roughness"]["texture"] = tex_filename # Set up specular parameters logger.debug("Specular params") datablock["specular"] = { "value": material.specular_color[0:3] } - tex_filename = self.prepare_texture(bsdf.specular_texture)[0] + tex_filename = self.prepare_texture(bsdf.specular_texture) if tex_filename: - datablock["specular"]["texture"] = os.path.split(tex_filename)[-1] + datablock["specular"]["texture"] = tex_filename # Set up transparency parameters, only if texture is present logger.debug("Transparency params") # Initialize blendblock blendblocks = {} - alpha_tex, alpha_strength = gather_alpha_texture(bsdf) - tex_filename, alpha_tex_src = self.prepare_texture(alpha_tex) - if tex_filename: - #datablock["alpha_test"] = ["greater_equal", material.alpha_threshold, False] - # Give blendblock specific settings - if material.blend_method == "OPAQUE": # OPAQUE will pass for now - pass - elif material.blend_method == "CLIP": # CLIP enables alpha_test (alpha rejection) - datablock["alpha_test"] = ["greater_equal", material.alpha_threshold, False] - elif material.blend_method in ["HASHED", "BLEND"]: - datablock["transparency"] = { - "mode": "Transparent", - "use_alpha_from_textures": True, # DEFAULT - "value": max(0, min(alpha_strength, 1)) - } - # Give blendblock common settings - datablock["blendblock"] = ["blendblock_name", "blendblock_name_for_shadows"] - blendblocks["blendblock_name"] = {} - blendblocks["blendblock_name"]["alpha_to_coverage"] = False - blendblocks["blendblock_name"]["blendmask"] = "rgba" - blendblocks["blendblock_name"]["separate_blend"] = False - blendblocks["blendblock_name"]["blend_operation"] = "add" - blendblocks["blendblock_name"]["blend_operation_alpha"] = "add" - blendblocks["blendblock_name"]["src_blend_factor"] = "one" - blendblocks["blendblock_name"]["dst_blend_factor"] = "one_minus_src_colour" # using "dst_colour" give an even clearer result than BLEND - blendblocks["blendblock_name"]["src_alpha_blend_factor"] = "one" - blendblocks["blendblock_name"]["dst_alpha_blend_factor"] = "one_minus_src_colour" - - # Add Alpha texture as the alpha channel of the diffuse texure - if ("texture" in datablock["diffuse"]): - if alpha_tex_src != diffuse_tex_src: - logger.warning("The Alpha texture used on material '{}' is not from the same file as " - "the diffuse texture! This is supported, but make sure you used the right Alpha texture!.".format( - material.name)) - - exe = config.get('IMAGE_MAGICK_CONVERT') - diffuse_tex_dst = diffuse_tex_dst.replace(os.path.split(diffuse_tex_dst)[-1], "new_" + os.path.split(diffuse_tex_dst)[-1]) - - cmd = [exe, diffuse_tex_src] - x,y = diffuse_tex.image.size - - cmd.append(alpha_tex_src) - cmd.append('-set') - cmd.append('-channel') - cmd.append('rgb') - #cmd.append('-separate') - cmd.append('+channel') - #cmd.append('-alpha') - #cmd.append('off') - cmd.append('-compose') - cmd.append('copy-opacity') - cmd.append('-composite') - - if x > config.get('MAX_TEXTURE_SIZE') or y > config.get('MAX_TEXTURE_SIZE'): - cmd.append( '-resize' ) - cmd.append( str(config.get('MAX_TEXTURE_SIZE')) ) - - if diffuse_tex_dst.endswith('.dds'): - cmd.append('-define') - cmd.append('dds:mipmaps={}'.format(config.get('DDS_MIPS'))) - - cmd.append(diffuse_tex_dst) - - logger.debug('image magick: "%s"', ' '.join(cmd)) - subprocess.run(cmd) - - # Point the diffuse texture to the new image - datablock["diffuse"]["texture"] = os.path.split(diffuse_tex_dst)[-1] - else: - logger.debug("Base color and Alpha channel both came from the same image") - else: - logger.debug("No diffuse texture found, combining alpha channel with Principled BSDF's base color value") - exe = config.get('IMAGE_MAGICK_CONVERT') - alpha_tex_dst = tex_filename - alpha_tex_dst = alpha_tex_dst.replace(os.path.split(alpha_tex_dst)[-1], "new_" + os.path.split(alpha_tex_dst)[-1]) - - cmd = [exe, alpha_tex_src] - x,y = alpha_tex.image.size - - cmd.append(alpha_tex_src) - cmd.append('-set') - cmd.append('-channel') - cmd.append('rgb') - #cmd.append('-separate') - cmd.append('+channel') - #cmd.append('-alpha') - #cmd.append('off') - cmd.append('-compose') - cmd.append('copy-opacity') - cmd.append('-composite') - cmd.append('-fill') - cmd.append( - 'rgb(' + str(int(bsdf.base_color[0] * 255)) - + ',' + str(int(bsdf.base_color[1] * 255)) - + ',' + str(int(bsdf.base_color[2] * 255)) - + ')') - cmd.append('-colorize') - cmd.append('100') - - if x > config.get('MAX_TEXTURE_SIZE') or y > config.get('MAX_TEXTURE_SIZE'): - cmd.append( '-resize' ) - cmd.append( str(config.get('MAX_TEXTURE_SIZE')) ) - - if alpha_tex_dst.endswith('.dds'): - cmd.append('-define') - cmd.append('dds:mipmaps={}'.format(config.get('DDS_MIPS'))) - - cmd.append(alpha_tex_dst) - - logger.debug('image magick: "%s"', ' '.join(cmd)) - subprocess.run(cmd) - - # Point the diffuse texture to the new image - datablock["diffuse"]["texture"] = os.path.split(alpha_tex_dst)[-1] - else: - logger.warn("No Alpha texture found, the output will not have an Alpha channel") - # UNSUSED IN OGRE datablock["transparency"]["texture"] = tex_filename - - + tex_filename = self.prepare_texture(bsdf.alpha_texture) + # Give blendblock specific settings + if material.blend_method == "OPAQUE": # OPAQUE will pass for now + pass + elif material.blend_method == "CLIP": # CLIP enables alpha_test (alpha rejection) + datablock["alpha_test"] = ["greater_equal", material.alpha_threshold, False] + elif material.blend_method in ["HASHED", "BLEND"]: + datablock["transparency"] = { + "mode": "Transparent", + "use_alpha_from_textures": tex_filename != None, # DEFAULT + "value": bsdf.alpha + } + # Give blendblock common settings + datablock["blendblock"] = ["blendblock_name", "blendblock_name_for_shadows"] + blendblocks["blendblock_name"] = {} + blendblocks["blendblock_name"]["alpha_to_coverage"] = False + blendblocks["blendblock_name"]["blendmask"] = "rgba" + blendblocks["blendblock_name"]["separate_blend"] = False + blendblocks["blendblock_name"]["blend_operation"] = "add" + blendblocks["blendblock_name"]["blend_operation_alpha"] = "add" + blendblocks["blendblock_name"]["src_blend_factor"] = "one" + blendblocks["blendblock_name"]["dst_blend_factor"] = "one_minus_src_colour" # using "dst_colour" give an even clearer result than BLEND + blendblocks["blendblock_name"]["src_alpha_blend_factor"] = "one" + blendblocks["blendblock_name"]["dst_alpha_blend_factor"] = "one_minus_src_colour" # Backface culling datablock["two_sided"] = not material.use_backface_culling @@ -369,9 +264,8 @@ def prepare_texture(self, tex, channel=None): channel is None=all channels, 0=red 1=green 2=blue """ - base_return = (None, None) if not (tex and tex.image): - return base_return + return None src_filename = bpy.path.abspath(tex.image.filepath or tex.image.name) dst_filename = bpy.path.basename(src_filename) @@ -399,7 +293,7 @@ def prepare_texture(self, tex, channel=None): if not os.path.isfile(src_filename): logger.error("Cannot find source image: '{}'".format(src_filename)) Report.errors.append("Cannot find source image: '{}'".format(src_filename)) - return + return None if src_format != dst_format or channel is not None: # using extensions to determine filetype? gross @@ -407,8 +301,7 @@ def prepare_texture(self, tex, channel=None): else: self.copy_set.add((src_filename, dst_filename)) - #return os.path.split(dst_filename)[-1] - return dst_filename, src_filename + return os.path.split(dst_filename)[-1] def copy_textures(self): """Copy and/or convert textures from previous prepare_texture() calls"""