]> git.tdb.fi Git - libs/gl.git/blobdiff - blender/io_mspgl/material.py
Make texture channel handling in the Blender exporter more flexible
[libs/gl.git] / blender / io_mspgl / material.py
index a24c09e3b59dd59a05f9c3ba504afd398a1f7fbf..b30a2ef5d90fef6e7ec1aa1337d6fa5002661136 100644 (file)
@@ -1,14 +1,16 @@
 import os
 
-def get_linked_node_and_socket(node_tree, socket):
-       for l in node_tree.links:
-               if socket==l.to_socket:
-                       return (l.from_node, l.from_socket)
-               elif socket==l.from_socket:
-                       return (l.to_node, l.to_socket)
-       return (None, None)
+def compute_render_method_hash(material):
+       descr = ""
+       for m in material.render_methods:
+               if descr:
+                       descr += ","
+               descr += "{}={}".format(m.tag, m.shader)
+       return hash(descr)
 
 def check_group(node_tree, group, func):
+       from .util import get_linked_node_and_socket
+
        output = group.node_tree.nodes["Group Output"]
        from_node, _ = get_linked_node_and_socket(group.node_tree, output.inputs[0])
        if from_node:
@@ -23,26 +25,71 @@ def check_invert_green(node_tree, node):
        elif node.type!='COMBRGB':
                return (None, None)
 
-       green, g_sock = get_linked_node_and_socket(node_tree, node.inputs["G"])
+       from .util import get_linked_node_and_socket
+
+       green, _ = get_linked_node_and_socket(node_tree, node.inputs["G"])
        if not green or green.type!='MATH' or green.operation!='SUBTRACT':
                return (None, None)
-       green, g_sock = get_linked_node_and_socket(node_tree, green.inputs[1])
+       green, _ = get_linked_node_and_socket(node_tree, green.inputs[1])
 
-       red, r_sock = get_linked_node_and_socket(node_tree, node.inputs["R"])
-       blue, b_sock = get_linked_node_and_socket(node_tree, node.inputs["B"])
+       red, _ = get_linked_node_and_socket(node_tree, node.inputs["R"])
+       blue, _ = get_linked_node_and_socket(node_tree, node.inputs["B"])
        if not red or red.type!='SEPRGB' or blue!=red or green!=red:
                return (None, None)
 
        return get_linked_node_and_socket(node_tree, red.inputs["Image"])
 
+def check_additive_blend(node_tree, node):
+       if node.type=='GROUP':
+               return check_group(node_tree, node, check_additive_blend)
+       elif node.type=='ADD_SHADER':
+               from .util import get_linked_node_and_socket
+
+               shader1, _ = get_linked_node_and_socket(node_tree, node.inputs[0])
+               shader2, _ = get_linked_node_and_socket(node_tree, node.inputs[1])
+               if shader1.type=='BSDF_TRANSPARENT':
+                       return get_linked_node_and_socket(node_tree, node.inputs[1])
+               elif shader2.type=='BSDF_TRANSPARENT':
+                       return get_linked_node_and_socket(node_tree, node.inputs[0])
+
+       return (None, None)
+
+def get_unlit_inputs(node_tree, node, additive):
+       from .util import get_linked_node_and_socket
+
+       if node.type=='GROUP':
+               return check_group(node_tree, node, get_unlit_inputs)
+       elif node.type=='MIX_SHADER' and not additive:
+               shader1, _ = get_linked_node_and_socket(node_tree, node.inputs[1])
+               shader2, _ = get_linked_node_and_socket(node_tree, node.inputs[2])
+               if shader1.type=='BSDF_TRANSPARENT' and shader2.type=='EMISSION':
+                       factor_input = node.inputs["Fac"]
+                       factor_from, _ = get_linked_node_and_socket(node_tree, factor_input)
+                       color_input = shader2.inputs["Color"]
+                       color_from, _ = get_linked_node_and_socket(node_tree, color_input)
+                       if factor_from==color_from:
+                               return (color_input, factor_input)
+       elif node.type=='EMISSION':
+               color_input = node.inputs["Color"]
+               if additive:
+                       color_from, _ = get_linked_node_and_socket(node_tree, color_input)
+                       if color_from.type=='MIX_RGB' and color_from.blend_type=='MIX':
+                               mix_factor_input = color_from.inputs["Fac"]
+                               mix_factor_from, _ = get_linked_node_and_socket(node_tree, mix_factor_input)
+                               mix_color_input = color_from.inputs["Color2"]
+                               mix_color_from, _ = get_linked_node_and_socket(node_tree, mix_color_input)
+                               if mix_factor_from==mix_color_from:
+                                       return (mix_color_input, mix_factor_input)
+               return (color_input, None)
+       return (None, None)
+
 class MaterialProperty:
        def __init__(self, keyword, tex_keyword, value):
                self.keyword = keyword
                self.tex_keyword = tex_keyword
                self.value = value
                self.texture = None
