Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved Alpha channel and Transparency for exporting JSON material #185

Merged
merged 7 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions io_ogre/ogre/material.py
Original file line number Diff line number Diff line change
Expand Up @@ -837,3 +837,51 @@ def gather_metallic_roughness_texture(mat_wrapper):
return None

return ShaderImageTextureWrapper(node_image)

def gather_alpha_texture(mat_wrapper):
paroj marked this conversation as resolved.
Show resolved Hide resolved

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)


197 changes: 158 additions & 39 deletions io_ogre/ogre/materialv2json.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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))
Expand All @@ -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)
Expand Down Expand Up @@ -159,82 +161,197 @@ 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_src = 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]
diffuse_tex_dst = 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]
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)
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]
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")
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_src = 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
}
#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 "
paroj marked this conversation as resolved.
Show resolved Hide resolved
"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")
paroj marked this conversation as resolved.
Show resolved Hide resolved
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



# Backface culling
datablock["two_sided"] = not material.use_backface_culling

Expand All @@ -245,15 +362,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)
Expand Down Expand Up @@ -289,7 +407,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 dst_filename, src_filename

def copy_textures(self):
"""Copy and/or convert textures from previous prepare_texture() calls"""
Expand Down
Loading