瀏覽代碼

rewrite script shader generate code and several improvements in script shader generating

1. change transparent object's default refraction offset to (0.0, 0.0)
2. successfuly generating shader script even if no texture image
provided
3. if image texture node has no coordinate input, default to UV
4. remove glass effection in transparent principled BSDF
5. set a assertion guard in defining output sockets
6. correct emission and transparent bsdf output
7. more safe property check in mix shader
Jason0214 6 年之前
父節點
當前提交
bb3baceca7

+ 2 - 0
io_scene_godot/converters/material/__init__.py

@@ -0,0 +1,2 @@
+"""Converter from Blender material to Godot material"""
+from .material import export_material

+ 3 - 3
io_scene_godot/converters/material.py → io_scene_godot/converters/material/material.py

@@ -7,8 +7,8 @@ tree into a flat bunch of parameters is not trivial. So for someone else:"""
 import logging
 import os
 import bpy
-from .material_node_tree.exporters import export_node_tree
-from ..structures import (
+from .script_shader import export_script_shader
+from ...structures import (
     InternalResource, ExternalResource, gamma_correct, ValidationError)
 
 
@@ -74,7 +74,7 @@ def generate_material_resource(escn_file, export_settings, material):
     if engine == 'CYCLES' and material.node_tree is not None:
         mat = InternalResource("ShaderMaterial", material_rsc_name)
         try:
-            export_node_tree(
+            export_script_shader(
                 escn_file, export_settings, material, mat
             )
         except ValidationError as exception:

+ 1 - 1
io_scene_godot/converters/material_node_tree/__init__.py → io_scene_godot/converters/material/script_shader/__init__.py

@@ -1,3 +1,3 @@
 """Module for export Blender CYCLES and EEVEE material node tree
 to Godot ShaderMaterial"""
-from .exporters import export_node_tree
+from .node_tree import export_script_shader

+ 890 - 0
io_scene_godot/converters/material/script_shader/node_converters.py

@@ -0,0 +1,890 @@
+"""a set of shader node converters responsible for generate"""
+import logging
+from collections import deque
+import re
+import bpy
+import mathutils
+from .shader_links import FragmentShaderLink
+from .shader_functions import (
+    find_node_function, find_function_by_name, node_has_function)
+
+
+def blender_value_to_string(blender_value):
+    """convert blender socket.default_value to shader script"""
+    if isinstance(blender_value,
+                  (bpy.types.bpy_prop_array, mathutils.Vector)):
+        tmp = list()
+        for val in blender_value:
+            tmp.append(str(val))
+
+        return "vec%d(%s)" % (len(tmp), ", ".join(tmp))
+
+    if isinstance(blender_value, mathutils.Matrix):
+        # godot mat is column major order
+        mat = blender_value.transposed()
+        column_vec_list = list()
+        for vec in mat:
+            column_vec_list.append(blender_value_to_string(vec))
+
+        return "mat%d(%s)" % (
+            len(column_vec_list),
+            ", ".join(column_vec_list)
+        )
+
+    return "float(%s)" % blender_value
+
+
+def socket_to_type_string(socket):
+    """return a type string of a blender shader node socket"""
+    if socket.type == 'RGBA':
+        return 'vec4'
+
+    if socket.type == 'VECTOR':
+        return 'vec3'
+
+    if socket.type == 'VALUE':
+        return 'float'
+
+    assert False, 'Unknown type %s' % socket.type
+    return None
+
+
+def filter_id_illegal_char(string):
+    """filter out non-ascii char in string and convert all char
+    to lower case"""
+    return re.sub(r'\W', '', string).lower()
+
+
+def is_normal_texture(image_texture_node):
+    """check whether the texture in a TexImage node is normal texture"""
+    assert image_texture_node.bl_idname == 'ShaderNodeTexImage'
+    node_queue = deque()
+    for link in image_texture_node.outputs['Color'].links:
+        node_queue.append((link.to_node, link.to_socket))
+
+    while node_queue:
+        node, socket = node_queue.popleft()
+        if (socket.name == 'Color' and
+                node.bl_idname == 'ShaderNodeNormalMap'):
+            return True
+        for sock in node.outputs:
+            for link in sock.links:
+                node_queue.append((link.to_node, link.to_socket))
+
+    return False
+
+
+class Texture:
+    """A texture"""
+
+    def __init__(self, bl_image, identifier, hint_normal):
+        # note that image could be None, it need to be safely handled
+        self.image = bl_image
+        self.hint_normal = hint_normal
+        # identifier is the variable name in scripts
+        self.tmp_identifier = identifier
+
+    def __hash__(self):
+        return hash((self.image, self.hint_normal))
+
+    def hint_str(self):
+        """form all the hints into a string"""
+        if self.hint_normal:
+            return ": hint_normal"
+        return ""
+
+
+class ShadingFlags:
+    """flags used in compositing shader scripts from node"""
+
+    def __init__(self):
+        self.transparent = False
+        self.glass = False
+        self.inv_view_mat_used = False
+        self.inv_model_mat_used = False
+        self.uv_or_tangent_used = False
+        self.transmission_used = False
+
+
+class NodeConverterBase:
+    # pylint: disable-msg=too-many-instance-attributes
+    """helper class which wraps a blender shader node and
+    able to generate fragment/vertex script from the node"""
+
+    # please set the flag when use them!
+    INV_MODEL_MAT = "INV_MODEL_MAT"
+    INV_VIEW_MAT = "INV_VIEW_MAT"
+
+    def __init__(self, index, bl_node):
+        self.in_sockets_map = dict()
+        self.out_sockets_map = dict()
+
+        self._defined_ids = set()
+
+        self.functions = set()
+        self.textures = list()
+        self.local_code = list()
+        self.input_definitions = list()
+        self.output_definitions = list()
+
+        self.input_definitions.append("// input sockets handling")
+        self.output_definitions.append("// output sockets definitions")
+        self.local_code.append("\n")
+
+        # flags
+        self.flags = ShadingFlags()
+
+        self.bl_node = bl_node
+        self._id_prefix = "node%s_" % index
+
+        self.variable_count = 0
+        self.input_var_count = 0
+        self.output_var_count = 0
+
+    def is_valid(self) -> bool:
+        """if this segment is valid"""
+        return True
+
+    def generate_socket_id_str(self, socket):
+        """generate a variable name for given socket"""
+        if socket.is_output:
+            socket_prefix = 'out%d_' % self.output_var_count
+            self.output_var_count += 1
+        else:
+            socket_prefix = 'in%d_' % self.input_var_count
+            self.input_var_count += 1
+        return self._id_prefix + socket_prefix + \
+            filter_id_illegal_char(socket.name)
+
+    def generate_shader_id_str(self, socket, shader_prop_name):
+        """generate a variable name for property of ShaderLink"""
+        if socket.is_output:
+            socket_prefix = 'out%d_' % self.output_var_count
+            self.output_var_count += 1
+        else:
+            socket_prefix = 'in%d_' % self.input_var_count
+            self.input_var_count += 1
+        ret_id = self._id_prefix + filter_id_illegal_char(
+            socket.name + '_' + socket_prefix + shader_prop_name)
+        return ret_id
+
+    def generate_variable_id_str(self, hint):
+        """generate variable name for tmp variable"""
+        var_prefix = "var%s_" % self.variable_count
+        self.variable_count += 1
+        return self._id_prefix + var_prefix + filter_id_illegal_char(hint)
+
+    def generate_tmp_texture_id(self, hashable_key):
+        """generate a temp variable for texture, later it would be replaced
+        by uniform var"""
+        var_prefix = "tex%s_" % hash(hashable_key)
+        return self._id_prefix + filter_id_illegal_char(var_prefix)
+
+    def generate_socket_assignment(self, to_socket_id, to_socket_type,
+                                   from_socket_id, from_socket_type):
+        # pylint: disable-msg=too-many-return-statements
+        """assign a socket variable to another, it handles type conversion"""
+        if to_socket_type == from_socket_type:
+            return "%s = %s" % (to_socket_id, from_socket_id)
+
+        if to_socket_type == 'VALUE' and from_socket_type == 'VECTOR':
+            return "%s = dot(%s, vec3(0.333333, 0.333333, 0.333333))" \
+                % (to_socket_id, from_socket_id)
+
+        if to_socket_type == 'VALUE' and from_socket_type == 'RGBA':
+            return "%s = dot(%s.rgb, vec3(0.2126, 0.7152, 0.0722))" \
+                % (to_socket_id, from_socket_id)
+
+        if to_socket_type == 'VECTOR' and from_socket_type == 'VALUE':
+            return "%s = vec3(%s, %s, %s)" \
+                % ((to_socket_id,) + (from_socket_id, ) * 3)
+
+        if to_socket_type == 'RGBA' and from_socket_type == 'VALUE':
+            return "%s = vec4(%s, %s, %s, %s)" \
+                % ((to_socket_id,) + (from_socket_id, ) * 4)
+
+        if to_socket_type == 'RGBA' and from_socket_type == 'VECTOR':
+            return "%s = vec4(%s, 1.0)" % (to_socket_id, from_socket_id)
+
+        if to_socket_type == 'VECTOR' and from_socket_type == 'RGBA':
+            return "%s = %s.rgb" % (to_socket_id, from_socket_id)
+
+        assert False, "Cannot link sockets '%s' and '%s'" % (
+            to_socket_id, from_socket_id)
+        return ""
+
+    def mix_frag_shader_link(self, input_a, input_b, output, fac):
+        """mix two ShaderLink with factor 'fac', used only by
+        AddShader and MixShader"""
+        # simply a mix of each property,
+        # except alpha is added with its complementary
+
+        # note that for unconnected shader input,
+        # albedo would default to black and alpha would default to 1.0,
+        for pname in FragmentShaderLink.ALL_PROPERTIES:
+            prop_a = input_a.get_property(pname)
+            prop_b = input_b.get_property(pname)
+
+            if pname == FragmentShaderLink.ALPHA:
+                # any shader node has no ALPHA property
+                # default to have alpha = 1.0
+                if prop_a is not None and prop_b is None:
+                    prop_b = '1.0'
+                elif prop_b is not None and prop_a is None:
+                    prop_a = '1.0'
+
+            output_socket = self.bl_node.outputs[0]
+            mix_result_id = self.generate_shader_id_str(output_socket, pname)
+            if prop_a is not None and prop_b is not None:
+                ptype = FragmentShaderLink.get_property_type(pname)
+                self.local_code.append(
+                    "%s = mix(%s, %s, %s)"
+                    % (mix_result_id, prop_a, prop_b, fac)
+                )
+
+                output.set_property(pname, mix_result_id)
+            elif prop_a is not None:
+                output.set_property(pname, prop_a)
+            elif prop_b is not None:
+                output.set_property(pname, prop_b)
+
+    def add_function_call(self, function, in_args, out_args):
+        """add function invoking script"""
+        self.functions.add(function)
+
+        self.local_code.append(
+            "%s(%s);" % (
+                function.name,
+                ', '.join([str(x) for x in in_args + out_args]),
+            )
+        )
+
+    def yup_to_zup(self, var):
+        """Convert a vec3 from y-up space to z-up space"""
+        function = find_function_by_name("space_convert_yup_to_zup")
+        self.add_function_call(function, [var], [])
+
+    def zup_to_yup(self, var):
+        """Convert a vec3 from z-up space to y-up space"""
+        function = find_function_by_name("space_convert_zup_to_yup")
+        self.add_function_call(function, [var], [])
+
+    def view_to_model(self, var, is_direction=True):
+        """Convert a vec3 from view space to model space,
+        note that conversion is done in y-up space"""
+        self.flags.inv_view_mat_used = True
+        self.flags.inv_model_mat_used = True
+        if is_direction:
+            function = find_function_by_name(
+                "dir_space_convert_view_to_model")
+        else:
+            function = find_function_by_name(
+                "point_space_convert_view_to_model")
+        self.add_function_call(
+            function,
+            [var, self.INV_MODEL_MAT, self.INV_VIEW_MAT],
+            [])
+
+    def model_to_view(self, var, is_direction=True):
+        """Convert a vec3 from model space to view space,
+        note that conversion is done in y-up space"""
+        if is_direction:
+            function = find_function_by_name(
+                "dir_space_convert_model_to_view")
+        else:
+            function = find_function_by_name(
+                "point_space_convert_model_to_view")
+        self.add_function_call(
+            function, [var, 'INV_CAMERA_MATRIX', 'WORLD_MATRIX'], []
+        )
+
+    def view_to_world(self, var, is_direction=True):
+        """Convert a vec3 from view space to world space,
+        note that it is done in y-up space"""
+        self.flags.inv_view_mat_used = True
+        if is_direction:
+            function = find_function_by_name(
+                "dir_space_convert_view_to_world")
+        else:
+            function = find_function_by_name(
+                "point_space_convert_view_to_world")
+        self.add_function_call(function, [var, self.INV_VIEW_MAT], [])
+
+    def world_to_view(self, var, is_direction=True):
+        """Convert a vec3 from world space to view space,
+        note that it is done in y-up space"""
+        if is_direction:
+            function = find_function_by_name(
+                "dir_space_convert_world_to_view")
+        else:
+            function = find_function_by_name(
+                "point_space_convert_world_to_view")
+        self.add_function_call(function, [var, 'INV_CAMERA_MATRIX'], [])
+
+    def _initialize_value_in_socket(self, socket, blnode_to_converter_map):
+        type_str = socket_to_type_string(socket)
+        id_str = self.generate_socket_id_str(socket)
+        self.in_sockets_map[socket] = id_str
+        self._defined_ids.add(id_str)
+
+        use_default_value = True
+        if socket.is_linked:
+            link = socket.links[0]
+            from_node = link.from_node
+            from_socket = link.from_socket
+            from_converter = blnode_to_converter_map[from_node]
+            if not from_converter.is_valid():
+                logging.warning("input node '%s' not supported,"
+                                "use default value for socket '%s'",
+                                from_node.name, socket.name)
+            else:
+                use_default_value = False
+                from_socket_id = from_converter.out_sockets_map[from_socket]
+                inter_socket_assign_str = self.generate_socket_assignment(
+                    id_str, socket.type, from_socket_id, from_socket.type)
+                self.input_definitions.append(
+                    "%s %s" % (type_str, inter_socket_assign_str)
+                )
+
+        if use_default_value:
+            if socket.name == 'Normal':
+                value_str = 'NORMAL'
+            elif socket.name == 'Tangent':
+                value_str = 'TANGENT'
+            else:
+                value_str = blender_value_to_string(socket.default_value)
+            self.input_definitions.append(
+                "%s %s = %s" % (type_str, id_str, value_str)
+            )
+
+    def _initialize_shader_in_socket(self, socket, blnode_to_converter_map):
+        in_shader_link = None
+        if socket.is_linked:
+            link = socket.links[0]
+            from_node = link.from_node
+            from_socket = link.from_socket
+            from_converter = blnode_to_converter_map[from_node]
+
+            if from_socket.type == 'SHADER' and from_converter.is_valid():
+                in_shader_link = from_converter.out_sockets_map[from_socket]
+                self.in_sockets_map[socket] = in_shader_link
+
+        if in_shader_link is None:
+            # default only set albedo
+            in_shader_link = FragmentShaderLink()
+            in_shader_link.albedo = self.generate_shader_id_str(
+                socket, FragmentShaderLink.ALBEDO)
+            self.in_sockets_map[socket] = in_shader_link
+            self.input_definitions.append(
+                "vec3 %s = vec3(0.0, 0.0, 0.0)" % in_shader_link.albedo
+            )
+
+        for pname in FragmentShaderLink.ALL_PROPERTIES:
+            from_prop_id = in_shader_link.get_property(pname)
+            if from_prop_id is not None:
+                cur_prop_id = self.generate_shader_id_str(socket, pname)
+                self._defined_ids.add(cur_prop_id)
+                cur_prop_type = FragmentShaderLink.get_property_type(pname)
+                in_shader_link.set_property(pname, cur_prop_id)
+                self.input_definitions.append(
+                    "%s %s = %s" % (cur_prop_type, cur_prop_id, from_prop_id)
+                )
+
+    def initialize_inputs(self, blnode_to_converter_map):
+        """initialize the input sockets variable through links
+        or default_value"""
+        for in_socket in self.bl_node.inputs:
+            if in_socket.type != 'SHADER':
+                self._initialize_value_in_socket(
+                    in_socket, blnode_to_converter_map)
+            else:
+                self._initialize_shader_in_socket(
+                    in_socket, blnode_to_converter_map)
+
+    def initialize_outputs(self):
+        """initialize definition of the output sockets"""
+        # here not all the sockets are exported, because some of them
+        # may not feasible to supported in godot. Here only export
+        # those registed in `out_sockets_map`. Registering is done in
+        # `parse_to_fragment` or `parse_to_vertex`
+        id_to_define = list()
+        for out_socket in self.bl_node.outputs:
+            var = self.out_sockets_map.get(out_socket, None)
+            if var is not None:
+                if out_socket.type != 'SHADER':
+                    id_str = var
+                    type_str = socket_to_type_string(out_socket)
+                    id_to_define.append((type_str, id_str))
+                else:
+                    for pname in FragmentShaderLink.ALL_PROPERTIES:
+                        id_str = var.get_property(pname)
+                        type_str = var.get_property_type(pname)
+                        if id_str is not None:
+                            id_to_define.append((type_str, id_str))
+
+        for type_str, id_str in id_to_define:
+            # don't define if they already in input sockets
+            assert isinstance(id_str, str) and id_str.isidentifier()
+            if id_str not in self._defined_ids:
+                self._defined_ids.add(id_str)
+                self.output_definitions.append(
+                    "%s %s" % (type_str, id_str)
+                )
+
+    def parse_node_to_fragment(self):
+        """Parse the node to generate fragment shader script"""
+        assert False, 'Not implemented'
+
+    def parse_node_to_vertex(self):
+        """Parse the node to generate vertex shader script"""
+        assert False, 'Not implemented'
+
+
+class InvalidNodeConverter(NodeConverterBase):
+    """converter for not supported shader nodes"""
+
+    def is_valid(self):
+        return False
+
+    def parse_node_to_fragment(self):
+        self.local_code.append("// Warn: node not supported")
+
+    def parse_node_to_vertex(self):
+        self.local_code.append("// Warn: node not supported")
+
+
+class AddShaderConverter(NodeConverterBase):
+    """Converter for ShaderNodeAddShader"""
+
+    def parse_node_to_fragment(self):
+        shader_socket_a = self.bl_node.inputs[0]
+        in_shader_a = self.in_sockets_map[shader_socket_a]
+
+        shader_socket_b = self.bl_node.inputs[1]
+        in_shader_b = self.in_sockets_map[shader_socket_b]
+
+        output_shader_link = FragmentShaderLink()
+        self.mix_frag_shader_link(
+            in_shader_a, in_shader_b, output_shader_link, 0.5)
+
+        out_socket = self.bl_node.outputs[0]
+        self.out_sockets_map[out_socket] = output_shader_link
+
+
+class MixShaderConverter(NodeConverterBase):
+    """Converter for ShaderNodeMixShader"""
+
+    def parse_node_to_fragment(self):
+        output = FragmentShaderLink()
+
+        in_fac_socket = self.bl_node.inputs['Fac']
+        in_fac = self.in_sockets_map[in_fac_socket]
+
+        in_shader_socket_a = self.bl_node.inputs[1]
+        in_shader_a = self.in_sockets_map[in_shader_socket_a]
+
+        in_shader_socket_b = self.bl_node.inputs[2]
+        in_shader_b = self.in_sockets_map[in_shader_socket_b]
+
+        output_shader_link = FragmentShaderLink()
+        self.mix_frag_shader_link(
+            in_shader_a, in_shader_b, output_shader_link, in_fac)
+
+        out_socket = self.bl_node.outputs[0]
+        self.out_sockets_map[out_socket] = output_shader_link
+
+
+class BsdfNodeConverter(NodeConverterBase):
+    """Converter for all the BSDF nodes"""
+
+    def parse_node_to_fragment(self):
+        output_socket = self.bl_node.outputs[0]
+        output_shader_link = FragmentShaderLink()
+        self.out_sockets_map[output_socket] = output_shader_link
+
+        if self.bl_node.bl_idname in ('ShaderNodeBsdfGlass',):
+            self.flags.glass = True
+
+        if self.bl_node.bl_idname in \
+                ('ShaderNodeBsdfTransparent', 'ShaderNodeBsdfGlass'):
+            self.flags.transparent = True
+
+        tangent_socket = self.bl_node.inputs.get('Tangent', None)
+        if tangent_socket is not None and tangent_socket.is_linked:
+            self.flags.uv_or_tangent_used = True
+
+        transmission_socket = self.bl_node.inputs.get('Transmission', None)
+        if (transmission_socket is not None and
+                transmission_socket.is_linked and
+                transmission_socket.default_value == 0.0):
+            self.flags.transmission_used = True
+
+        function = find_node_function(self.bl_node)
+        func_in_args = list()
+        func_out_args = list()
+
+        for socket_name in function.in_sockets:
+            socket = self.bl_node.inputs[socket_name]
+            func_in_args.append(self.in_sockets_map[socket])
+
+        for prop_name in function.output_properties:
+            var_id = self.generate_shader_id_str(output_socket, prop_name)
+            output_shader_link.set_property(prop_name, var_id)
+            func_out_args.append(var_id)
+
+        self.add_function_call(function, func_in_args, func_out_args)
+
+        # normal and tangent don't go to function
+        normal_socket = self.bl_node.inputs.get('Normal', None)
+        # normal and tangent input to shader node is in view space
+        for pname, socket in (
+                (FragmentShaderLink.NORMAL, normal_socket),
+                (FragmentShaderLink.TANGENT, tangent_socket)):
+            if socket is not None:
+                socket_var = self.in_sockets_map[socket]
+                if socket.is_linked:
+                    # default value is in y-up, view space
+                    # while value come from socket is z-up, model space
+                    self.zup_to_yup(socket_var)
+                    self.world_to_view(socket_var)
+                output_shader_link.set_property(pname, socket_var)
+
+
+class RerouteNodeConverter(NodeConverterBase):
+    """Converter for NodeReroute"""
+
+    def parse_node_to_fragment(self):
+        """do nothing but assign output = input"""
+        in_socket = self.bl_node.inputs[0]
+        out_socket = self.bl_node.outputs[0]
+        self.out_sockets_map[out_socket] = self.in_sockets_map[in_socket]
+
+
+class BumpNodeConverter(NodeConverterBase):
+    """Converter for ShaderNodeBump"""
+
+    def parse_node_to_fragment(self):
+        function = find_node_function(self.bl_node)
+
+        in_arguments = list()
+        for socket in self.bl_node.inputs:
+            socket_var = self.in_sockets_map[socket]
+            if socket.name == 'Normal' and socket.is_linked:
+                self.zup_to_yup(socket_var)
+                self.world_to_view(socket_var)
+            in_arguments.append(socket_var)
+
+        in_arguments.append('VERTEX')
+        if self.bl_node.invert:
+            in_arguments.append(1.0)
+        else:
+            in_arguments.append(0.0)
+
+        out_socket = self.bl_node.outputs[0]
+        out_normal = self.generate_socket_id_str(out_socket)
+        self.out_sockets_map[out_socket] = out_normal
+
+        self.add_function_call(function, in_arguments, [out_normal])
+        self.view_to_world(out_normal)
+        self.yup_to_zup(out_normal)
+
+
+class NormalMapNodeConverter(NodeConverterBase):
+    """Converter for ShaderNodeNormalMap"""
+
+    def parse_node_to_fragment(self):
+        function = find_node_function(self.bl_node)
+
+        in_arguments = list()
+        for socket in self.bl_node.inputs:
+            in_arguments.append(self.in_sockets_map[socket])
+
+        output_socket = self.bl_node.outputs[0]
+        output_normal = self.generate_socket_id_str(output_socket)
+        self.out_sockets_map[output_socket] = output_normal
+        if self.bl_node.space == 'TANGENT':
+            in_arguments.append('NORMAL')
+            in_arguments.append('TANGENT')
+            in_arguments.append('BINORMAL')
+            self.add_function_call(function, in_arguments, [output_normal])
+            self.view_to_world(output_normal)
+            self.yup_to_zup(output_normal)
+
+        elif self.bl_node.space == 'WORLD':
+            self.flags.inv_view_mat_used = True
+            in_arguments.append('NORMAL')
+            in_arguments.append(self.INV_VIEW_MAT)
+            self.add_function_call(function, in_arguments, [output_normal])
+            self.yup_to_zup(output_normal)
+
+        elif self.bl_node.space == 'OBJECT':
+            self.flags.inv_view_mat_used = True
+            in_arguments.append('NORMAL')
+            in_arguments.append(self.INV_VIEW_MAT)
+            in_arguments.append('WORLD_MATRIX')
+            self.add_function_call(function, in_arguments, [output_normal])
+            self.yup_to_zup(output_normal)
+
+
+class TexCoordNodeConverter(NodeConverterBase):
+    """Converter for ShaderNodeTexCoord"""
+
+    def parse_node_to_fragment(self):
+        for socket in self.bl_node.outputs:
+            if socket.is_linked:
+                socket_id = self.generate_socket_id_str(socket)
+                self.out_sockets_map[socket] = socket_id
+
+        uv_socket = self.bl_node.outputs['UV']
+        if uv_socket.is_linked:
+            uv_id = self.out_sockets_map[uv_socket]
+            self.local_code.append("%s = vec3(UV, 0.0)" % uv_id)
+
+        window_socket = self.bl_node.outputs['Window']
+        if window_socket.is_linked:
+            window_id = self.out_sockets_map[window_socket]
+            self.local_code.append("%s = vec3(SCREEN_UV, 0.0)" % window_id)
+
+        camera_socket = self.bl_node.outputs['Camera']
+        if camera_socket.is_linked:
+            camera_id = self.out_sockets_map[camera_socket]
+            self.local_code.append(
+                "%s = vec3(VERTEX.xy, -VERTEX.z)" % camera_id)
+
+        normal_socket = self.bl_node.outputs['Normal']
+        if normal_socket.is_linked:
+            normal_id = self.out_sockets_map[normal_socket]
+            self.local_code.append('%s = NORMAL' % normal_id)
+            self.view_to_model(normal_id)
+            self.yup_to_zup(normal_id)
+
+        obj_socket = self.bl_node.outputs['Object']
+        if obj_socket.is_linked:
+            object_id = self.out_sockets_map[obj_socket]
+            self.local_code.append('%s = VERTEX' % object_id)
+            self.view_to_model(object_id, False)
+            self.yup_to_zup(object_id)
+            self.out_sockets_map[obj_socket] = object_id
+
+        ref_socket = self.bl_node.outputs['Reflection']
+        if ref_socket.is_linked:
+            reflect_id = self.out_sockets_map[ref_socket]
+            self.local_code.append(
+                '%s = reflect(normalize(VERTEX), NORMAL)'
+                % reflect_id
+            )
+            self.view_to_model(reflect_id)
+            self.yup_to_zup(reflect_id)
+            self.out_sockets_map[ref_socket] = reflect_id
+
+        generated_socket = self.bl_node.outputs['Generated']
+        if generated_socket.is_linked:
+            generated_id = self.out_sockets_map[generated_socket]
+            self.local_code.append(
+                '// Generated texture coordinate not supported yet,'
+                ' here reset to UV')
+            self.local_code.append('%s = vec3(UV, 1.0)' % generated_id)
+
+
+class RgbNodeConverter(NodeConverterBase):
+    """Converter for ShaderNodeRGB"""
+
+    def parse_node_to_fragment(self):
+        rgb_socket = self.bl_node.outputs[0]
+        self.out_sockets_map[rgb_socket] = blender_value_to_string(
+            rgb_socket.default_value)
+
+
+class ValueNodeConverter(NodeConverterBase):
+    """Converter for ShaderNodeValue"""
+
+    def parse_node_to_fragment(self):
+        value_socket = self.bl_node.outputs['Value']
+        self.out_sockets_map[value_socket] = blender_value_to_string(
+            value_socket.default_value)
+
+
+class ImageTextureNodeConverter(NodeConverterBase):
+    """Converter for ShaderNodeTexImage"""
+
+    def parse_node_to_fragment(self):
+        function = find_node_function(self.bl_node)
+
+        in_arguments = list()
+        tex_coord_socket = self.bl_node.inputs[0]
+        tex_coord = self.in_sockets_map[tex_coord_socket]
+        if not tex_coord_socket.is_linked:
+            # TODO: once generated texture get supported, it should be changed
+            logging.warning(
+                "No texture coordinate input for '%s' "
+                "UV is used by default, at '%s'",
+                self.bl_node.bl_idname, self.bl_node.name)
+            self.local_code.append("%s = vec3(UV, 0.0)" % tex_coord)
+
+        tex_var = self.generate_tmp_texture_id(self.bl_node.name)
+        if self.bl_node.image is not None:
+            is_normal = is_normal_texture(self.bl_node)
+        else:
+            is_normal = False
+        self.textures.append(
+            Texture(self.bl_node.image, tex_var, is_normal)
+        )
+
+        in_arguments.append(tex_coord)
+        in_arguments.append(tex_var)
+
+        out_arguments = list()
+
+        for socket in self.bl_node.outputs:
+            output_var = self.generate_socket_id_str(socket)
+            self.out_sockets_map[socket] = output_var
+            out_arguments.append(output_var)
+
+        self.add_function_call(function, in_arguments, out_arguments)
+
+
+class MappingNodeConverter(NodeConverterBase):
+    """Converter for ShaderNodeMapping"""
+
+    def parse_node_to_fragment(self):
+        function = find_node_function(self.bl_node)
+
+        rot_mat = self.bl_node.rotation.to_matrix().to_4x4()
+        loc_mat = mathutils.Matrix.Translation(self.bl_node.translation)
+        sca_mat = mathutils.Matrix((
+            (self.bl_node.scale[0], 0, 0),
+            (0, self.bl_node.scale[1], 0),
+            (0, 0, self.bl_node.scale[2]),
+        )).to_4x4()
+
+        in_vec = self.in_sockets_map[self.bl_node.inputs[0]]
+
+        if self.bl_node.vector_type == "TEXTURE":
+            # Texture: Transform a texture by inverse
+            # mapping the texture coordinate
+            transform_mat = (loc_mat * rot_mat * sca_mat).inverted_safe()
+        elif self.bl_node.vector_type == "POINT":
+            transform_mat = loc_mat * rot_mat * sca_mat
+        else:  # node.vector_type in ("VECTOR", "NORMAL")
+            # no translation for vectors
+            transform_mat = rot_mat * sca_mat
+
+        mat = blender_value_to_string(transform_mat)
+        clamp_min = blender_value_to_string(self.bl_node.min)
+        clamp_max = blender_value_to_string(self.bl_node.max)
+        use_min = 1.0 if self.bl_node.use_min else 0.0
+        use_max = 1.0 if self.bl_node.use_max else 0.0
+
+        in_arguments = list()
+        in_arguments.append(in_vec)
+        in_arguments.append(mat)
+        in_arguments.append(clamp_min)
+        in_arguments.append(clamp_max)
+        in_arguments.append(use_min)
+        in_arguments.append(use_max)
+
+        output_socket = self.bl_node.outputs[0]
+        out_vec = self.generate_socket_id_str(output_socket)
+        self.out_sockets_map[output_socket] = out_vec
+
+        self.add_function_call(function, in_arguments, [out_vec])
+        if self.bl_node.vector_type == "NORMAL":
+            # need additonal normalize
+            self.local_code.append(
+                '%s = normalize(%s)' % (out_vec, out_vec)
+            )
+
+
+class TangentNodeConverter(NodeConverterBase):
+    """Converter for ShaderNodeTangent"""
+
+    def parse_node_to_fragment(self):
+        if self.bl_node.direction_type != 'UV_MAP':
+            logging.warning(
+                'tangent space Radial not supported at %s',
+                self.bl_node.name
+            )
+
+        self.flags.uv_or_tangent_used = True
+
+        tangent_socket = self.bl_node.outputs[0]
+        tangent_id = self.generate_socket_id_str(tangent_socket)
+        self.out_sockets_map[tangent_socket] = tangent_id
+
+        self.local_code.append(
+            '%s = TANGENT' % tangent_id
+        )
+
+
+class UvmapNodeConverter(NodeConverterBase):
+    """Converter for ShaderNodeUVMap"""
+
+    def parse_node_to_fragment(self):
+        self.flags.uv_or_tangent_used = True
+
+        uv_socket = self.bl_node.outputs['UV']
+        uv_id = self.generate_socket_id_str(uv_socket)
+        self.out_sockets_map[uv_socket] = uv_id
+
+        self.local_code.append(
+            '%s = vec3(UV, 0.0)' % uv_id
+        )
+
+        logging.warning(
+            "'%s' use the active UV map, make sure the correct "
+            "one is selected, at '%s",
+            self.bl_node.bl_idname, self.bl_node.name
+        )
+
+
+class GeneralNodeConverter(NodeConverterBase):
+    """Converter for general converter node, they all use functions"""
+
+    def parse_node_to_fragment(self):
+        function = find_node_function(self.bl_node)
+        in_arguments = list()
+
+        for socket in self.bl_node.inputs:
+            in_arguments.append(
+                self.in_sockets_map[socket]
+            )
+
+        out_arguments = list()
+        for socket in self.bl_node.outputs:
+            socket_id = self.generate_socket_id_str(socket)
+            self.out_sockets_map[socket] = socket_id
+            out_arguments.append(socket_id)
+
+        self.add_function_call(function, in_arguments, out_arguments)
+
+
+NODE_CONVERTERS = {
+    'ShaderNodeMapping': MappingNodeConverter,
+    'ShaderNodeTexImage': ImageTextureNodeConverter,
+    'ShaderNodeTexCoord': TexCoordNodeConverter,
+    'ShaderNodeRGB': RgbNodeConverter,
+    'ShaderNodeNormalMap': NormalMapNodeConverter,
+    'ShaderNodeBump': BumpNodeConverter,
+    'NodeReroute': RerouteNodeConverter,
+    'ShaderNodeMixShader': MixShaderConverter,
+    'ShaderNodeAddShader': AddShaderConverter,
+    'ShaderNodeTangent': TangentNodeConverter,
+    'ShaderNodeUVMap': UvmapNodeConverter,
+    'ShaderNodeValue': ValueNodeConverter,
+}
+
+
+def converter_factory(idx, node):
+    """Return a visitor function for the node"""
+    if node.bl_idname in NODE_CONVERTERS:
+        return NODE_CONVERTERS[node.bl_idname](idx, node)
+
+    if (node.outputs and
+            node.outputs[0].identifier in ('Emission', 'BSDF', 'BSSRDF')):
+        # for shader node output bsdf closure
+        return BsdfNodeConverter(idx, node)
+
+    if node_has_function(node):
+        return GeneralNodeConverter(idx, node)
+
+    return InvalidNodeConverter(idx, node)

+ 424 - 0
io_scene_godot/converters/material/script_shader/node_tree.py

@@ -0,0 +1,424 @@
+"""Interface for material node tree exporter"""
+import os
+import logging
+import textwrap
+from collections import OrderedDict
+from shutil import copyfile
+import bpy
+from .shader_links import FragmentShaderLink
+from .shader_functions import find_function_by_name
+from .node_converters import (
+    converter_factory, NodeConverterBase, ShadingFlags)
+from ....structures import InternalResource, ExternalResource, ValidationError
+
+
+class ScriptShader:
+    # pylint: disable-msg=too-many-instance-attributes
+    """generator of the shader scripts"""
+
+    SCRIPT_MAX_WIDTH = 80
+
+    def __init__(self):
+        self._render_mode = [
+            'blend_mix',
+            'depth_draw_always',
+            'cull_back',
+            'diffuse_burley',
+            'specular_schlick_ggx',
+        ]
+        self._functions = set()
+        self._uniform_code_lines = list()
+        self._fragment_code_lines = list()
+        self._vertex_code_lines = list()
+
+        self._textures = dict()
+
+        self.flags = ShadingFlags()
+
+    def add_functions(self, functions):
+        """append local converter functions to shader"""
+        self._functions = self._functions.union(functions)
+
+    def add_fragment_code(self, frag_code_list):
+        """get local fragment code and append to shader"""
+        self._fragment_code_lines.extend(frag_code_list)
+
+    def add_fragment_output(self, output_shader_link):
+        """link the node tree output with godot fragment output"""
+        # pylint: disable-msg=too-many-branches
+        # pylint: disable-msg=too-many-statements
+
+        # hack: define those two variable at the begining
+        self._fragment_code_lines.insert(0, "\n")
+        if self.flags.inv_view_mat_used:
+            self._fragment_code_lines.insert(
+                0,
+                "mat4 %s = inverse(INV_CAMERA_MATRIX)"
+                % NodeConverterBase.INV_VIEW_MAT
+            )
+
+        if self.flags.inv_model_mat_used:
+            self._fragment_code_lines.insert(
+                0,
+                "mat4 %s = inverse(WORLD_MATRIX)"
+                % NodeConverterBase.INV_MODEL_MAT,
+            )
+
+        for name in (
+                FragmentShaderLink.ALBEDO, FragmentShaderLink.SSS_STRENGTH,
+                FragmentShaderLink.SPECULAR, FragmentShaderLink.METALLIC,
+                FragmentShaderLink.ROUGHNESS, FragmentShaderLink.CLEARCOAT,
+                FragmentShaderLink.CLEARCOAT_GLOSS,
+                FragmentShaderLink.EMISSION, FragmentShaderLink.NORMAL):
+            # trival properties
+            var = output_shader_link.get_property(name)
+            if var is not None:
+                self._fragment_code_lines.append(
+                    '%s = %s' % (name.upper(), str(var))
+                )
+
+        transmission = output_shader_link.get_property(
+            FragmentShaderLink.TRANSMISSION)
+        transm_output_assign = \
+            'TRANSMISSION = vec3(1.0, 1.0, 1.0) * %s;' % transmission
+        # blender transmission is a float, however in godot
+        # it's a vec3, here the conversion is done
+        if self.flags.transmission_used and transmission is not None:
+            self._fragment_code_lines.append(transm_output_assign)
+        elif transmission is not None:
+            self._fragment_code_lines.append(
+                "// uncomment it when you need it")
+            self._fragment_code_lines.append("// " + transm_output_assign)
+
+        oren_nayar_roughness = output_shader_link.get_property(
+            FragmentShaderLink.OREN_NAYAR_ROUGHNESS
+        )
+        if oren_nayar_roughness is not None:
+            # oren nayar roughness is created from BsdfDiffuseNode
+            # mostly we don't need it, disney model is better
+            self._fragment_code_lines.append(
+                "// uncomment it only when you set diffuse mode to oren nayar")
+            self._fragment_code_lines.append(
+                '// ROUGHNESS = %s;' % oren_nayar_roughness
+            )
+
+        tangent = output_shader_link.get_property(FragmentShaderLink.TANGENT)
+        tgt_output_assign = \
+            "TANGENT = normalize(cross(cross(%s, NORMAL), NORMAL));" % tangent
+        binml_output_assign = "BINORMAL = cross(TANGENT, NORMAL);"
+        if self.flags.uv_or_tangent_used and tangent is not None:
+            self._fragment_code_lines.append(tgt_output_assign)
+            self._fragment_code_lines.append(binml_output_assign)
+        elif tangent is not None:
+            self._fragment_code_lines.append(
+                "// uncomment it when you are modifing TANGENT")
+            self._fragment_code_lines.append("// " + tgt_output_assign)
+            self._fragment_code_lines.append("// " + binml_output_assign)
+
+        anisotropy = \
+            output_shader_link.get_property(FragmentShaderLink.ANISOTROPY)
+        ans_output_assign = "ANISOTROPY = %s;" % anisotropy
+        if self.flags.uv_or_tangent_used and anisotropy is not None:
+            self._fragment_code_lines.append(ans_output_assign)
+        elif anisotropy is not None:
+            self._fragment_code_lines.append(
+                "// uncomment it when you have tangent(UV) set")
+            self._fragment_code_lines.append("// " + ans_output_assign)
+
+        alpha = output_shader_link.get_property(FragmentShaderLink.ALPHA)
+        ior = output_shader_link.get_property(FragmentShaderLink.IOR)
+        if self.flags.transparent and alpha is not None:
+            if ior is not None and self.flags.glass:
+                refraction_offset_value = (0.2, 0.2)
+                fresnel_func = find_function_by_name('refraction_fresnel')
+                self._functions.add(fresnel_func)
+                self._fragment_code_lines.append(
+                    "refraction_fresnel(VERTEX, NORMAL, %s, %s)" %
+                    (ior, alpha)
+                )
+                refraction_offset = 'refraction_offset'
+                # just some magic random value, available for improvements
+                self._uniform_code_lines.append(
+                    'uniform vec2 %s = vec2(0.2, 0.2)' % refraction_offset
+                )
+                self._fragment_code_lines.append(
+                    "EMISSION += textureLod(SCREEN_TEXTURE, SCREEN_UV - "
+                    "NORMAL.xy * %s , ROUGHNESS).rgb * (1.0 - %s)" %
+                    (refraction_offset, alpha)
+                )
+            else:
+                self._fragment_code_lines.append(
+                    "EMISSION += textureLod(SCREEN_TEXTURE, SCREEN_UV, "
+                    "ROUGHNESS).rgb * (1.0 - %s)" % alpha
+                )
+
+            self._fragment_code_lines.append(
+                "ALBEDO *= %s" % alpha
+            )
+            self._fragment_code_lines.append(
+                'ALPHA = 1.0;'
+            )
+
+    def generate_scripts(self):
+        """return the whole script in the format of string"""
+        def generate_line_suffix(line):
+            """add prefix at the end of line"""
+            if line.startswith("//"):
+                _suffix = "\n"
+            elif line.endswith("\n"):
+                _suffix = ""
+            elif line.endswith(";"):
+                _suffix = "\n"
+            else:
+                _suffix = ";\n"
+            return _suffix
+
+        def line_wrap(line, suffix):
+            """wrap a long line into several short ones"""
+            wrapped_lines = ""
+
+            prefix = ""
+            if line.startswith("\\"):
+                prefix = "\\ "
+
+            line_list = textwrap.wrap(line, self.SCRIPT_MAX_WIDTH)
+            wrapped_lines += ("\t" + line_list[0] + "\n")
+            for lline in line_list[1:-1]:
+                wrapped_lines += ("\t\t" + prefix + lline + "\n")
+            wrapped_lines += ("\t\t" + prefix + line_list[-1] + suffix)
+
+            return wrapped_lines
+
+        script = "shader_type spatial;\n"
+        script += "render_mode " + ", ".join(self._render_mode) + ";\n"
+        script += "\n"
+
+        for line in self._uniform_code_lines:
+            script += line + ";\n"
+
+        for tex, tex_uniform in self._textures.items():
+            script += "uniform sampler2D %s%s;\n" % (
+                tex_uniform, tex.hint_str())
+        script += "\n"
+
+        # determine the order, make it easy to work with testcases
+        for func in sorted(self._functions, key=lambda x: x.name):
+            script += func.code
+            script += "\n"
+
+        script += "void vertex () {\n"
+        for line in self._vertex_code_lines:
+            suffix = generate_line_suffix(line)
+            if len(line) > self.SCRIPT_MAX_WIDTH:
+                script += line_wrap(line, suffix)
+            else:
+                script += "\t" + line + suffix
+        script += "}\n"
+        script += "\n"
+
+        script += "void fragment () {\n"
+        for line in self._fragment_code_lines:
+            suffix = generate_line_suffix(line)
+            if len(line) > self.SCRIPT_MAX_WIDTH:
+                script += line_wrap(line, suffix)
+            else:
+                script += "\t" + line + suffix
+        script += "}\n"
+
+        return script
+
+    def update_texture(self, converter):
+        """add converter textures into shader and update the texture info
+        in converter"""
+        for tex in converter.textures:
+            if tex in self._textures:
+                tex_uniform = self._textures[tex]
+            else:
+                tex_uniform = "texture_%d" % len(self._textures)
+                self._textures[tex] = tex_uniform
+
+            for idx, line in enumerate(converter.local_code):
+                # replace tmp texture id with the uniform
+                converter.local_code[idx] = \
+                    line.replace(tex.tmp_identifier, tex_uniform)
+
+    def get_images(self):
+        """return a set of all the images used in shader"""
+        image_set = set()
+        for tex in self._textures:
+            if tex.image is not None:
+                image_set.add(tex.image)
+        return image_set
+
+    def get_image_texture_info(self):
+        """return a list of tuple (image, texture uniform)"""
+        image_uniform_tuples = list()
+        for tex, uniform in self._textures.items():
+            if tex.image is not None:
+                image_uniform_tuples.append((tex.image, uniform))
+        return image_uniform_tuples
+
+
+def find_material_output_node(node_tree):
+    """Find materia output node in the material node tree, if
+    two output nodes found, raise error"""
+    output_node = None
+    for node in node_tree.nodes:
+        if node.bl_idname == 'ShaderNodeOutputMaterial':
+            if output_node is None:
+                output_node = node
+            else:
+                logging.warning(
+                    "More than one material output node find",
+                )
+    return output_node
+
+
+def topology_sort(nodes):
+    """topology sort all the nodes"""
+    def find_zero_input_node(nodes_input_list):
+        """find a node with zero input links"""
+        for node, inputs in nodes_input_list:
+            if inputs == 0:
+                return node
+        return None
+
+    sorted_node_list = list()
+
+    nodes_input_count = OrderedDict()
+    for node in nodes:
+        cnt = 0
+        for sock in node.inputs:
+            if sock.is_linked and sock.links[0].is_valid:
+                cnt += 1
+        nodes_input_count[node] = cnt
+
+    cur_node = find_zero_input_node(nodes_input_count.items())
+    while cur_node is not None:
+        sorted_node_list.append(cur_node)
+
+        for sock in cur_node.outputs:
+            for link in sock.links:
+                if link.is_valid:
+                    nodes_input_count[link.to_node] -= 1
+
+        # made cur_node -1, so prevent it from being found
+        # as zero input
+        nodes_input_count[cur_node] = -1
+
+        cur_node = find_zero_input_node(nodes_input_count.items())
+
+    return sorted_node_list
+
+
+def export_texture(escn_file, export_settings, image):
+    """Export texture image as an external resource"""
+    resource_id = escn_file.get_external_resource(image)
+    if resource_id is not None:
+        return resource_id
+
+    dst_dir_path = os.path.dirname(export_settings['path'])
+    dst_path = dst_dir_path + os.sep + image.name
+
+    if image.packed_file is not None:
+        image_format = image.file_format
+        image_name_lower = image.name.lower()
+
+        if image_format in ('JPEG', 'JPEG2000'):
+            # jpg and jpeg are same thing
+            if not image_name_lower.endswith('.jpg') and \
+                    not image_name_lower.endswith('.jpeg'):
+                dst_path = dst_path + '.jpg'
+        elif not image_name_lower.endswith('.' + image_format.lower()):
+            dst_path = dst_path + '.' + image_format.lower()
+        image.filepath_raw = dst_path
+        image.save()
+    else:
+        if image.filepath_raw.startswith("//"):
+            src_path = bpy.path.abspath(image.filepath_raw)
+        else:
+            src_path = image.filepath_raw
+        if os.path.normpath(src_path) != os.path.normpath(dst_path):
+            copyfile(src_path, dst_path)
+
+    img_resource = ExternalResource(dst_path, "Texture")
+    return escn_file.add_external_resource(img_resource, image)
+
+
+def export_script_shader(escn_file, export_settings,
+                         bl_node_mtl, gd_shader_mtl):
+    """Export cycles material to godot shader script"""
+    shader = ScriptShader()
+
+    exportable = False
+    mtl_output_node = find_material_output_node(bl_node_mtl.node_tree)
+    if mtl_output_node is not None:
+        frag_node_list = topology_sort(bl_node_mtl.node_tree.nodes)
+
+        node_to_converter_map = dict()
+        for idx, node in enumerate(frag_node_list):
+            if node == mtl_output_node:
+                continue
+
+            converter = converter_factory(idx, node)
+            node_to_converter_map[node] = converter
+
+            converter.initialize_inputs(node_to_converter_map)
+            converter.parse_node_to_fragment()
+            converter.initialize_outputs()
+
+            shader.add_functions(converter.functions)
+            # update texture before add local code
+            shader.update_texture(converter)
+
+            shader.add_fragment_code([
+                "// node: '%s'" % node.name,
+                "// type: '%s'" % node.bl_idname,
+            ])
+            # definitions first
+            shader.add_fragment_code(converter.input_definitions)
+            shader.add_fragment_code(converter.output_definitions)
+            shader.add_fragment_code(converter.local_code)
+            shader.add_fragment_code(["\n", "\n"])
+
+            # flags are all True/False, here use '|=' instead of
+            # 'or' assignment, just for convenience
+            shader.flags.glass |= converter.flags.glass
+            shader.flags.transparent |= converter.flags.transparent
+            shader.flags.inv_model_mat_used \
+                |= converter.flags.inv_model_mat_used
+            shader.flags.inv_view_mat_used |= converter.flags.inv_view_mat_used
+            shader.flags.transmission_used |= converter.flags.transmission_used
+            shader.flags.uv_or_tangent_used \
+                |= converter.flags.uv_or_tangent_used
+
+        surface_output_socket = mtl_output_node.inputs['Surface']
+        if surface_output_socket.is_linked:
+            surface_in_socket = surface_output_socket.links[0].from_socket
+            root_converter = node_to_converter_map[surface_in_socket.node]
+            if root_converter.is_valid():
+                exportable = True
+                shader.add_fragment_output(
+                    root_converter.out_sockets_map[surface_in_socket]
+                )
+
+    if not exportable:
+        raise ValidationError(
+            "Blender material '%s' not able to export as Shader Material"
+            % bl_node_mtl.name
+        )
+
+    shader_resource = InternalResource('Shader', bl_node_mtl.node_tree.name)
+    shader_resource['code'] = '"{}"'.format(shader.generate_scripts())
+    resource_id = escn_file.add_internal_resource(
+        shader_resource, bl_node_mtl.node_tree
+    )
+    gd_shader_mtl['shader'] = "SubResource(%d)" % resource_id
+
+    for image in shader.get_images():
+        export_texture(escn_file, export_settings, image)
+
+    for image, image_unifrom in shader.get_image_texture_info():
+        shader_param_key = 'shader_param/%s' % image_unifrom
+        resource_id = escn_file.get_external_resource(image)
+        gd_shader_mtl[shader_param_key] = "ExtResource(%d)" % resource_id

+ 123 - 43
io_scene_godot/converters/material_node_tree/shader_functions.py → io_scene_godot/converters/material/script_shader/shader_functions.py

@@ -1,17 +1,19 @@
 """Prewritten shader scripts for node in material node tree,
