Przeglądaj źródła

rewrite script shader generate code

Jason0214 6 lat temu
rodzic
commit
1900a09c36

+ 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 logging
 import os
 import os
 import bpy
 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)
     InternalResource, ExternalResource, gamma_correct, ValidationError)
 
 
 
 
@@ -75,7 +75,7 @@ def generate_material_resource(escn_file, export_settings, material):
             material.node_tree is not None):
             material.node_tree is not None):
         mat = InternalResource("ShaderMaterial", material_rsc_name)
         mat = InternalResource("ShaderMaterial", material_rsc_name)
         try:
         try:
-            export_node_tree(
+            export_script_shader(
                 escn_file, export_settings, material, mat
                 escn_file, export_settings, material, mat
             )
             )
         except ValidationError as exception:
         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
 """Module for export Blender CYCLES and EEVEE material node tree
 to Godot ShaderMaterial"""
 to Godot ShaderMaterial"""
-from .exporters import export_node_tree
+from .node_tree import export_script_shader

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

@@ -0,0 +1,884 @@
+"""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):
+        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, texture_name):
+        """generate a temp variable for texture, later it would be replaced
+        by uniform var"""
+        var_prefix = "tex%s_" % hash(texture_name)
+        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:
+                # if one shader input has alpha, the other one should
+                # default to have alpha = 1.0
+                if prop_a and not prop_b:
+                    prop_b = '1.0'
+                elif prop_b and not prop_a:
+                    prop_a = '1.0'
+
+            mix_result_id = self.generate_variable_id_str('bsdf_' + pname)
+            if prop_a and prop_b:
+                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:
+                output.set_property(pname, prop_a)
+            elif prop_b:
+                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
+            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', 'ShaderNodeBsdfPrincipled'):
+            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 = self.in_sockets_map[self.bl_node.inputs[0]]
+
+        if self.bl_node.image is not None:
+            tex_var = self.generate_tmp_texture_id(self.bl_node.image.name)
+            is_normal = is_normal_texture(self.bl_node)
+            self.textures.append(
+                Texture(self.bl_node.image, tex_var, is_normal)
+            )
+        else:
+            # it causes parsing error, so user can see it
+            tex_var = "NO_TEXTURE"
+            logging.error(
+                "No image selected for '%s', at'%s'",
+                self.bl_node.bl_idname, self.bl_node.name
+            )
+
+        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[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)

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

@@ -0,0 +1,387 @@
+"""Interface for material node tree exporter"""
+import os
+import logging
+import textwrap
+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
+
+        # 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:
+            # define an unifrom var
+            refraction_offset = 'refraction_offset'
+            self._uniform_code_lines.append(
+                'uniform vec2 %s = vec2(0.2, 0.2)' % refraction_offset
+            )
+            if ior is not None and self.flags.glass:
+                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)
+                )
+            self._fragment_code_lines.append(
+                "EMISSION += textureLod(SCREEN_TEXTURE, SCREEN_UV - "
+                "NORMAL.xy * %s , ROUGHNESS).rgb * (1.0 - %s)" %
+                (refraction_offset, 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):
+            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):
+            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"""
+        return {tex.image for tex in self._textures}
+
+    def get_image_texture_info(self):
+        """return a list of tuple (image, texture uniform)"""
+        return [(tex.image, uniform)
+                for tex, uniform in self._textures.items()]
+
+
+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 breadth_first_search(begin_socket):
+    """bfs the node tree from the given socket"""
+    sorted_node_list = list()
+
+    link_queue = list()
+    if begin_socket.is_linked:
+        link_queue.append(begin_socket.links[0])
+    while link_queue:
+        cur_link = link_queue.pop(0)
+        if not cur_link.is_valid:
+            continue
+
+        cur_node = cur_link.from_node
+        sorted_node_list.append(cur_node)
+
+        for in_sock in cur_node.inputs:
+            if in_sock.is_linked:
+                for link in in_sock.links:
+                    link_queue.append(link)
+
+    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
+        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)
+    surface_output_socket = mtl_output_node.inputs['Surface']
+    if surface_output_socket.is_linked:
+        frag_node_list = breadth_first_search(surface_output_socket)
+
+        node_to_converter_map = dict()
+        for idx, node in enumerate(reversed(frag_node_list)):
+            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_in_socket = surface_output_socket.links[0].from_socket
+        root_converter = node_to_converter_map[frag_node_list[0]]
+        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

+ 118 - 42
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,
 """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
 import re