-               self.tex_usage = None
-               self.invert_green = False
+               self.tex_channels = None
 
        def set_from_input(self, node_tree, input_socket, alpha_socket=None):
                if self.keyword:
@@ -55,19 +102,22 @@ class MaterialProperty:
                                self.value = input_socket.default_value
 
                if self.tex_keyword:
+                       from .util import get_linked_node_and_socket
+
                        from_node, _ = get_linked_node_and_socket(node_tree, input_socket)
                        alpha_from = None
                        if from_node:
-                               usage = None
+                               channels = None
                                if from_node.type=='NORMAL_MAP':
                                        from_node, _ = get_linked_node_and_socket(node_tree, from_node.inputs["Color"])
                                        invert, _ = check_invert_green(node_tree, from_node)
+                                       channels = ['R', 'G', 'B']
                                        if invert:
                                                from_node = invert
-                                               self.invert_green = True
-                                       usage = 'RGB'
+                                               channels[1] = '~G';
                                elif from_node.type=='RGBTOBW':
                                        from_node, _ = get_linked_node_and_socket(node_tree, from_node.inputs["Color"])
+                                       channels = ['Y']
 
                                if alpha_socket:
                                        alpha_from, _ = get_linked_node_and_socket(node_tree, alpha_socket)
@@ -76,14 +126,14 @@ class MaterialProperty:
 
                                if from_node.type=='TEX_IMAGE':
                                        self.texture = from_node
-                                       if usage:
-                                               self.tex_usage = usage
+                                       if channels:
+                                               self.tex_channels = channels
                                        elif alpha_from:
-                                               self.tex_usage = 'RGBA'
+                                               self.tex_channels = ['R', 'G', 'B', 'A']
                                        elif type(self.value)==tuple:
-                                               self.tex_usage = 'RGB'
+                                               self.tex_channels = ['R', 'G', 'B']
                                        else:
-                                               self.tex_usage = 'GRAY'
+                                               self.tex_channels = ['Y']
                                else:
                                        raise Exception("Unsupported property input node type "+from_node.type)
 
@@ -95,28 +145,36 @@ class Material:
 
                self.render_mode = material.render_mode
                self.technique = material.technique
-               self.shader = material.shader
+               self.render_methods = material.render_methods[:]
+               self.uniforms = material.uniforms[:]
+               self.receive_shadows = material.receive_shadows
+               self.cast_shadows = (material.shadow_method!='NONE')
+               self.blend_type = 'ALPHA' if material.blend_method=='BLEND' else 'NONE'
+               self.image_based_lighting = material.image_based_lighting
 
                if self.render_mode=='EXTERNAL' and not self.technique:
-                       raise Exception("Missing technique with external rendering mode")
-               elif self.render_mode=='CUSTOM' and not self.shader:
-                       raise Exception("Missing shader with custom rendering mode")
-
-               out_node = None
-               for n in material.node_tree.nodes:
-                       if n.type=='OUTPUT_MATERIAL':
-                               out_node = n
-                               break
+                       raise Exception("Invalid configuration on material {}: No technique for external rendering".format(self.name))
+               elif self.render_mode=='CUSTOM' and not self.render_methods:
+                       raise Exception("Invalid configuration on material {}: No render methods for custom rendering".format(self.name))
 
+               out_node = next((n for n in material.node_tree.nodes if n.type=='OUTPUT_MATERIAL'), None)
                if not out_node:
-                       raise Exception("No material output node found")
+                       raise Exception("No output node found on material {}".format(self.name))
+
+               from .util import get_linked_node_and_socket
 
                surface_node, _ = get_linked_node_and_socket(material.node_tree, out_node.inputs["Surface"])
                if not surface_node:
                        if self.render_mode=='BUILTIN':
-                               raise Exception("Empty material can't use builtin rendering mode")
+                               raise Exception("Invalid configuration on material {}: Empty material with builtin rendering".format(self.name))
                        return