-reference: https://developer.blender.org/diffusion/B/browse/
-blender2.8/source/blender/gpu/shaders/gpu_shader_material.glsl"""
+reference: 'https://developer.blender.org/diffusion/B/browse/
+master/source/blender/gpu/shaders/gpu_shader_material.glsl'"""
 import re
-from ...structures import ValidationError
+from .shader_links import FragmentShaderLink
+from ....structures import ValidationError
 
 FUNCTION_HEAD_PATTERN = re.compile(
-    (r'void\s+([a-zA-Z]\w*)\s*\(((\s*(out\s+)?'
+    (r'void\s+([a-zA-Z]\w*)\s*\(((\s*((in|inout|out)\s+)?'
      r'(vec2|vec3|vec4|float|mat4|sampler2D)\s+[a-zA-Z]\w*\s*,?)*)\)'),
 )
 
 
 class ShaderFunction:
     """Shader function for a blender node"""
+
     def __init__(self, code):
         # at most one group
         self.code = code
@@ -26,18 +28,22 @@ class ShaderFunction:
             tokens = tuple([x.strip() for x in param_str.split()])
             if tokens[0] == 'out':
                 self.out_param_types.append(tokens[1])
-            else:
+            else:  # 'in', 'inout'
                 self.in_param_types.append(tokens[0])
 
+    def __hash__(self):
+        return hash(self.name)
+
 
 class BsdfShaderFunction(ShaderFunction):
     """Function for bsdf shader node, has additional information of
     input and output socket"""
-    def __init__(self, code, input_sockets, output_sockets):
+
+    def __init__(self, code, input_sockets, output_properties):
         super().__init__(code)
         # linked socket ids of material node
         self.in_sockets = tuple(input_sockets)
-        self.out_sockets = tuple(output_sockets)
+        self.output_properties = tuple(output_properties)
 
 
 # Shader function nameing convention:
@@ -105,55 +111,59 @@ void node_bsdf_principled(vec4 color, float subsurface, vec4 subsurface_color,
             "Transmission",
             "IOR",
         ],
-        output_sockets=[
-            "albedo",
-            "sss_strength",
-            "metallic",
-            "specular",
-            "roughness",
-            "clearcoat",
-            "clearcoat_gloss",
-            "anisotropy",
-            "transmission",
-            "ior",
+        output_properties=[
+            FragmentShaderLink.ALBEDO,
+            FragmentShaderLink.SSS_STRENGTH,
+            FragmentShaderLink.METALLIC,
+            FragmentShaderLink.SPECULAR,
+            FragmentShaderLink.ROUGHNESS,
+            FragmentShaderLink.CLEARCOAT,
+            FragmentShaderLink.CLEARCOAT_GLOSS,
+            FragmentShaderLink.ANISOTROPY,
+            FragmentShaderLink.TRANSMISSION,
+            FragmentShaderLink.IOR,
         ]
     ),
 
     BsdfShaderFunction(
         code="""
 void node_emission(vec4 emission_color, float strength,
-        out vec3 emission_out){
+        out vec3 emission_out, out float alpha_out) {
     emission_out = emission_color.rgb * strength;
+    alpha_out = emission_color.a;
 }
 """,
         input_sockets=["Color", "Strength"],
-        output_sockets=["emission"]
+        output_properties=[
+            FragmentShaderLink.EMISSION,
+            FragmentShaderLink.ALPHA,
+        ]
     ),
 
     BsdfShaderFunction(
         code="""
 void node_bsdf_diffuse(vec4 color, float roughness, out vec3 albedo,
-        out float specular_out, out float roughness_out) {
+        out float specular_out, out float oren_nayar_roughness_out) {
     albedo = color.rgb;
     specular_out = 0.5;
-    roughness_out = 1.0;
+    oren_nayar_roughness_out = roughness;
 }
 """,
         input_sockets=[
             "Color",
             "Roughness",
         ],
-        output_sockets=[
-            "albedo",
-            "specular",
-            "oren_nayar_roughness",
+        output_properties=[
+            FragmentShaderLink.ALBEDO,
+            FragmentShaderLink.SPECULAR,
+            FragmentShaderLink.OREN_NAYAR_ROUGHNESS,
         ]
     ),
 
     BsdfShaderFunction(
         code="""
 void node_bsdf_glossy(vec4 color, float roughness, out vec3 albedo,
-        out float metallic_out, out float roughness_out){
+        out float metallic_out, out float roughness_out) {
     albedo = color.rgb;
     roughness_out = sqrt(roughness);
     metallic_out = 1.0;
@@ -163,21 +173,21 @@ void node_bsdf_glossy(vec4 color, float roughness, out vec3 albedo,
             "Color",
             "Roughness",
         ],
-        output_sockets=[
-            "albedo",
-            "metallic",
-            "roughness",
+        output_properties=[
+            FragmentShaderLink.ALBEDO,
+            FragmentShaderLink.METALLIC,
+            FragmentShaderLink.ROUGHNESS,
         ]
     ),
 
     BsdfShaderFunction(
         code="""
 void node_bsdf_transparent(vec4 color, out float alpha) {
-    alpha = 0.0;
+    alpha = clamp(1.0 - dot(color.rgb, vec3(0.3333334)), 0.0, 1.0);
 }
 """,
         input_sockets=['Color'],
-        output_sockets=['alpha'],
+        output_properties=[FragmentShaderLink.ALPHA],
     ),
 
     BsdfShaderFunction(
@@ -198,19 +208,19 @@ void node_bsdf_glass(vec4 color, float roughness, float IOR, out vec3 albedo,
             "Roughness",
             "IOR",
         ],
-        output_sockets=[
-            "albedo",
-            "alpha",
-            "specular",
-            "roughness",
-            "transmission",
-            "ior",
+        output_properties=[
+            FragmentShaderLink.ALBEDO,
+            FragmentShaderLink.ALPHA,
+            FragmentShaderLink.SPECULAR,
+            FragmentShaderLink.ROUGHNESS,
+            FragmentShaderLink.TRANSMISSION,
+            FragmentShaderLink.IOR,
         ]
     ),
 
     # trivial converter node functions
     ShaderFunction(code="""
-void node_rgb_to_bw(vec4 color, out float result){
+void node_rgb_to_bw(vec4 color, out float result) {
     result = color.r * 0.2126 + color.g * 0.7152 + color.b * 0.0722;
 }
 """),
@@ -239,7 +249,7 @@ void node_combine_rgb(float r, float g, float b, out vec4 color) {
 
     ShaderFunction(code="""
 void node_bump(float strength, float dist, float height, vec3 normal,
-               vec3 surf_pos, float invert, out vec3 out_normal){
+               vec3 surf_pos, float invert, out vec3 out_normal) {
     if (invert != 0.0) {
         dist *= -1.0;
     }
@@ -492,7 +502,7 @@ void node_math_power_clamp(float val1, float val2, out float outval) {
 """),
 
     ShaderFunction(code="""
-void node_math_logarithm_clamp(float val1, float val2, out float outval){
+void node_math_logarithm_clamp(float val1, float val2, out float outval) {
     if (val1 > 0.0  && val2 > 0.0)
         outval = clamp(log2(val1) / log2(val2), 0.0, 1.0);
     else
@@ -550,6 +560,70 @@ void node_vector_math_normalize(vec3 v, out vec3 outvec, out float outval) {
 """),
 
     # non-node function:
+    ShaderFunction(code="""
+void space_convert_zup_to_yup(inout vec3 dir) {
+    dir = mat3(vec3(1, 0, 0), vec3(0, 0, -1), vec3(0, 1, 0)) * dir;
+}
+"""),
+
+    ShaderFunction(code="""
+void space_convert_yup_to_zup(inout vec3 dir) {
+    dir = mat3(vec3(1, 0, 0), vec3(0, 0, 1), vec3(0, -1, 0)) * dir;
+}
+"""),
+
+    ShaderFunction(code="""
+void dir_space_convert_view_to_model(inout vec3 dir,
+        in mat4 inv_model_mat, in mat4 inv_view_mat) {
+    dir = normalize( inv_model_mat * (inv_view_mat * vec4(dir, 0.0))).xyz;
+}
+"""),
+
+    ShaderFunction(code="""
+void point_space_convert_view_to_model(inout vec3 pos,
+        in mat4 inv_model_mat, in mat4 inv_view_mat) {
+    pos = (inv_model_mat * (inv_view_mat * vec4(pos, 1.0))).xyz;
+}
+"""),
+
+    ShaderFunction(code="""
+void dir_space_convert_model_to_view(inout vec3 dir,
+        in mat4 view_mat, in mat4 model_mat) {
+    dir = normalize(view_mat * (model_mat * vec4(dir, 0.0))).xyz;
+}
+"""),
+
+    ShaderFunction(code="""
+void point_space_convert_model_to_view(inout vec3 pos,
+        in mat4 view_mat, in mat4 model_mat) {
+    pos = (view_mat * (model_mat * vec4(pos, 1.0))).xyz;
+}
+"""),
+
+    ShaderFunction(code="""
+void dir_space_convert_view_to_world(inout vec3 dir, in mat4 inv_view_mat) {
+    dir = normalize(inv_view_mat * vec4(dir, 0.0)).xyz;
+}
+"""),
+
+    ShaderFunction(code="""
+void point_space_convert_view_to_world(inout vec3 pos, in mat4 inv_view_mat) {
+    pos = (inv_view_mat * vec4(pos, 1.0)).xyz;
+}
+"""),
+
+    ShaderFunction(code="""
+void dir_space_convert_world_to_view(inout vec3 dir, in mat4 view_mat) {
+    dir = normalize(view_mat * vec4(dir, 0.0)).xyz;
+}
+"""),
+
+    ShaderFunction(code="""
+void point_space_convert_world_to_view(inout vec3 pos, in mat4 view_mat) {
+    pos = (view_mat * vec4(dir, 1.0)).xyz;
+}
+"""),
+
     ShaderFunction(code="""
 void refraction_fresnel(vec3 view_dir, vec3 normal, float ior, out float kr) {
 // reference [https://www.scratchapixel.com/lessons/
@@ -614,6 +688,12 @@ def convert_node_to_function_name(node):
     return function_name_base
 
 
+def node_has_function(node):
+    """Check if a shader node has associated functions"""
+    func_name = convert_node_to_function_name(node)
+    return func_name in FUNCTION_NAME_MAPPING
+
+
 def find_node_function(node):
     """Given a material node, return its corresponding function"""
     function_name = convert_node_to_function_name(node)

+ 78 - 0
io_scene_godot/converters/material/script_shader/shader_links.py

@@ -0,0 +1,78 @@
+"""Data structure represents the link between 'SHADER' type sockets"""
+
+
+class FragmentShaderLink:
+    # pylint: disable-msg=too-many-instance-attributes
+    """due to not able to make a closure for SHADER link as blender does,
+    here aggregate godot fragment shader output to simulate"""
+    ALBEDO = 'albedo'
+    ALPHA = 'alpha'
+    SSS_STRENGTH = 'sss_strength'
+    SPECULAR = 'specular'
+    METALLIC = 'metallic'
+    ROUGHNESS = 'roughness'
+    OREN_NAYAR_ROUGHNESS = 'oren_nayar_roughness'
+    CLEARCOAT = 'clearcoat'
+    CLEARCOAT_GLOSS = 'clearcoat_gloss'
+    ANISOTROPY = 'anisotropy'
+    TRANSMISSION = 'transmission'
+    IOR = 'ior'
+    EMISSION = 'emission'
+    NORMAL = 'normal'
+    TANGENT = 'tangent'
+
+    TYPES = {
+        ALBEDO: 'vec3',
+        ALPHA: 'float',
+        SSS_STRENGTH: 'float',
+        SPECULAR: 'float',
+        METALLIC: 'float',
+        ROUGHNESS: 'float',
+        OREN_NAYAR_ROUGHNESS: 'float',
+        CLEARCOAT: 'float',
+        CLEARCOAT_GLOSS: 'float',
+        ANISOTROPY: 'float',
+        TRANSMISSION: 'float',
+        IOR: 'float',
+        EMISSION: 'vec3',
+        NORMAL: 'vec3',
+        TANGENT: 'vec3',
+    }
+
+    ALL_PROPERTIES = (
+        ALBEDO, ALPHA, SSS_STRENGTH, SPECULAR,
+        METALLIC, ROUGHNESS, OREN_NAYAR_ROUGHNESS,
+        CLEARCOAT, CLEARCOAT_GLOSS, ANISOTROPY,
+        TRANSMISSION, IOR, EMISSION, NORMAL, TANGENT
+    )
+
+    def __init__(self):
+        # default only has albedo
+        self.albedo = None
+        self.alpha = None
+        self.sss_strength = None
+        self.specular = None
+        self.metallic = None
+        self.roughness = None
+        self.oren_nayar_roughness = None
+        self.clearcoat = None
+        self.clearcoat_gloss = None
+        self.anisotropy = None
+        self.transmission = None
+        self.ior = None
+        self.emission = None
+        self.normal = None
+        self.tangent = None
+
+    def set_property(self, prop_name, new_var):
+        """set an inner property of shader link"""
+        setattr(self, prop_name, new_var)
+
+    def get_property(self, prop_name):
+        """get the value of an inner property"""
+        return getattr(self, prop_name)
+
+    @classmethod
+    def get_property_type(cls, prop_name):
+        """get property type in string format"""
+        return cls.TYPES[prop_name]

+ 0 - 126
io_scene_godot/converters/material_node_tree/exporters.py

@@ -1,126 +0,0 @@
-"""Interface for node tree exporter"""
-import os
-import logging
-from shutil import copyfile
-import bpy
-from .shaders import ShaderGlobals
-from .node_vistors import find_node_visitor
-from ...structures import InternalResource, ExternalResource
-
-
-def find_material_output_node(node_tree):
-    """Find materia output node in the material node tree, if
-    two output nodes found, raise error"""
-    output_node = None
-    for node in node_tree.nodes:
-        if node.bl_idname == 'ShaderNodeOutputMaterial':
-            if output_node is None:
-                output_node = node
-            else:
-                logging.warning(
-                    "More than one material output node find",
-                )
-    return output_node
-
-
-def export_texture(escn_file, export_settings, image):
-    """Export texture image as an external resource"""
-    resource_id = escn_file.get_external_resource(image)
-    if resource_id is not None:
-        return resource_id
-
-    dst_dir_path = os.path.dirname(export_settings['path'])
-    dst_path = dst_dir_path + os.sep + image.name
-
-    if image.packed_file is not None:
-        # image is packed into .blend file
-        image_extension = '.' + image.file_format.lower()
-        if not image.name.endswith(image_extension):
-            dst_path = dst_path + image_extension
-        image.filepath_raw = dst_path
-        image.save()
-    else:
-        if image.filepath_raw.startswith("//"):
-            src_path = bpy.path.abspath(image.filepath_raw)
-        else:
-            src_path = image.filepath_raw
-        if os.path.abspath(src_path) != os.path.abspath(dst_path):
-            copyfile(src_path, dst_path)
-
-    img_resource = ExternalResource(dst_path, "Texture")
-    return escn_file.add_external_resource(img_resource, image)
-
-
-def traversal_tree_from_socket(shader, root_socket):
-    """Deep frist traversal the node tree from a root socket"""
-    if shader.is_socket_cached(root_socket):
-        return shader.fetch_variable_from_socket(root_socket)
-
-    def get_unvisited_depend_node(node):
-        """Return an unvisited node linked to the current node"""
-        for socket in node.inputs:
-            if socket.is_linked and not shader.is_socket_cached(socket):
-                return socket.links[0].from_node
-        return None
-
-    stack = list()
-    cur_node = root_socket.links[0].from_node
-
-    while stack or cur_node is not None:
-        while True:
-            next_node = get_unvisited_depend_node(cur_node)
-            if next_node is None:
-                break
-            stack.append(cur_node)
-            cur_node = next_node
-
-        visitor = find_node_visitor(shader, cur_node)
-        shader.append_comment_line("node: {}".format(cur_node.name))
-        shader.append_comment_line("type: {}".format(cur_node.bl_idname))
-        visitor(shader, cur_node)
-        shader.append_empty_line()
-
-        if stack:
-            cur_node = stack.pop()
-        else:
-            cur_node = None
-
-    return shader.fetch_variable_from_socket(root_socket)
-
-
-def export_node_tree(escn_file, export_settings, cycle_mat, shader_mat):
-    """Export cycles material to godot shader script"""
-    shader_globals = ShaderGlobals()
-    fragment_shader = shader_globals.fragment_shader
-    vertex_shader = shader_globals.vertex_shader
-
-    mat_output_node = find_material_output_node(cycle_mat.node_tree)
-    if mat_output_node is not None:
-        surface_socket = mat_output_node.inputs['Surface']
-        displacement_socket = mat_output_node.inputs['Displacement']
-
-        if surface_socket.is_linked:
-            fragment_shader.add_bsdf_surface(
-                traversal_tree_from_socket(
-                    fragment_shader, surface_socket
-                )
-            )
-
-        if displacement_socket.is_linked:
-            fragment_shader.add_bump_displacement(
-                traversal_tree_from_socket(
-                    fragment_shader, displacement_socket
-                )
-            )
-
-    shader_resource = InternalResource('Shader', cycle_mat.node_tree.name)
-    shader_resource['code'] = '"{}"'.format(shader_globals.to_string())
-    resource_id = escn_file.add_internal_resource(
-        shader_resource, cycle_mat.node_tree
-    )
-    shader_mat['shader'] = "SubResource({})".format(resource_id)
-
-    for image, uniform_var in shader_globals.textures.items():
-        resource_id = export_texture(escn_file, export_settings, image)
-        shader_param_key = 'shader_param/{}'.format(str(uniform_var))
-        shader_mat[shader_param_key] = "ExtResource({})".format(resource_id)

+ 0 - 554
io_scene_godot/converters/material_node_tree/node_vistors.py

@@ -1,554 +0,0 @@
-"""A set of node visitor functions to convert material node to shader script"""
-import logging
-from collections import deque
-import mathutils
-from .shaders import (FragmentShader, VertexShader,
-                      Value, Variable, FragmentBSDFContainer)
-from .shader_functions import find_node_function
-from ...structures import ValidationError
-
-
-def _is_normal_texture(image_texture_node):
-    assert image_texture_node.bl_idname == 'ShaderNodeTexImage'
-    node_queue = deque()
-    for link in image_texture_node.outputs['Color'].links:
-        node_queue.append((link.to_node, link.to_socket))
-
-    while node_queue:
-        node, socket = node_queue.popleft()
-        if (socket.name == 'Color' and
-                node.bl_idname == 'ShaderNodeNormalMap'):
-            return True
-        for sock in node.outputs:
-            for link in sock.links:
-                node_queue.append((link.to_node, link.to_socket))
-
-    return False
-
-
-def _mix_fragment_bsdf(frag_shader, shader_node_a, shader_node_b,
-                       output, fac):
-    for attribute_name in FragmentBSDFContainer.attribute_names_iterable():
-        attr_a = shader_node_a.get_attribute(attribute_name)
-        attr_b = shader_node_b.get_attribute(attribute_name)
-
-        if attribute_name == 'alpha':
-            # if one shader input has alpha, the other one should
-            # default to have alpha = 1.0
-            if attr_a and not attr_b:
-                attr_b = Value('float', 1.0)
-            elif attr_b and not attr_a:
-                attr_a = Value('float', 1.0)
-
-        if attr_a and attr_b:
-            attr_type = FragmentBSDFContainer.attribute_type(
-                attribute_name)
-            if attribute_name in ("normal", "tangent"):
-                # don't mix normal and tangent, use default
-                continue
-            mix_code_pattern = '{} = mix({}, {}, {});'
-            attr_mixed = frag_shader.define_variable(
-                attr_type, attribute_name
-            )
-            frag_shader.append_code_line(
-                mix_code_pattern,
-                (attr_mixed, attr_a, attr_b, fac)
-            )
-            output.set_attribute(attribute_name, attr_mixed)
-        elif attr_a:
-            output.set_attribute(attribute_name, attr_a)
-        elif attr_b:
-            output.set_attribute(attribute_name, attr_b)
-
-
-def visit_add_shader_node(shader, node):
-    """alpha is added with its complementary, other attributes are averaged"""
-    output = FragmentBSDFContainer()
-
-    shader_socket_a = node.inputs[0]
-    if shader_socket_a.is_linked:
-        in_shader_a = shader.fetch_variable_from_socket(shader_socket_a)
-    else:
-        in_shader_a = FragmentBSDFContainer.default()
-
-    shader_socket_b = node.inputs[1]
-    if shader_socket_b.is_linked:
-        in_shader_b = shader.fetch_variable_from_socket(shader_socket_b)
-    else:
-        in_shader_b = FragmentBSDFContainer.default()
-
-    _mix_fragment_bsdf(shader, in_shader_a, in_shader_b, output, 0.5)
-
-    shader.assign_variable_to_socket(node.outputs[0], output)
-
-
-def visit_mix_shader_node(shader, node):
-    """simply a mix of each attribute, note that for unconnect shader input,
-    albedo would default to black and alpha would default to 1.0."""
-    output = FragmentBSDFContainer()
-
-    in_fac_socket = node.inputs['Fac']
-    in_fac = Value('float', in_fac_socket.default_value)
-    if in_fac_socket.is_linked:
-        in_fac = shader.fetch_variable_from_socket(in_fac_socket)
-
-    in_shader_a = FragmentBSDFContainer.default()
-    in_shader_socket_a = node.inputs[1]
-    if in_shader_socket_a.is_linked:
-        in_shader_a = shader.fetch_variable_from_socket(in_shader_socket_a)
-
-    in_shader_b = FragmentBSDFContainer.default()
-    in_shader_socket_b = node.inputs[2]
-    if in_shader_socket_b.is_linked:
-        in_shader_b = shader.fetch_variable_from_socket(in_shader_socket_b)
-
-    _mix_fragment_bsdf(shader, in_shader_a, in_shader_b, output, in_fac)
-
-    shader.assign_variable_to_socket(node.outputs[0], output)
-
-
-def visit_bsdf_node(shader, node):
-    """Visitor for trivial bsdf nodes,
-    output in the format of a FragmentBSDFContainer"""
-    function = find_node_function(node)
-
-    output = FragmentBSDFContainer()
-    in_arguments = list()
-    out_arguments = list()
-
-    for socket_name in function.in_sockets:
-        socket = node.inputs[socket_name]
-        if socket.is_linked:
-            var = shader.fetch_variable_from_socket(socket)
-            in_arguments.append(var)
-        else:
-            input_value = Value.create_from_blender_value(
-                socket.default_value)
-            variable = shader.define_variable_from_socket(
-                node, socket
-            )
-            shader.append_assignment_code(variable, input_value)
-            in_arguments.append(variable)
-
-    for attr_name in function.out_sockets:
-        var_type = FragmentBSDFContainer.attribute_type(attr_name)
-        new_var = shader.define_variable(
-            var_type, 'out_' + attr_name
-        )
-        out_arguments.append(new_var)
-
-        if attr_name == "transmission" and "Transmission" in node.inputs:
-            # due to different implementation of transmission between
-            # godot and blender, only export it when it is set.
-            socket = node.inputs["Transmission"]
-            if not socket.is_linked and socket.default_value == 0.0:
-                continue
-        output.set_attribute(attr_name, new_var)
-
-    shader.add_function_call(function, in_arguments, out_arguments)
-
-    # normal and tangent don't go to bsdf functions
-    normal_socket = node.inputs.get('Normal', None)
-    tangent_socket = node.inputs.get('Tangent', None)
-    # normal and tangent input to shader node is in view space
-    for name, socket in (
-            ('normal', normal_socket), ('tangent', tangent_socket)):
-        if socket is not None and socket.is_linked:
-            world_space_dir = shader.fetch_variable_from_socket(socket)
-            # convert to y-up axis
-            shader.zup_to_yup(world_space_dir)
-            # convert direction to view space
-            shader.world_to_view(world_space_dir)
-            output.set_attribute(name, world_space_dir)
-
-    if node.bl_idname in ('ShaderNodeBsdfGlass', 'ShaderNodeBsdfPrincipled'):
-        shader.glass_effect = True
-
-    shader.assign_variable_to_socket(node.outputs[0], output)
-
-
-def visit_reroute_node(shader, node):
-    """For reroute node, traversal it's child and cache the output result"""
-    input_socket = node.inputs[0]
-    if input_socket.is_linked:
-        var = shader.fetch_variable_from_socket(input_socket)
-    else:
-        logging.warning(
-            "'%s' has no input, at '%s'", node.bl_idname, node.name
-        )
-        var = Value('vec3', (1.0, 1.0, 1.0))
-
-    for output_socket in node.outputs:
-        shader.assign_variable_to_socket(output_socket, var)
-
-
-def visit_bump_node(shader, node):
-    """Convert bump node to shader script"""
-    function = find_node_function(node)
-
-    in_arguments = list()
-    for socket in node.inputs:
-        if socket.is_linked:
-            var = shader.fetch_variable_from_socket(socket)
-            if socket.identifier == 'Normal':
-                # convert from model, z-up to view y-up
-                # bump function is calculate in view space y-up
-                shader.zup_to_yup(var)
-                shader.world_to_view(var)
-                in_arguments.append(var)
-            else:
-                in_arguments.append(var)
-        else:
-            if socket.identifier == 'Normal':
-                in_arguments.append(Variable('vec3', 'NORMAL'))
-            else:
-                in_arguments.append(
-                    Value.create_from_blender_value(socket.default_value)
-                )
-
-    in_arguments.append(Variable('vec3', 'VERTEX'))
-    if node.invert:
-        in_arguments.append(Value('float', 1.0))
-    else:
-        in_arguments.append(Value('float', 0.0))
-
-    out_normal = shader.define_variable(
-        'vec3', 'out_normal'
-    )
-    shader.add_function_call(function, in_arguments, [out_normal])
-
-    if isinstance(shader, FragmentShader):
-        # convert output normal to world_space
-        shader.view_to_world(out_normal)
-    shader.yup_to_zup(out_normal)
-
-    shader.assign_variable_to_socket(node.outputs[0], out_normal)
-
-
-def visit_normal_map_node(shader, node):
-    """Convert normal map node to shader script, note that it can not
-    be used in vertex shader"""
-    if isinstance(shader, VertexShader):
-        raise ValidationError(
-            "'{}' not support in true displacement, at '{}'".format(
-                node.bl_idname,
-                node.name
-            )
-        )
-
-    in_arguments = list()
-    for socket in node.inputs:
-        if socket.is_linked:
-            in_arguments.append(
-                shader.fetch_variable_from_socket(socket)
-            )
-        else:
-            in_arguments.append(
-                Value.create_from_blender_value(socket.default_value)
-            )
-    function = find_node_function(node)
-    output_normal = shader.define_variable('vec3', 'out_normal')
-    if node.space == 'TANGENT':
-        in_arguments.append(Variable('vec3', 'NORMAL'))
-        in_arguments.append(Variable('vec3', 'TANGENT'))
-        in_arguments.append(Variable('vec3', 'BINORMAL'))
-        shader.add_function_call(function, in_arguments, [output_normal])
-        shader.view_to_world(output_normal)
-        shader.yup_to_zup(output_normal)
-
-    elif node.space == 'WORLD':
-        in_arguments.append(Variable('vec3', 'NORMAL'))
-        in_arguments.append(shader.invert_view_mat)
-        shader.add_function_call(function, in_arguments, [output_normal])
-        shader.yup_to_zup(output_normal)
-
-    elif node.space == 'OBJECT':
-        in_arguments.append(Variable('vec3', 'NORMAL'))
-        in_arguments.append(shader.invert_view_mat)
-        in_arguments.append(Variable('mat4', 'WORLD_MATRIX'))
-        shader.add_function_call(function, in_arguments, [output_normal])
-        shader.yup_to_zup(output_normal)
-
-    shader.assign_variable_to_socket(node.outputs[0], output_normal)
-
-
-def visit_texture_coord_node(shader, node):
-    """Convert texture coordinate node to shader script"""
-    if node.outputs['UV'].is_linked:
-        shader.assign_variable_to_socket(
-            node.outputs['UV'],
-            Value("vec3", ('UV', 0.0)),
-        )
-
-    if isinstance(shader, FragmentShader):
-        if node.outputs['Window'].is_linked:
-            shader.assign_variable_to_socket(
-                node.outputs['Window'],
-                Value("vec3", ('SCREEN_UV', 0.0)),
-            )
-        if node.outputs['Camera'].is_linked:
-            shader.assign_variable_to_socket(
-                node.outputs['Camera'],
-                Value("vec3", ('VERTEX.xy', '-VERTEX.z')),
-            )
-
-    view_mat = Variable('mat4', 'INV_CAMERA_MATRIX')
-    world_mat = Variable('mat4', 'WORLD_MATRIX')
-    normal = Variable('vec3', 'NORMAL')
-    position = Variable('vec3', 'VERTEX')
-
-    if node.outputs['Normal'].is_linked:
-        normal_socket = node.outputs['Normal']
-        output_normal = shader.define_variable_from_socket(
-            node, normal_socket
-        )
-        shader.append_assignment_code(output_normal, normal)
-        if isinstance(shader, FragmentShader):
-            shader.view_to_model(output_normal)
-        shader.yup_to_zup(output_normal)
-        shader.assign_variable_to_socket(
-            normal_socket, output_normal
-        )
-
-    if node.outputs['Object'].is_linked:
-        obj_socket = node.outputs['Object']
-        output_obj_pos = shader.define_variable_from_socket(
-            node, obj_socket
-        )
-        shader.append_assignment_code(output_obj_pos, position)
-        if isinstance(shader, FragmentShader):
-            shader.view_to_model(output_obj_pos, False)
-        shader.yup_to_zup(output_obj_pos)
-        shader.assign_variable_to_socket(obj_socket, output_obj_pos)
-
-    if node.outputs['Reflection'].is_linked:
-        ref_socket = node.outputs['Reflection']
-        reflect_output = shader.define_variable_from_socket(
-            node, ref_socket
-        )
-        if isinstance(shader, FragmentShader):
-            shader.append_code_line(
-                ('{} = (inverse({}) * vec4('
-                 'reflect(normalize({}), {}), 0.0)).xyz;'),
-                (reflect_output, view_mat, position, normal)
-            )
-        else:
-            shader.append_code_line(
-                '{} = (reflect(normalize({}, {}), 0.0)).xyz;',
-                (reflect_output, position, normal)
-            )
-        shader.yup_to_zup(reflect_output)
-        shader.assign_variable_to_socket(ref_socket, reflect_output)
-
-    if node.outputs['Generated'].is_linked:
-        logging.warning(
-            'Texture coordinates `Generated` not supported'
-        )
-        shader.assign_variable_to_socket(
-            node.outputs['Generated'],
-            Value('vec3', (1.0, 1.0, 1.0))
-        )
-
-
-def visit_rgb_node(shader, node):
-    """Convert rgb input node to shader scripts"""
-    output = node.outputs[0]
-    shader.assign_variable_to_socket(
-        output,
-        Value.create_from_blender_value(output.default_value)
-    )
-
-
-def visit_image_texture_node(shader, node):
-    """Store image texture as a uniform"""
-    function = find_node_function(node)
-
-    in_arguments = list()
-
-    tex_coord = Value.create_from_blender_value(
-        node.inputs[0].default_value)
-    if node.inputs[0].is_linked:
-        tex_coord = shader.fetch_variable_from_socket(node.inputs[0])
-
-    if node.image is None:
-        logging.warning(
-            "Image Texture node '%s' has no image being set",
-            node.name
-        )
-
-    if node.image is None or node.image not in shader.global_ref.textures:
-        shader.append_comment_line(
-            'texture image from node {}'.format(node.name))
-        if _is_normal_texture(node):
-            tex_image_var = shader.global_ref.define_uniform(
-                "sampler2D", node.image.name, hint='hint_normal'
-            )
-        else:
-            tex_image_var = shader.global_ref.define_uniform(
-                "sampler2D", node.image.name,
-            )
-
-        shader.global_ref.add_image_texture(
-            tex_image_var, node.image
-        )
-    else:
-        tex_image_var = shader.global_ref.textures[node.image]
-
-    in_arguments.append(tex_coord)
-    in_arguments.append(tex_image_var)
-
-    out_arguments = list()
-
-    for socket in node.outputs:
-        output_var = shader.define_variable_from_socket(
-            node, socket
-        )
-        out_arguments.append(output_var)
-        shader.assign_variable_to_socket(socket, output_var)
-
-    shader.add_function_call(function, in_arguments, out_arguments)
-
-
-def visit_mapping_node(shader, node):
-    """Mapping node which apply transform onto point or direction"""
-    function = find_node_function(node)
-
-    rot_mat = node.rotation.to_matrix().to_4x4()
-    loc_mat = mathutils.Matrix.Translation(node.translation)
-    sca_mat = mathutils.Matrix((
-        (node.scale[0], 0, 0),
-        (0, node.scale[1], 0),
-        (0, 0, node.scale[2]),
-    )).to_4x4()
-
-    in_vec = Value("vec3", (0.0, 0.0, 0.0))
-    if node.inputs[0].is_linked:
-        in_vec = shader.fetch_variable_from_socket(node.inputs[0])
-
-    if node.vector_type == "TEXTURE":
-        # Texture: Transform a texture by inverse
-        # mapping the texture coordinate
-        transform_mat = (loc_mat * rot_mat * sca_mat).inverted_safe()
-    elif node.vector_type == "POINT":
-        transform_mat = loc_mat * rot_mat * sca_mat
-    else:  # node.vector_type in ("VECTOR", "NORMAL")
-        # no translation for vectors
-        transform_mat = rot_mat * sca_mat
-
-    mat = Value.create_from_blender_value(transform_mat)
-    clamp_min = Value.create_from_blender_value(node.min)
-    clamp_max = Value.create_from_blender_value(node.max)
-    use_min = Value("float", 1.0 if node.use_min else 0.0)
-    use_max = Value("float", 1.0 if node.use_max else 0.0)
-
-    in_arguments = list()
-    in_arguments.append(in_vec)
-    in_arguments.append(mat)
-    in_arguments.append(clamp_min)
-    in_arguments.append(clamp_max)
-    in_arguments.append(use_min)
-    in_arguments.append(use_max)
-
-    out_vec = shader.define_variable_from_socket(
-        node, node.outputs[0]
-    )
-    shader.add_function_call(function, in_arguments, [out_vec])
-
-    if node.vector_type == "NORMAL":
-        # need additonal normalize
-        shader.append_code_line(
-            '{} = normalize({});',
-            (out_vec, out_vec)
-        )
-    shader.assign_variable_to_socket(node.outputs[0], out_vec)
-
-
-def visit_converter_node(shader, node):
-    """For genearl converter node, which has inputs and outputs and can be
-    parsed as a shader function"""
-    function = find_node_function(node)
-    in_arguments = list()
-
-    for socket in node.inputs:
-        if socket.is_linked:
-            # iput socket only has one link
-            in_arguments.append(
-                shader.fetch_variable_from_socket(socket)
-            )
-        else:
-            input_value = Value.create_from_blender_value(
-                socket.default_value)
-            variable = shader.define_variable_from_socket(
-                node, socket
-            )
-            shader.append_assignment_code(variable, input_value)
-            in_arguments.append(variable)
-
-    out_arguments = list()
-
-    for socket in node.outputs:
-        new_var = shader.define_variable_from_socket(node, socket)
-        shader.assign_variable_to_socket(socket, new_var)
-        out_arguments.append(new_var)
-
-    shader.add_function_call(function, in_arguments, out_arguments)
-
-
-def visit_tangent_node(shader, node):
-    """Visit tangent node"""
-    if node.direction_type != 'UV_MAP':
-        logging.warning(
-            'tangent space Radial not supported at %s',
-            node.name
-        )
-    shader.assign_variable_to_socket(
-        node.outputs[0], Variable('vec3', 'TANGENT')
-    )
-
-
-def visit_uvmap_node(shader, node):
-    """Visit UV Map node"""
-    if node.from_dupli:
-        raise ValidationError(
-            "'{}' from_dupli not supported, at '{}'".format(
-                node.bl_idname,
-                node.name
-            )
-        )
-
-    shader.assign_variable_to_socket(
-        node.outputs['UV'],
-        Value("vec3", ('UV', 0.0)),
-    )
-
-    logging.warning(
-        "'%s' use the active UV map, make sure the correct "
-        "one is selected, at'%s", node.bl_idname, node.name
-    )
-
-
-NODE_VISITOR_FUNCTIONS = {
-    'ShaderNodeMapping': visit_mapping_node,
-    'ShaderNodeTexImage': visit_image_texture_node,
-    'ShaderNodeTexCoord': visit_texture_coord_node,
-    'ShaderNodeRGB': visit_rgb_node,
-    'ShaderNodeNormalMap': visit_normal_map_node,
-    'ShaderNodeBump': visit_bump_node,
-    'NodeReroute': visit_reroute_node,
-    'ShaderNodeMixShader': visit_mix_shader_node,
-    'ShaderNodeAddShader': visit_add_shader_node,
-    'ShaderNodeTangent': visit_tangent_node,
-    'ShaderNodeUVMap': visit_uvmap_node,
-}
-
-
-def find_node_visitor(shader, node):
-    """Return a visitor function for the node"""
-    if node.bl_idname in NODE_VISITOR_FUNCTIONS:
-        return NODE_VISITOR_FUNCTIONS[node.bl_idname]
-
-    if node.outputs[0].identifier in ('Emission', 'BSDF', 'BSSRDF'):
-        # for shader node output bsdf closure
-        return visit_bsdf_node
-
-    return visit_converter_node

+ 0 - 627
io_scene_godot/converters/material_node_tree/shaders.py

@@ -1,627 +0,0 @@
-"""Class represents fragment shader and vertex shader"""
-import re
-import collections
-import bpy
-import mathutils
-from .shader_functions import find_function_by_name
-from ...structures import Array, ValidationError
-
-
-def _clear_variable_name(raw_var_name):
-    """Remove illegal charactors from given name and
-    return the cleared one"""
-    return re.sub(r'\W', '', raw_var_name)
-
-
-class Variable:
-    """A variable in material shader scripts"""
-
-    def __init__(self, var_type, var_name):
-        self.type = var_type
-        self.name = var_name
-
-    def __str__(self):
-        """Convert to string"""
-        return self.name
-
-
-class Value:
-    """A constant value in material shader scripts"""
-
-    def __init__(self, type_str, data):
-        self.type = type_str
-        self.data = data
-
-    @classmethod
-    def create_from_blender_value(cls, blender_value):
-        """Creaate a Value() from a blender object"""
-        if isinstance(
-                blender_value, (bpy.types.bpy_prop_array, mathutils.Vector)):
-            tmp = list()
-            for val in blender_value:
-                tmp.append(val)
-
-            return Value("vec{}".format(len(tmp)), tuple(tmp))
-
-        if isinstance(blender_value, mathutils.Matrix):
-            # godot mat is column major order
-            mat = blender_value.transposed()
-            column_vec_list = list()
-            for vec in mat:
-                column_vec_list.append(cls.create_from_blender_value(vec))
-
-            return Value(
-                "mat{}".format(len(column_vec_list)),
-                tuple(column_vec_list)
-            )
-
-        return Value("float", blender_value)
-
-    def __str__(self):
-        """Convert to string"""
-        if self.type.startswith(('vec', 'mat')):
-            return "{}({})".format(
-                self.type,
-                ', '.join([str(x) for x in self.data])
-            )
-        return str(self.data)
-
-
-class FragmentBSDFContainer:
-    """Several attributes altogether represents
-    blender shader output closure"""
-    # 'roughness' mixing of mix_shader and add_shader, there are two
-    # different 'roughness' among all the shaders. one is Oren Nayar
-    # roughness term used in diffuse shader, the other one is ggx roughness
-    # used in glossy, principle, etc.
-    _ATTRIBUTES_META = collections.OrderedDict([
-        ('albedo', 'vec3'),
-        ('alpha', 'float'),
-        ('sss_strength', 'float'),
-        ('specular', 'float'),
-        ('metallic', 'float'),
-        ('roughness', 'float'),
-        ('oren_nayar_roughness', 'float'),
-        ('clearcoat', 'float'),
-        ('clearcoat_gloss', 'float'),
-        ('anisotropy', 'float'),
-        ('transmission', 'float'),
-        ('ior', 'float'),
-        ('emission', 'vec3'),
-        ('normal', 'vec3'),
-        ('tangent', 'vec3'),
-    ])
-
-    def __init__(self):
-        self._data = collections.OrderedDict()
-
-    def get_attribute(self, attr_name):
-        """Get a property value, if the property is empty return None"""
-        return self._data.get(attr_name, None)
-
-    def set_attribute(self, attr_name, attr_value):
-        """Set a property, note that property value can be
-        either Value() or Variable()"""
-        self._data[attr_name] = attr_value
-
-    @classmethod
-    def attribute_names_iterable(cls):
-        """Return an iteralble of all attribute names"""
-        return cls._ATTRIBUTES_META.keys()
-
-    @classmethod
-    def attribute_type(cls, attr_name):
-        """Return a type a given attribute"""
-        return cls._ATTRIBUTES_META[attr_name]
-
-    @classmethod
-    def default(cls):
-        """Default closure for unconnected socket"""
-        new_closure = cls()
-        new_closure.set_attribute('albedo', Value('vec3', (0.0, 0.0, 0.0)))
-        return new_closure
-
-
-class BaseShader:
-    """Shared methods in vertex shader and fragment shader"""
-    def __init__(self, formated_array, global_ref):
-        # array of code
-        self.code_array = formated_array
-
-        # reference of global scripts,
-        # used to create uniform, add function
-        self.global_ref = global_ref
-
-        # maintain a mapping from all output sockets
-        # to already calculated var
-        self._socket_to_var_map = dict()
-        # use to create unique variable name
-        self._variable_count = 0
-
-    def append_code_line(self, code_pattern, variables=()):
-        """Format a line of code string and append it to codes"""
-        assert code_pattern[-1] == ';'
-        self.code_array.append(
-            code_pattern.format(*tuple([str(x) for x in variables]))
-        )
-
-    def append_code_lines_left(self, lines):
-        """Format a line of code string and append it to codes"""
-        for i, line in enumerate(lines):
-            assert line[-1] == ';'
-            self.code_array.insert(i, line)
-
-    def append_comment_line(self, comment):
-        """Add a line of comment"""
-        self.code_array.append(
-            '// ' + comment
-        )
-
-    def append_empty_line(self):
-        """Add an empty linef"""
-        self.code_array.append("")
-
-    def _append_defination_code(self, var_to_define):
-        definition_str = '{} {};'.format(
-            var_to_define.type, str(var_to_define)
-        )
-        self.code_array.append(definition_str)
-
-    def define_variable(self, var_type, var_base_name):
-        """Create a unique variable, and define it in the shader script"""
-        self._variable_count += 1
-        raw_var_name = 'var{}_{}'.format(
-            self._variable_count,
-            var_base_name,
-        )
-        var_name = var_name = _clear_variable_name(raw_var_name)
-        new_var = Variable(var_type, var_name)
-        self._append_defination_code(new_var)
-        return new_var
-
-    def define_variable_from_socket(self, node, socket):
-        """Create a unique variable, variable name and type generate
-        from socket, and also define it in the shader script"""
-        if socket.type == 'RGBA':
-            var_type = 'vec4'
-        elif socket.type == 'VECTOR':
-            var_type = 'vec3'
-        elif socket.type == 'VALUE':
-            var_type = 'float'
-        else:
-            raise ValidationError(
-                "socket '{}' at '{}' is incorrectly connected".format(
-                    socket.identifier, node.name
-                )
-            )
-
-        self._variable_count += 1
-        raw_var_name = 'var{}_{}_{}'.format(
-            self._variable_count,
-            node.name,
-            socket.identifier
-        )
-        var_name = var_name = _clear_variable_name(raw_var_name)
-        new_var = Variable(var_type, var_name)
-        self._append_defination_code(new_var)
-        return new_var
-
-    def append_assignment_code(self, var_to_write, var_to_read):
-        """Assign a variable or value to another variable"""
-        assignment_str = '{} = {};'.format(
-            str(var_to_write), str(var_to_read)
-        )
-        self.code_array.append(assignment_str)
-
-    def is_socket_cached(self, input_socket):
-        """Return bool indicating whether an input socket
-        has a variable cached"""
-        if input_socket.links[0].from_socket in self._socket_to_var_map:
-            return True
-        return False
-
-    def fetch_variable_from_socket(self, input_socket):
-        """Given a input socket, return the variable assigned to that
-        socket, note that the variable is actually from the output socket
-        at the other side of the link"""
-        socket_link = input_socket.links[0]
-        var_from_link = self._socket_to_var_map[socket_link.from_socket]
-
-        if socket_link.from_socket.type == socket_link.to_socket.type:
-            return var_from_link
-
-        # if the type of two sockets are not matched,
-        # insert an implicit conversion
-        return self._implicit_socket_convert(
-            var_from_link,
-            socket_link.from_socket.type,
-            socket_link.to_socket.type,
-        )
-
-    def assign_variable_to_socket(self, output_socket, variable):
-        """Assign an output socket with a variable for later use"""
-        self._socket_to_var_map[output_socket] = variable
-
-    def _implicit_socket_convert(self, src_variable,
-                                 from_socket_type, to_socket_type):
-        """Implicitly convert variable type between a pair of socket with
-        different type. It is performed when you link two socket with
-        different color in node editor"""
-        if (to_socket_type == 'VALUE' and
-                from_socket_type in ('VECTOR', 'RGBA')):
-            converted_var = self.define_variable(
-                'float', 'converted_' + str(src_variable)
-            )
-            if from_socket_type == 'VECTOR':
-                src_variable = Value('vec4', (src_variable, 1.0))
-
-            function = find_function_by_name('node_rgb_to_bw')
-            self.add_function_call(function, [src_variable], [converted_var])
-            return converted_var
-
-        if to_socket_type == 'VECTOR' and from_socket_type == 'VALUE':
-            return Value('vec3', (src_variable,) * 3)
-
-        if to_socket_type == 'RGBA' and from_socket_type == 'VALUE':
-            return Value('vec4', (src_variable,) * 4)
-
-        if to_socket_type == 'RGBA' and from_socket_type == 'VECTOR':
-            converted_var = self.define_variable(
-                'vec4', 'auto_insert_VecToColor'
-            )
-            self.append_code_line(
-                ('{} = vec4(clamp({}, vec3(0.0, 0.0, 0.0),'
-                 'vec3(1.0, 1.0, 1.0)).xyz, 1.0);'),
-                (converted_var, src_variable)
-            )
-            return converted_var
-
-        if to_socket_type == 'VECTOR' and from_socket_type == 'RGBA':
-            converted_var = self.define_variable(
-                'vec3', 'converted_' + str(src_variable)
-            )
-            self.append_code_line(
-                '{} = {}.xyz;', (converted_var, src_variable)
-            )
-            return converted_var
-
-        raise ValidationError(
-            "Cannot link two sockets with type '{}' and '{}'".format(
-                from_socket_type, to_socket_type
-            )
-        )
-
-    @staticmethod
-    def _function_call_type_check(argument_var_list, param_type_list):
-        assert len(argument_var_list) == len(param_type_list)
-        for index, var in enumerate(argument_var_list):
-            assert var.type == param_type_list[index]
-
-    def add_function_call(self, function, in_arguments, out_arguments):
-        """Call function in shader scripts"""
-        self.global_ref.add_function(function)
-
-        # runtime check to make sure generated scripts is valid
-        self._function_call_type_check(in_arguments, function.in_param_types)
-        self._function_call_type_check(out_arguments, function.out_param_types)
-
-        invoke_str = '{}({}, {});'.format(
-            function.name,
-            ', '.join([str(x) for x in in_arguments]),
-            ', '.join([str(x) for x in out_arguments])
-        )
-        self.code_array.append(invoke_str)
-
-    def zup_to_yup(self, var_to_convert):
-        """Convert a vec3 from z-up space to y-up space"""
-        assert var_to_convert.type == 'vec3'
-        self.append_comment_line("convert from z-up to y-up")
-        self.append_code_line(
-            '{} = mat3(vec3(1, 0, 0), vec3(0, 0, -1), vec3(0, 1, 0)) * {};',
-            (var_to_convert, var_to_convert)
-        )
-
-    def yup_to_zup(self, var_to_convert):
-        """Convert a vec3 from y-up space to z-up space"""
-        assert var_to_convert.type == 'vec3'
-        self.append_comment_line("convert from y-up to z-up")
-        self.append_code_line(
-            '{} = mat3(vec3(1, 0, 0), vec3(0, 0, 1), vec3(0, -1, 0)) * {};',
-            (var_to_convert, var_to_convert)
-        )
-
-    def to_string(self):
-        """Serialze"""
-        return self.code_array.to_string()
-
-
-class FragmentShader(BaseShader):
-    """Fragment shader Script"""
-    def __init__(self, global_ref):
-        super().__init__(
-            Array(
-                prefix='\nvoid fragment() {\n\t',
-                seperator='\n\t',
-                suffix='\n}\n'
-            ),
-            global_ref
-        )
-
-        # flag would be set when glass_bsdf is used
-        self.glass_effect = False
-
-        self._invert_view_mat = None
-        self._invert_model_mat = None
-
-    @property
-    def invert_view_mat(self):
-        """Return inverted view matrix"""
-        if self._invert_view_mat is None:
-            self._invert_view_mat = Variable('mat4', 'inverted_view_matrix')
-            self.append_code_lines_left([
-                'mat4 {};'.format(str(self._invert_view_mat)),
-                '{} = inverse({});'.format(
-                    str(self._invert_view_mat),
-                    str(Variable('mat4', 'INV_CAMERA_MATRIX'))
-                )
-            ])
-        return self._invert_view_mat
-
-    @property
-    def invert_model_mat(self):
-        """Return inverted model matrix"""
-        if self._invert_model_mat is None:
-            self._invert_model_mat = Variable('mat4', 'inverted_model_matrix')
-            self.append_code_lines_left([
-                'mat4 {};'.format(str(self._invert_model_mat)),
-                '{} = inverse({});'.format(
-                    str(self._invert_model_mat),
-                    str(Variable('mat4', 'WORLD_MATRIX'))
-                )
-            ])
-        return self._invert_model_mat
-
-    def add_bsdf_surface(self, bsdf_output):
-        """Link bsdf output to godot fragment builtin out qualifiers"""
-        for name in ('albedo', 'sss_strength', 'specular', 'metallic',
-                     'roughness', 'clearcoat', 'clearcoat_gloss', 'emission',
-                     'normal'):
-            var = bsdf_output.get_attribute(name)
-            if var is not None:
-                self.code_array.append(
-                    '{} = {};'.format(name.upper(), str(var))
-                )
-
-        transmission_var = bsdf_output.get_attribute('transmission')
-        if transmission_var is not None:
-            self.append_code_line(
-                'TRANSMISSION = vec3(1.0, 1.0, 1.0) * {};',
-                (transmission_var,)
-            )
-
-        self.append_comment_line("uncomment it only when you set diffuse "
-                                 "mode to oren nayar")
-        self.append_comment_line(
-            'ROUGHNESS = oren_nayar_rougness'
-        )
-
-        tangent = bsdf_output.get_attribute('tangent')
-        anisotropy = bsdf_output.get_attribute('anisotropy')
-        if tangent is not None and anisotropy is not None:
-            self.append_code_line('ANISOTROPY = {};', (anisotropy,))
-            self.append_code_line(
-                'TANGENT = normalize(cross(cross({}, NORMAL), NORMAL));',
-                (tangent,),
-            )
-            self.append_code_line('BINORMAL = cross(TANGENT, NORMAL);')
-
-        alpha = bsdf_output.get_attribute('alpha')
-        if alpha is not None:
-            refraction_offset = self.global_ref.define_uniform(
-                'float', 'refraction_offset'
-            )
-            if self.glass_effect:
-                fresnel_func = find_function_by_name('refraction_fresnel')
-                in_arguments = list()
-                in_arguments.append(Variable('vec3', 'VERTEX'))
-                in_arguments.append(Variable('vec3', 'NORMAL'))
-                in_arguments.append(bsdf_output.get_attribute('ior'))
-                self.add_function_call(
-                    fresnel_func, in_arguments, [alpha]
-                )
-            self.append_code_line(
-                'EMISSION += textureLod(SCREEN_TEXTURE, SCREEN_UV - '
-                'NORMAL.xy * {}, ROUGHNESS).rgb * (1.0 - {});',
-                (refraction_offset, alpha)
-            )
-            self.append_code_line(
-                'ALBEDO *= {};',
-                (alpha,),
-            )
-            self.append_code_line(
-                'ALPHA = 1.0;'
-            )
-
-    def add_bump_displacement(self, displacement_output):
-        """Add bump displacement to fragment shader"""
-        # xxx: use tangent space if uv exists?
-        function = find_function_by_name('node_bump')
-
-        in_arguments = list()
-        # default bump parameters
-        in_arguments.append(Value('float', 1.0))
-        in_arguments.append(Value('float', 0.1))
-        in_arguments.append(displacement_output)
-        in_arguments.append(Variable('vec3', 'NORMAL'))
-        in_arguments.append(Variable('vec3', 'VERTEX'))
-        in_arguments.append(Value('float', 0.0))
-
-        out = Variable('vec3', 'NORMAL')
-        self.add_function_call(function, in_arguments, [out])
-
-    def view_to_model(self, var_to_convert, is_direction=True):
-        """Convert a vec3 from view space to model space,
-        note that conversion is done in y-up space"""
-        assert var_to_convert.type == 'vec3'
-        self.append_comment_line("convert from view space to model space")
-        if is_direction:
-            self.append_code_line(
-                '{} = normalize({} * ({} * vec4({}, 0.0))).xyz;',
-                (var_to_convert, self.invert_model_mat,
-                 self.invert_view_mat, var_to_convert)
-            )
-        else:
-            self.append_code_line(
-                '{} = ({} * ({} * vec4({}, 1.0))).xyz;',
-                (var_to_convert, self.invert_model_mat,
-                 self.invert_view_mat, var_to_convert)
-            )
-
-    def model_to_view(self, var_to_convert, is_direction=True):
-        """Convert a vec3 from model space to view space,
-        note that conversion is done in y-up space"""
-        assert var_to_convert.type == 'vec3'
-        self.append_comment_line("convert from model space to view space")
-        view_mat = Variable('mat4', 'INV_CAMERA_MATRIX')
-        model_mat = Variable('mat4', 'WORLD_MATRIX')
-        if is_direction:
-            self.append_code_line(
-                '{} = normalize({} * ({} * vec4({}, 0.0))).xyz;',
-                (var_to_convert, view_mat, model_mat, var_to_convert)
-            )
-        else:
-            self.append_code_line(
-                '{} = ({} * ({} * vec4({}, 1.0))).xyz;',
-                (var_to_convert, view_mat, model_mat, var_to_convert)
-            )
-
-    def view_to_world(self, var_to_convert, is_direction=True):
-        """Convert a vec3 from view space to world space,
-        note that it is done in y-up space"""
-        assert var_to_convert.type == 'vec3'
-        self.append_comment_line("convert from view space to world space")
-        if is_direction:
-            self.append_code_line(
-                '{} = normalize({} * vec4({}, 0.0)).xyz;',
-                (var_to_convert, self.invert_view_mat, var_to_convert)
-            )
-        else:
-            self.append_code_line(
-                '{} = ({} * vec4({}, 1.0)).xyz;',
-                (var_to_convert, self.invert_view_mat, var_to_convert)
-            )
-
-    def world_to_view(self, var_to_convert, is_direction=True):
-        """Convert a vec3 from world space to view space,
-        note that it is done in y-up space"""
-        assert var_to_convert.type == 'vec3'
-        self.append_comment_line("convert from world space to view space")
-        view_mat = Variable('mat4', 'INV_CAMERA_MATRIX')
-        if is_direction:
-            self.append_code_line(
-                '{} = normalize({} * vec4({}, 0.0)).xyz;',
-                (var_to_convert, view_mat, var_to_convert)
-            )
-        else:
-            self.append_code_line(
-                '{} = ({} * vec4({}, 1.0)).xyz;',
-                (var_to_convert, view_mat, var_to_convert)
-            )
-
-
-class VertexShader(BaseShader):
-    """Vertex shader scripts"""
-    def __init__(self, global_ref):
-        super().__init__(
-            Array(
-                prefix='\nvoid vertex() {\n\t',
-                seperator='\n\t',
-                suffix='\n}\n'
-            ),
-            global_ref
-        )
-
-
-class ShaderGlobals:
-    """Global space of shader material, maintains uniforms, functions
-    and rendering configures."""
-    def __init__(self):
-        # render mode and render type is also
-        # placed here
-        self.uniform_codes = Array(
-            prefix='',
-            seperator='\n',
-            suffix='\n'
-        )
-
-        # cache function names to avoid duplicated
-        # function code being added
-        self.function_name_set = set()
-        self.function_codes = Array(
-            prefix='',
-            seperator='\n',
-            suffix='\n'
-        )
-
-        self.textures = dict()
-
-        self._render_mode = Array(
-            prefix='render_mode ',
-            seperator=',',
-            suffix=';'
-        )
-
-        self._set_render_mode()
-        self._uniform_var_count = 0
-
-        self.fragment_shader = FragmentShader(self)
-        self.vertex_shader = VertexShader(self)
-
-    def _set_render_mode(self):
-        self._render_mode.extend([
-            'blend_mix',
-            'depth_draw_always',
-            'cull_back',
-            'diffuse_burley',
-            'specular_schlick_ggx',
-        ])
-
-    def add_function(self, function):
-        """Add function body to global codes"""
-        if function.name not in self.function_name_set:
-            self.function_name_set.add(function.name)
-            self.function_codes.append(function.code)
-
-    def define_uniform(self, uni_type, uni_base_name, hint=None):
-        """Define an uniform variable"""
-        self._uniform_var_count += 1
-        raw_var_name = 'uni{}_{}'.format(
-            self._uniform_var_count,
-            uni_base_name,
-        )
-        var_name = var_name = _clear_variable_name(raw_var_name)
-        new_var = Variable(uni_type, var_name)
-        if hint is not None:
-            def_str = 'uniform {} {} : {};'.format(uni_type, var_name, hint)
-        else:
-            def_str = 'uniform {} {};'.format(uni_type, var_name)
-        self.uniform_codes.append(def_str)
-        return new_var
-
-    def add_image_texture(self, uniform_var, image):
-        """Define a uniform referring to the texture sampler
-        and store the image object"""
-        # store image
-        if image is not None:
-            self.textures[image] = uniform_var
-
-    def to_string(self):
-        """Serialization"""
-        return '\n'.join([
-            "shader_type spatial;",  # shader type is spatial for 3D scene
-            self._render_mode.to_string(),
-            self.uniform_codes.to_string(),
-            self.function_codes.to_string(),
-            self.vertex_shader.to_string(),
-            self.fragment_shader.to_string(),
-        ])

+ 175 - 123
tests/reference_exports/material_cycle/material_anistropy.escn

@@ -4,10 +4,15 @@
 
 resource_name = "Shader Nodetree"
 code = "shader_type spatial;
-render_mode blend_mix,depth_draw_always,cull_back,diffuse_burley,specular_schlick_ggx;
+render_mode blend_mix, depth_draw_always, cull_back, diffuse_burley, specular_schlick_ggx;
 
 
 
+void dir_space_convert_world_to_view(inout vec3 dir, in mat4 view_mat) {
+    dir = normalize(view_mat * vec4(dir, 0.0)).xyz;
+}
+
+
 void node_bsdf_principled(vec4 color, float subsurface, vec4 subsurface_color,
         float metallic, float specular, float roughness, float clearcoat,
         float clearcoat_roughness, float anisotropy, float transmission,
@@ -34,68 +39,86 @@ void node_bsdf_principled(vec4 color, float subsurface, vec4 subsurface_color,
 }
 
 
-
-void vertex() {
-	
+void space_convert_zup_to_yup(inout vec3 dir) {
+    dir = mat3(vec3(1, 0, 0), vec3(0, 0, -1), vec3(0, 1, 0)) * dir;
 }
 
+void vertex () {
+}
 
-void fragment() {
-	// node: Tangent
-	// type: ShaderNodeTangent
+void fragment () {
+	
+	// node: 'Tangent'
+	// type: 'ShaderNodeTangent'
+	// input sockets handling
+	// output sockets definitions
+	vec3 node0_out0_tangent;
 	
-	// node: Principled BSDF
-	// type: ShaderNodeBsdfPrincipled
-	vec4 var1_PrincipledBSDF_BaseColor;
-	var1_PrincipledBSDF_BaseColor = vec4(0.800000011920929, 0.800000011920929, 0.800000011920929, 1.0);
-	float var2_PrincipledBSDF_Subsurface;
-	var2_PrincipledBSDF_Subsurface = 0.0;
-	vec4 var3_PrincipledBSDF_SubsurfaceColor;
-	var3_PrincipledBSDF_SubsurfaceColor = vec4(0.699999988079071, 0.10000000149011612, 0.10000000149011612, 1.0);
-	float var4_PrincipledBSDF_Metallic;
-	var4_PrincipledBSDF_Metallic = 1.0;
-	float var5_PrincipledBSDF_Specular;
-	var5_PrincipledBSDF_Specular = 0.5;
-	float var6_PrincipledBSDF_Roughness;
-	var6_PrincipledBSDF_Roughness = 0.12921348214149475;
-	float var7_PrincipledBSDF_Clearcoat;
-	var7_PrincipledBSDF_Clearcoat = 0.0;
-	float var8_PrincipledBSDF_ClearcoatRoughness;
-	var8_PrincipledBSDF_ClearcoatRoughness = 0.029999999329447746;
-	float var9_PrincipledBSDF_Anisotropic;
-	var9_PrincipledBSDF_Anisotropic = 1.0;
-	float var10_PrincipledBSDF_Transmission;
-	var10_PrincipledBSDF_Transmission = 0.0;
-	float var11_PrincipledBSDF_IOR;
-	var11_PrincipledBSDF_IOR = 1.4500000476837158;
-	vec3 var12_out_albedo;
-	float var13_out_sss_strength;
-	float var14_out_metallic;
-	float var15_out_specular;
-	float var16_out_roughness;
-	float var17_out_clearcoat;
-	float var18_out_clearcoat_gloss;
-	float var19_out_anisotropy;
-	float var20_out_transmission;
-	float var21_out_ior;
-	node_bsdf_principled(var1_PrincipledBSDF_BaseColor, var2_PrincipledBSDF_Subsurface, var3_PrincipledBSDF_SubsurfaceColor, var4_PrincipledBSDF_Metallic, var5_PrincipledBSDF_Specular, var6_PrincipledBSDF_Roughness, var7_PrincipledBSDF_Clearcoat, var8_PrincipledBSDF_ClearcoatRoughness, var9_PrincipledBSDF_Anisotropic, var10_PrincipledBSDF_Transmission, var11_PrincipledBSDF_IOR, var12_out_albedo, var13_out_sss_strength, var14_out_metallic, var15_out_specular, var16_out_roughness, var17_out_clearcoat, var18_out_clearcoat_gloss, var19_out_anisotropy, var20_out_transmission, var21_out_ior);
-	// convert from z-up to y-up
-	TANGENT = mat3(vec3(1, 0, 0), vec3(0, 0, -1), vec3(0, 1, 0)) * TANGENT;
-	// convert from world space to view space
-	TANGENT = normalize(INV_CAMERA_MATRIX * vec4(TANGENT, 0.0)).xyz;
+	node0_out0_tangent = TANGENT;
 	
-	ALBEDO = var12_out_albedo;
-	SSS_STRENGTH = var13_out_sss_strength;
-	SPECULAR = var15_out_specular;
-	METALLIC = var14_out_metallic;
-	ROUGHNESS = var16_out_roughness;
-	CLEARCOAT = var17_out_clearcoat;
-	CLEARCOAT_GLOSS = var18_out_clearcoat_gloss;
-	// uncomment it only when you set diffuse mode to oren nayar
-	// ROUGHNESS = oren_nayar_rougness
-	ANISOTROPY = var19_out_anisotropy;
-	TANGENT = normalize(cross(cross(TANGENT, NORMAL), NORMAL));
+	
+	// node: 'Principled BSDF'
+	// type: 'ShaderNodeBsdfPrincipled'
+	// input sockets handling
+	vec4 node1_in0_basecolor = vec4(0.800000011920929, 0.800000011920929,
+		0.800000011920929, 1.0);
+	float node1_in1_subsurface = float(0.0);
+	vec3 node1_in2_subsurfaceradius = vec3(1.0, 1.0, 1.0);
+	vec4 node1_in3_subsurfacecolor = vec4(0.699999988079071, 0.10000000149011612,
+		0.10000000149011612, 1.0);
+	float node1_in4_metallic = float(1.0);
+	float node1_in5_specular = float(0.5);
+	float node1_in6_speculartint = float(0.0);
+	float node1_in7_roughness = float(0.12921348214149475);
+	float node1_in8_anisotropic = float(1.0);
+	float node1_in9_anisotropicrotation = float(0.0);
+	float node1_in10_sheen = float(0.0);
+	float node1_in11_sheentint = float(0.5);
+	float node1_in12_clearcoat = float(0.0);
+	float node1_in13_clearcoatroughness = float(0.029999999329447746);
+	float node1_in14_ior = float(1.4500000476837158);
+	float node1_in15_transmission = float(0.0);
+	float node1_in16_transmissionroughness = float(0.0);
+	vec3 node1_in17_normal = NORMAL;
+	vec3 node1_in18_clearcoatnormal = vec3(0.0, 0.0, 0.0);
+	vec3 node1_in19_tangent = node0_out0_tangent;
+	// output sockets definitions
+	vec3 node1_bsdf_out0_albedo;
+	float node1_bsdf_out1_sss_strength;
+	float node1_bsdf_out3_specular;
+	float node1_bsdf_out2_metallic;
+	float node1_bsdf_out4_roughness;
+	float node1_bsdf_out5_clearcoat;
+	float node1_bsdf_out6_clearcoat_gloss;
+	float node1_bsdf_out7_anisotropy;
+	float node1_bsdf_out8_transmission;
+	float node1_bsdf_out9_ior;
+	
+	node_bsdf_principled(node1_in0_basecolor, node1_in1_subsurface,
+		node1_in3_subsurfacecolor, node1_in4_metallic, node1_in5_specular,
+		node1_in7_roughness, node1_in12_clearcoat, node1_in13_clearcoatroughness,
+		node1_in8_anisotropic, node1_in15_transmission, node1_in14_ior,
+		node1_bsdf_out0_albedo, node1_bsdf_out1_sss_strength, node1_bsdf_out2_metallic,
+		node1_bsdf_out3_specular, node1_bsdf_out4_roughness, node1_bsdf_out5_clearcoat,
+		node1_bsdf_out6_clearcoat_gloss, node1_bsdf_out7_anisotropy,
+		node1_bsdf_out8_transmission, node1_bsdf_out9_ior);
+	space_convert_zup_to_yup(node1_in19_tangent);
+	dir_space_convert_world_to_view(node1_in19_tangent, INV_CAMERA_MATRIX);
+	
+	
+	ALBEDO = node1_bsdf_out0_albedo;
+	SSS_STRENGTH = node1_bsdf_out1_sss_strength;
+	SPECULAR = node1_bsdf_out3_specular;
+	METALLIC = node1_bsdf_out2_metallic;
+	ROUGHNESS = node1_bsdf_out4_roughness;
+	CLEARCOAT = node1_bsdf_out5_clearcoat;
+	CLEARCOAT_GLOSS = node1_bsdf_out6_clearcoat_gloss;
+	NORMAL = node1_in17_normal;
+	// uncomment it when you need it
+	// TRANSMISSION = vec3(1.0, 1.0, 1.0) * node1_bsdf_out8_transmission;
+	TANGENT = normalize(cross(cross(node1_in19_tangent, NORMAL), NORMAL));
 	BINORMAL = cross(TANGENT, NORMAL);
+	ANISOTROPY = node1_bsdf_out7_anisotropy;
 }
 "
 
@@ -128,8 +151,13 @@ surfaces/0 = {
 
 resource_name = "Shader Nodetree"
 code = "shader_type spatial;
-render_mode blend_mix,depth_draw_always,cull_back,diffuse_burley,specular_schlick_ggx;
+render_mode blend_mix, depth_draw_always, cull_back, diffuse_burley, specular_schlick_ggx;
+
+
 
+void dir_space_convert_world_to_view(inout vec3 dir, in mat4 view_mat) {
+    dir = normalize(view_mat * vec4(dir, 0.0)).xyz;
+}
 
 
 void node_bsdf_principled(vec4 color, float subsurface, vec4 subsurface_color,
@@ -158,78 +186,102 @@ void node_bsdf_principled(vec4 color, float subsurface, vec4 subsurface_color,
 }
 
 
+void point_space_convert_view_to_model(inout vec3 pos,
+        in mat4 inv_model_mat, in mat4 inv_view_mat) {
+    pos = (inv_model_mat * (inv_view_mat * vec4(pos, 1.0))).xyz;
+}
+
 
-void vertex() {
-	
+void space_convert_yup_to_zup(inout vec3 dir) {
+    dir = mat3(vec3(1, 0, 0), vec3(0, 0, 1), vec3(0, -1, 0)) * dir;
 }
 
 
-void fragment() {
-	mat4 inverted_view_matrix;
-	inverted_view_matrix = inverse(INV_CAMERA_MATRIX);
-	mat4 inverted_model_matrix;
-	inverted_model_matrix = inverse(WORLD_MATRIX);
-	// node: Texture Coordinate
-	// type: ShaderNodeTexCoord
-	vec3 var1_TextureCoordinate_Object;
-	var1_TextureCoordinate_Object = VERTEX;
-	// convert from view space to model space
-	var1_TextureCoordinate_Object = (inverted_model_matrix * (inverted_view_matrix * vec4(var1_TextureCoordinate_Object, 1.0))).xyz;
-	// convert from y-up to z-up
-	var1_TextureCoordinate_Object = mat3(vec3(1, 0, 0), vec3(0, 0, 1), vec3(0, -1, 0)) * var1_TextureCoordinate_Object;
+void space_convert_zup_to_yup(inout vec3 dir) {
+    dir = mat3(vec3(1, 0, 0), vec3(0, 0, -1), vec3(0, 1, 0)) * dir;
+}
+
+void vertex () {
+}
+
+void fragment () {
+	mat4 INV_MODEL_MAT = inverse(WORLD_MATRIX);
+	mat4 INV_VIEW_MAT = inverse(INV_CAMERA_MATRIX);
+	
+	// node: 'Texture Coordinate'
+	// type: 'ShaderNodeTexCoord'
+	// input sockets handling
+	// output sockets definitions
+	vec3 node0_out0_object;
+	
+	node0_out0_object = VERTEX;
+	point_space_convert_view_to_model(node0_out0_object, INV_MODEL_MAT,
+		INV_VIEW_MAT);
+	space_convert_yup_to_zup(node0_out0_object);
+	
+	
+	// node: 'Principled BSDF'
+	// type: 'ShaderNodeBsdfPrincipled'
+	// input sockets handling
+	vec4 node1_in0_basecolor = vec4(0.800000011920929, 0.800000011920929,
+		0.800000011920929, 1.0);
+	float node1_in1_subsurface = float(0.0);
+	vec3 node1_in2_subsurfaceradius = vec3(1.0, 1.0, 1.0);
+	vec4 node1_in3_subsurfacecolor = vec4(0.699999988079071, 0.10000000149011612,
+		0.10000000149011612, 1.0);
+	float node1_in4_metallic = float(1.0);
+	float node1_in5_specular = float(0.5);
+	float node1_in6_speculartint = float(0.0);
+	float node1_in7_roughness = float(0.12921348214149475);
+	float node1_in8_anisotropic = float(1.0);
+	float node1_in9_anisotropicrotation = float(0.0);
+	float node1_in10_sheen = float(0.0);
+	float node1_in11_sheentint = float(0.5);
+	float node1_in12_clearcoat = float(0.0);
+	float node1_in13_clearcoatroughness = float(0.029999999329447746);
+	float node1_in14_ior = float(1.4500000476837158);
+	float node1_in15_transmission = float(0.0);
+	float node1_in16_transmissionroughness = float(0.0);
+	vec3 node1_in17_normal = NORMAL;
+	vec3 node1_in18_clearcoatnormal = vec3(0.0, 0.0, 0.0);
+	vec3 node1_in19_tangent = node0_out0_object;
+	// output sockets definitions
+	vec3 node1_bsdf_out0_albedo;
+	float node1_bsdf_out1_sss_strength;
+	float node1_bsdf_out3_specular;
+	float node1_bsdf_out2_metallic;
+	float node1_bsdf_out4_roughness;
+	float node1_bsdf_out5_clearcoat;
+	float node1_bsdf_out6_clearcoat_gloss;
+	float node1_bsdf_out7_anisotropy;
+	float node1_bsdf_out8_transmission;
+	float node1_bsdf_out9_ior;
+	
+	node_bsdf_principled(node1_in0_basecolor, node1_in1_subsurface,
+		node1_in3_subsurfacecolor, node1_in4_metallic, node1_in5_specular,
+		node1_in7_roughness, node1_in12_clearcoat, node1_in13_clearcoatroughness,
+		node1_in8_anisotropic, node1_in15_transmission, node1_in14_ior,
+		node1_bsdf_out0_albedo, node1_bsdf_out1_sss_strength, node1_bsdf_out2_metallic,
+		node1_bsdf_out3_specular, node1_bsdf_out4_roughness, node1_bsdf_out5_clearcoat,
+		node1_bsdf_out6_clearcoat_gloss, node1_bsdf_out7_anisotropy,
+		node1_bsdf_out8_transmission, node1_bsdf_out9_ior);
+	space_convert_zup_to_yup(node1_in19_tangent);
+	dir_space_convert_world_to_view(node1_in19_tangent, INV_CAMERA_MATRIX);
 	
-	// node: Principled BSDF
-	// type: ShaderNodeBsdfPrincipled
-	vec4 var2_PrincipledBSDF_BaseColor;
-	var2_PrincipledBSDF_BaseColor = vec4(0.800000011920929, 0.800000011920929, 0.800000011920929, 1.0);
-	float var3_PrincipledBSDF_Subsurface;
-	var3_PrincipledBSDF_Subsurface = 0.0;
-	vec4 var4_PrincipledBSDF_SubsurfaceColor;
-	var4_PrincipledBSDF_SubsurfaceColor = vec4(0.699999988079071, 0.10000000149011612, 0.10000000149011612, 1.0);
-	float var5_PrincipledBSDF_Metallic;
-	var5_PrincipledBSDF_Metallic = 1.0;
-	float var6_PrincipledBSDF_Specular;
-	var6_PrincipledBSDF_Specular = 0.5;
-	float var7_PrincipledBSDF_Roughness;
-	var7_PrincipledBSDF_Roughness = 0.12921348214149475;
-	float var8_PrincipledBSDF_Clearcoat;
-	var8_PrincipledBSDF_Clearcoat = 0.0;
-	float var9_PrincipledBSDF_ClearcoatRoughness;
-	var9_PrincipledBSDF_ClearcoatRoughness = 0.029999999329447746;
-	float var10_PrincipledBSDF_Anisotropic;
-	var10_PrincipledBSDF_Anisotropic = 1.0;
-	float var11_PrincipledBSDF_Transmission;
-	var11_PrincipledBSDF_Transmission = 0.0;
-	float var12_PrincipledBSDF_IOR;
-	var12_PrincipledBSDF_IOR = 1.4500000476837158;
-	vec3 var13_out_albedo;
-	float var14_out_sss_strength;
-	float var15_out_metallic;
-	float var16_out_specular;
-	float var17_out_roughness;
-	float var18_out_clearcoat;
-	float var19_out_clearcoat_gloss;
-	float var20_out_anisotropy;
-	float var21_out_transmission;
-	float var22_out_ior;
-	node_bsdf_principled(var2_PrincipledBSDF_BaseColor, var3_PrincipledBSDF_Subsurface, var4_PrincipledBSDF_SubsurfaceColor, var5_PrincipledBSDF_Metallic, var6_PrincipledBSDF_Specular, var7_PrincipledBSDF_Roughness, var8_PrincipledBSDF_Clearcoat, var9_PrincipledBSDF_ClearcoatRoughness, var10_PrincipledBSDF_Anisotropic, var11_PrincipledBSDF_Transmission, var12_PrincipledBSDF_IOR, var13_out_albedo, var14_out_sss_strength, var15_out_metallic, var16_out_specular, var17_out_roughness, var18_out_clearcoat, var19_out_clearcoat_gloss, var20_out_anisotropy, var21_out_transmission, var22_out_ior);
-	// convert from z-up to y-up
-	var1_TextureCoordinate_Object = mat3(vec3(1, 0, 0), vec3(0, 0, -1), vec3(0, 1, 0)) * var1_TextureCoordinate_Object;
-	// convert from world space to view space
-	var1_TextureCoordinate_Object = normalize(INV_CAMERA_MATRIX * vec4(var1_TextureCoordinate_Object, 0.0)).xyz;
 	
-	ALBEDO = var13_out_albedo;
-	SSS_STRENGTH = var14_out_sss_strength;
-	SPECULAR = var16_out_specular;
-	METALLIC = var15_out_metallic;
-	ROUGHNESS = var17_out_roughness;
-	CLEARCOAT = var18_out_clearcoat;
-	CLEARCOAT_GLOSS = var19_out_clearcoat_gloss;
-	// uncomment it only when you set diffuse mode to oren nayar
-	// ROUGHNESS = oren_nayar_rougness
-	ANISOTROPY = var20_out_anisotropy;
-	TANGENT = normalize(cross(cross(var1_TextureCoordinate_Object, NORMAL), NORMAL));
+	ALBEDO = node1_bsdf_out0_albedo;
+	SSS_STRENGTH = node1_bsdf_out1_sss_strength;
+	SPECULAR = node1_bsdf_out3_specular;
+	METALLIC = node1_bsdf_out2_metallic;
+	ROUGHNESS = node1_bsdf_out4_roughness;
+	CLEARCOAT = node1_bsdf_out5_clearcoat;
+	CLEARCOAT_GLOSS = node1_bsdf_out6_clearcoat_gloss;
+	NORMAL = node1_in17_normal;
+	// uncomment it when you need it
+	// TRANSMISSION = vec3(1.0, 1.0, 1.0) * node1_bsdf_out8_transmission;
+	TANGENT = normalize(cross(cross(node1_in19_tangent, NORMAL), NORMAL));
 	BINORMAL = cross(TANGENT, NORMAL);
+	ANISOTROPY = node1_bsdf_out7_anisotropy;
 }
 "
 

文件差異過大導致無法顯示
+ 486 - 330
tests/reference_exports/material_cycle/material_cycle.escn


文件差異過大導致無法顯示
+ 474 - 333
tests/reference_exports/material_cycle/material_normal.escn


+ 48 - 33
tests/reference_exports/material_cycle/material_unpack_texture.escn

@@ -5,54 +5,69 @@
 
 resource_name = "Shader Nodetree"
 code = "shader_type spatial;
-render_mode blend_mix,depth_draw_always,cull_back,diffuse_burley,specular_schlick_ggx;
-uniform sampler2D uni1_brick_4_diff_1kjpg;
+render_mode blend_mix, depth_draw_always, cull_back, diffuse_burley, specular_schlick_ggx;
 
-
-void node_tex_image(vec3 co, sampler2D ima, out vec4 color, out float alpha) {
-    color = texture(ima, co.xy);
-    alpha = color.a;
-}
+uniform sampler2D texture_0;
 
 
 void node_bsdf_diffuse(vec4 color, float roughness, out vec3 albedo,
-        out float specular_out, out float roughness_out) {
+        out float specular_out, out float oren_nayar_roughness_out) {
     albedo = color.rgb;
     specular_out = 0.5;
-    roughness_out = 1.0;
+    oren_nayar_roughness_out = roughness;
 }
 
 
-
-void vertex() {
-	
+void node_tex_image(vec3 co, sampler2D ima, out vec4 color, out float alpha) {
+    color = texture(ima, co.xy);
+    alpha = color.a;
 }
 
+void vertex () {
+}
 
-void fragment() {
-	// node: Texture Coordinate
-	// type: ShaderNodeTexCoord
+void fragment () {
+	
+	// node: 'Texture Coordinate'
+	// type: 'ShaderNodeTexCoord'
+	// input sockets handling
+	// output sockets definitions
+	vec3 node0_out0_uv;
+	
+	node0_out0_uv = vec3(UV, 0.0);
+	
+	
+	// node: 'Image Texture'
+	// type: 'ShaderNodeTexImage'
+	// input sockets handling
+	vec3 node1_in0_vector = node0_out0_uv;
+	// output sockets definitions
+	vec4 node1_out0_color;
+	float node1_out1_alpha;
+	
+	node_tex_image(node1_in0_vector, texture_0, node1_out0_color, node1_out1_alpha);
+	
+	
+	// node: 'Diffuse BSDF'
+	// type: 'ShaderNodeBsdfDiffuse'
+	// input sockets handling
+	vec4 node2_in0_color = node1_out0_color;
+	float node2_in1_roughness = float(0.0);
+	vec3 node2_in2_normal = NORMAL;
+	// output sockets definitions
+	vec3 node2_bsdf_out0_albedo;
+	float node2_bsdf_out1_specular;
+	float node2_bsdf_out2_oren_nayar_roughness;
 	
-	// node: Image Texture
-	// type: ShaderNodeTexImage
-	// texture image from node Image Texture
-	vec4 var1_ImageTexture_Color;
-	float var2_ImageTexture_Alpha;
-	node_tex_image(vec3(UV, 0.0), uni1_brick_4_diff_1kjpg, var1_ImageTexture_Color, var2_ImageTexture_Alpha);
+	node_bsdf_diffuse(node2_in0_color, node2_in1_roughness, node2_bsdf_out0_albedo,
+		node2_bsdf_out1_specular, node2_bsdf_out2_oren_nayar_roughness);
 	
-	// node: Diffuse BSDF
-	// type: ShaderNodeBsdfDiffuse
-	float var3_DiffuseBSDF_Roughness;
-	var3_DiffuseBSDF_Roughness = 0.0;
-	vec3 var4_out_albedo;
-	float var5_out_specular;
-	float var6_out_oren_nayar_roughness;
-	node_bsdf_diffuse(var1_ImageTexture_Color, var3_DiffuseBSDF_Roughness, var4_out_albedo, var5_out_specular, var6_out_oren_nayar_roughness);
 	
-	ALBEDO = var4_out_albedo;
-	SPECULAR = var5_out_specular;
+	ALBEDO = node2_bsdf_out0_albedo;
+	SPECULAR = node2_bsdf_out1_specular;
+	NORMAL = node2_in2_normal;
 	// uncomment it only when you set diffuse mode to oren nayar
-	// ROUGHNESS = oren_nayar_rougness
+	// ROUGHNESS = node2_bsdf_out2_oren_nayar_roughness;
 }
 "
 
@@ -60,7 +75,7 @@ void fragment() {
 
 resource_name = ""
 shader = SubResource(1)
-shader_param/uni1_brick_4_diff_1kjpg = ExtResource(1)
+shader_param/texture_0 = ExtResource(1)
 
 [sub_resource id=3 type="ArrayMesh"]
 

部分文件因文件數量過多而無法顯示