-from ...structures import ValidationError
+from .shader_links import FragmentShaderLink
+from ....structures import ValidationError
 
 
 FUNCTION_HEAD_PATTERN = re.compile(
 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*,?)*)\)'),
      r'(vec2|vec3|vec4|float|mat4|sampler2D)\s+[a-zA-Z]\w*\s*,?)*)\)'),
 )
 )
 
 
 
 
 class ShaderFunction:
 class ShaderFunction:
     """Shader function for a blender node"""
     """Shader function for a blender node"""
+
     def __init__(self, code):
     def __init__(self, code):
         # at most one group
         # at most one group
         self.code = code
         self.code = code
@@ -26,18 +28,22 @@ class ShaderFunction:
             tokens = tuple([x.strip() for x in param_str.split()])
             tokens = tuple([x.strip() for x in param_str.split()])
             if tokens[0] == 'out':
             if tokens[0] == 'out':
                 self.out_param_types.append(tokens[1])
                 self.out_param_types.append(tokens[1])
-            else:
+            else:  # 'in', 'inout'
                 self.in_param_types.append(tokens[0])
                 self.in_param_types.append(tokens[0])
 
 
+    def __hash__(self):
+        return hash(self.name)
+
 
 
 class BsdfShaderFunction(ShaderFunction):
 class BsdfShaderFunction(ShaderFunction):
     """Function for bsdf shader node, has additional information of
     """Function for bsdf shader node, has additional information of
     input and output socket"""
     input and output socket"""
-    def __init__(self, code, input_sockets, output_sockets):
+
+    def __init__(self, code, input_sockets, output_properties):
         super().__init__(code)
         super().__init__(code)
         # linked socket ids of material node
         # linked socket ids of material node
         self.in_sockets = tuple(input_sockets)
         self.in_sockets = tuple(input_sockets)
-        self.out_sockets = tuple(output_sockets)
+        self.output_properties = tuple(output_properties)
 
 
 
 
 # Shader function nameing convention:
 # Shader function nameing convention:
@@ -105,55 +111,55 @@ void node_bsdf_principled(vec4 color, float subsurface, vec4 subsurface_color,
             "Transmission",
             "Transmission",
             "IOR",
             "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(
     BsdfShaderFunction(
         code="""
         code="""
 void node_emission(vec4 emission_color, float strength,
 void node_emission(vec4 emission_color, float strength,
-        out vec3 emission_out){
+        out vec3 emission_out) {
     emission_out = emission_color.rgb * strength;
     emission_out = emission_color.rgb * strength;
 }
 }
 """,
 """,
         input_sockets=["Color", "Strength"],
         input_sockets=["Color", "Strength"],
-        output_sockets=["emission"]
+        output_properties=[FragmentShaderLink.EMISSION]
     ),
     ),
 
 
     BsdfShaderFunction(
     BsdfShaderFunction(
         code="""
         code="""
 void node_bsdf_diffuse(vec4 color, float roughness, out vec3 albedo,
 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;
     albedo = color.rgb;
     specular_out = 0.5;
     specular_out = 0.5;
-    roughness_out = 1.0;
+    oren_nayar_roughness_out = roughness;
 }
 }
 """,
 """,
         input_sockets=[
         input_sockets=[
             "Color",
             "Color",
             "Roughness",
             "Roughness",
         ],
         ],
-        output_sockets=[
-            "albedo",
-            "specular",
-            "oren_nayar_roughness",
+        output_properties=[
+            FragmentShaderLink.ALBEDO,
+            FragmentShaderLink.SPECULAR,
+            FragmentShaderLink.OREN_NAYAR_ROUGHNESS,
         ]
         ]
     ),
     ),
 
 
     BsdfShaderFunction(
     BsdfShaderFunction(
         code="""
         code="""
 void node_bsdf_glossy(vec4 color, float roughness, out vec3 albedo,
 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;
     albedo = color.rgb;
     roughness_out = roughness;
     roughness_out = roughness;
     metallic_out = 1.0;
     metallic_out = 1.0;
@@ -163,10 +169,10 @@ void node_bsdf_glossy(vec4 color, float roughness, out vec3 albedo,
             "Color",
             "Color",
             "Roughness",
             "Roughness",
         ],
         ],
-        output_sockets=[
-            "albedo",
-            "metallic",
-            "roughness",
+        output_properties=[
+            FragmentShaderLink.ALBEDO,
+            FragmentShaderLink.METALLIC,
+            FragmentShaderLink.ROUGHNESS,
         ]
         ]
     ),
     ),
 
 