-               elif surface_node.type=='BSDF_PRINCIPLED':
+
+               additive_node, _ = check_additive_blend(material.node_tree, surface_node)
+               if additive_node:
+                       self.blend_type = 'ADDITIVE'
+                       surface_node = additive_node
+
+               if surface_node.type=='BSDF_PRINCIPLED':
                        self.type = "pbr"
 
                        base_color = self.create_property("base_color", (0.8, 0.8, 0.8, 1.0))
@@ -130,23 +188,29 @@ class Material:
                        roughness.set_from_input(material.node_tree, surface_node.inputs["Roughness"])
                        normal.set_from_input(material.node_tree, surface_node.inputs["Normal"])
                        emission.set_from_input(material.node_tree, surface_node.inputs["Emission"])
-               elif surface_node.type=='EMISSION':
+               elif surface_node.type=='EMISSION' or surface_node.type=='MIX_SHADER':
+                       color_input, alpha_input = get_unlit_inputs(material.node_tree, surface_node, self.blend_type=='ADDITIVE')
+                       if not color_input:
+                               raise Exception("Unsupported configuration for unlit material {}".format(self.name))
+
                        self.type = "unlit"
 
                        color = self.create_property("color", "texture", (1.0, 1.0, 1.0, 1.0))
 
-                       color.set_from_input(material.node_tree, surface_node.inputs["Color"])
+                       color.set_from_input(material.node_tree, color_input, alpha_input)
+                       if self.blend_type=='ADDITIVE' and alpha_input:
+                               self.blend_type = 'ADDITIVE_ALPHA'
                else:
-                       raise Exception("Unsupported surface node type "+surface_node.type)
+                       raise Exception("Unsupported surface node type {} on material {}".format(surface_node.type, self.name))
 
                sampler_settings = None
                for p in self.properties:
                        if p.texture:
-                               settings = (p.texture.default_filter, p.texture.interpolation, p.texture.use_mipmap, p.texture.max_anisotropy)
+                               settings = (p.texture.interpolation, p.texture.use_mipmap, p.texture.max_anisotropy)
                                if sampler_settings is None:
                                        sampler_settings = settings
                                elif settings!=sampler_settings:
-                                       raise Exception("Conflicting sampler settings in material textures")
+                                       raise Exception("Material {} has conflicting texture sampler settings".format(self.name))
 
        def create_property(self, *args):
                prop = None
@@ -166,18 +230,27 @@ class MaterialAtlas:
                if self.render_mode=='EXTERNAL':
                        raise Exception("Material atlas with external render mode does not make sense")
 
-               self.shader = materials[0].shader
-               if self.shader:
-                       self.name = "material_atlas_"+os.path.splitext(self.shader)[0]
+               if self.render_mode=='CUSTOM':
+                       self.render_methods = materials[0].render_methods
+               else:
+                       self.render_methods = None
+               if self.render_methods:
+                       self.name = "material_atlas_"+os.path.splitext(self.render_methods[0].shader)[0]
                else:
                        self.name = "material_atlas"
+               self.receive_shadows = materials[0].receive_shadows
+               self.cast_shadows = (materials[0].shadow_method!='NONE')
                self.materials = materials
                self.material_names = [m.name for m in self.materials]
+               self.uniforms = None
+               method_hash = compute_render_method_hash(self)
                for m in self.materials:
                        if m.render_mode!=self.render_mode:
                                raise Exception("Conflicting render modes in MaterialAtlas constructor")
-                       if self.render_mode=='CUSTOM' and m.shader!=self.shader:
+                       if self.render_mode=='CUSTOM' and compute_render_method_hash(m)!=method_hash:
                                raise Exception("Conflicting shaders in MaterialAtlas constructor")
+                       if m.receive_shadows!=self.receive_shadows or m.shadow_method!=materials[0].shadow_method:
+                               raise Exception("Conflicting shadow settings in MaterialAtlas constructor")
 
                count = len(self.materials)
                size = 1
@@ -209,10 +282,10 @@ def create_material_atlas(context, material):
        if not material.material_atlas:
                raise Exception("Material is not part of a material atlas")
 
-       shader = material.shader
+       method_hash = compute_render_method_hash(material)
        materials = []
        for m in context.blend_data.materials:
-               if m.material_atlas and m.shader==shader:
+               if m.material_atlas and compute_render_method_hash(m)==method_hash:
                        materials.append(m)
 
        return MaterialAtlas(materials)