@@ -177,7 +183,7 @@ void node_bsdf_transparent(vec4 color, out float alpha) {
 }
 }
 """,
 """,
         input_sockets=['Color'],
         input_sockets=['Color'],
-        output_sockets=['alpha'],
+        output_properties=[FragmentShaderLink.ALPHA],
     ),
     ),
 
 
     BsdfShaderFunction(
     BsdfShaderFunction(
@@ -198,19 +204,19 @@ void node_bsdf_glass(vec4 color, float roughness, float IOR, out vec3 albedo,
             "Roughness",
             "Roughness",
             "IOR",
             "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
     # trivial converter node functions
     ShaderFunction(code="""
     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;
     result = color.r * 0.2126 + color.g * 0.7152 + color.b * 0.0722;
 }
 }
 """),
 """),
@@ -239,7 +245,7 @@ void node_combine_rgb(float r, float g, float b, out vec4 color) {
 
 
     ShaderFunction(code="""
     ShaderFunction(code="""
 void node_bump(float strength, float dist, float height, vec3 normal,
 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) {
     if (invert != 0.0) {
         dist *= -1.0;
         dist *= -1.0;
     }
     }
@@ -492,7 +498,7 @@ void node_math_power_clamp(float val1, float val2, out float outval) {
 """),
 """),
 
 
     ShaderFunction(code="""
     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)
     if (val1 > 0.0  && val2 > 0.0)
         outval = clamp(log2(val1) / log2(val2), 0.0, 1.0);
         outval = clamp(log2(val1) / log2(val2), 0.0, 1.0);
     else
     else
@@ -550,6 +556,70 @@ void node_vector_math_normalize(vec3 v, out vec3 outvec, out float outval) {
 """),
 """),
 
 
     # non-node function:
     # 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="""
     ShaderFunction(code="""
 void refraction_fresnel(vec3 view_dir, vec3 normal, float ior, out float kr) {
 void refraction_fresnel(vec3 view_dir, vec3 normal, float ior, out float kr) {
 // reference [https://www.scratchapixel.com/lessons/
 // reference [https://www.scratchapixel.com/lessons/
@@ -614,6 +684,12 @@ def convert_node_to_function_name(node):
     return function_name_base
     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):
 def find_node_function(node):
     """Given a material node, return its corresponding function"""
     """Given a material node, return its corresponding function"""
     function_name = convert_node_to_function_name(node)
     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 - 125
io_scene_godot/converters/material_node_tree/exporters.py

@@ -1,125 +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
-        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 - 556
io_scene_godot/converters/material_node_tree/node_vistors.py

@@ -1,556 +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"""
-    rgb_socket = node.outputs[0]
-    var = shader.define_variable_from_socket(node, rgb_socket)
-    shader.append_assignment_code(
-        var, Value.create_from_blender_value(rgb_socket.default_value))
-    shader.assign_variable_to_socket(rgb_socket, var)
-
-
-def visit_value_node(shader, node):
-    """Visit ShaderNodeValue"""
-    value_socket = node.outputs['Value']
-    var = shader.define_variable_from_socket(node, value_socket)
-    shader.append_assignment_code(
-        var, Value.create_from_blender_value(value_socket.default_value))
-    shader.assign_variable_to_socket(value_socket, var)
-
-
-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"""
-    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,
-    'ShaderNodeValue': visit_value_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 - 626
io_scene_godot/converters/material_node_tree/shaders.py

@@ -1,626 +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 = _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,
-            socket.identifier
-        )
-        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', 'converted_' + str(src_variable)
-            )
-            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 = _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"
 resource_name = "Shader Nodetree"
 code = "shader_type spatial;
 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,
 void node_bsdf_principled(vec4 color, float subsurface, vec4 subsurface_color,
         float metallic, float specular, float roughness, float clearcoat,
         float metallic, float specular, float roughness, float clearcoat,
         float clearcoat_roughness, float anisotropy, float transmission,
         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_BaseColor;
-	var1_BaseColor = vec4(0.800000011920929, 0.800000011920929, 0.800000011920929, 1.0);
-	float var2_Subsurface;
-	var2_Subsurface = 0.0;
-	vec4 var3_SubsurfaceColor;
-	var3_SubsurfaceColor = vec4(0.699999988079071, 0.10000000149011612, 0.10000000149011612, 1.0);
-	float var4_Metallic;
-	var4_Metallic = 1.0;
-	float var5_Specular;
-	var5_Specular = 0.5;
-	float var6_Roughness;
-	var6_Roughness = 0.12921348214149475;
-	float var7_Clearcoat;
-	var7_Clearcoat = 0.0;
-	float var8_ClearcoatRoughness;
-	var8_ClearcoatRoughness = 0.029999999329447746;
-	float var9_Anisotropic;
-	var9_Anisotropic = 1.0;
-	float var10_Transmission;
-	var10_Transmission = 0.0;
-	float var11_IOR;
-	var11_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_BaseColor, var2_Subsurface, var3_SubsurfaceColor, var4_Metallic, var5_Specular, var6_Roughness, var7_Clearcoat, var8_ClearcoatRoughness, var9_Anisotropic, var10_Transmission, var11_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);
 	BINORMAL = cross(TANGENT, NORMAL);
+	ANISOTROPY = node1_bsdf_out7_anisotropy;
 }
 }
 "
 "
 
 
@@ -128,8 +151,13 @@ surfaces/0 = {
 
 
 resource_name = "Shader Nodetree"
 resource_name = "Shader Nodetree"
 code = "shader_type spatial;
 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,
 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_Object;
-	var1_Object = VERTEX;
-	// convert from view space to model space
-	var1_Object = (inverted_model_matrix * (inverted_view_matrix * vec4(var1_Object, 1.0))).xyz;
-	// convert from y-up to z-up
-	var1_Object = mat3(vec3(1, 0, 0), vec3(0, 0, 1), vec3(0, -1, 0)) * var1_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_BaseColor;
-	var2_BaseColor = vec4(0.800000011920929, 0.800000011920929, 0.800000011920929, 1.0);
-	float var3_Subsurface;
-	var3_Subsurface = 0.0;
-	vec4 var4_SubsurfaceColor;
-	var4_SubsurfaceColor = vec4(0.699999988079071, 0.10000000149011612, 0.10000000149011612, 1.0);
-	float var5_Metallic;
-	var5_Metallic = 1.0;
-	float var6_Specular;
-	var6_Specular = 0.5;
-	float var7_Roughness;
-	var7_Roughness = 0.12921348214149475;
-	float var8_Clearcoat;
-	var8_Clearcoat = 0.0;
-	float var9_ClearcoatRoughness;
-	var9_ClearcoatRoughness = 0.029999999329447746;
-	float var10_Anisotropic;
-	var10_Anisotropic = 1.0;
-	float var11_Transmission;
-	var11_Transmission = 0.0;
-	float var12_IOR;
-	var12_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_BaseColor, var3_Subsurface, var4_SubsurfaceColor, var5_Metallic, var6_Specular, var7_Roughness, var8_Clearcoat, var9_ClearcoatRoughness, var10_Anisotropic, var11_Transmission, var12_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_Object = mat3(vec3(1, 0, 0), vec3(0, 0, -1), vec3(0, 1, 0)) * var1_Object;
-	// convert from world space to view space
-	var1_Object = normalize(INV_CAMERA_MATRIX * vec4(var1_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_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);
 	BINORMAL = cross(TANGENT, NORMAL);
+	ANISOTROPY = node1_bsdf_out7_anisotropy;
 }
 }
 "
 "
 
 

Plik diff jest za duży
+ 461 - 333
tests/reference_exports/material_cycle/material_cycle.escn


Plik diff jest za duży
+ 552 - 323
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"
 resource_name = "Shader Nodetree"
 code = "shader_type spatial;
 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,
 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;
     albedo = color.rgb;
     specular_out = 0.5;
     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_Color;
-	float var2_Alpha;
-	node_tex_image(vec3(UV, 0.0), uni1_brick_4_diff_1kjpg, var1_Color, var2_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_Roughness;
-	var3_Roughness = 0.0;
-	vec3 var4_out_albedo;
-	float var5_out_specular;
-	float var6_out_oren_nayar_roughness;
-	node_bsdf_diffuse(var1_Color, var3_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
 	// 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 = ""
 resource_name = ""
 shader = SubResource(1)
 shader = SubResource(1)
-shader_param/uni1_brick_4_diff_1kjpg = ExtResource(1)
+shader_param/texture_0 = ExtResource(1)
 
 
 [sub_resource id=3 type="ArrayMesh"]
 [sub_resource id=3 type="ArrayMesh"]
 
 

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików