Эх сурвалжийг харах

several improvements in script shader generating

1. change transparent object's default refraction offset to (0.0, 0.0)
2. successfuly generating shader script even if no texture image
provided
3. if image texture node has no coordinate input, default to UV
4. remove glass effection in transparent principled BSDF
5. set a assertion guard in defining output sockets
6. correct emission and transparent bsdf output
7. more safe property check in mix shader
8. fix possible SameFileError in texture exporting
Jason0214 6 жил өмнө
parent
commit
abfed45b82

+ 27 - 22
io_scene_godot/converters/material/script_shader/node_converters.py

@@ -78,6 +78,7 @@ class Texture:
     """A texture"""
 
     def __init__(self, bl_image, identifier, hint_normal):
+        # note that image could be None, it need to be safely handled
         self.image = bl_image
         self.hint_normal = hint_normal
         # identifier is the variable name in scripts
@@ -173,10 +174,10 @@ class NodeConverterBase:
         self.variable_count += 1
         return self._id_prefix + var_prefix + filter_id_illegal_char(hint)
 
-    def generate_tmp_texture_id(self, texture_name):
+    def generate_tmp_texture_id(self, hashable_key):
         """generate a temp variable for texture, later it would be replaced
         by uniform var"""
-        var_prefix = "tex%s_" % hash(texture_name)
+        var_prefix = "tex%s_" % hash(hashable_key)
         return self._id_prefix + filter_id_illegal_char(var_prefix)
 
     def generate_socket_assignment(self, to_socket_id, to_socket_type,
@@ -225,15 +226,16 @@ class NodeConverterBase:
             prop_b = input_b.get_property(pname)
 
             if pname == FragmentShaderLink.ALPHA:
-                # if one shader input has alpha, the other one should
+                # any shader node has no ALPHA property
                 # default to have alpha = 1.0
-                if prop_a and not prop_b:
+                if prop_a is not None and prop_b is None:
                     prop_b = '1.0'
-                elif prop_b and not prop_a:
+                elif prop_b is not None and prop_a is None:
                     prop_a = '1.0'
 
-            mix_result_id = self.generate_variable_id_str('bsdf_' + pname)
-            if prop_a and prop_b:
+            output_socket = self.bl_node.outputs[0]
+            mix_result_id = self.generate_shader_id_str(output_socket, pname)
+            if prop_a is not None and prop_b is not None:
                 ptype = FragmentShaderLink.get_property_type(pname)
                 self.local_code.append(
                     "%s = mix(%s, %s, %s)"
@@ -241,9 +243,9 @@ class NodeConverterBase:
                 )
 
                 output.set_property(pname, mix_result_id)
-            elif prop_a:
+            elif prop_a is not None:
                 output.set_property(pname, prop_a)
-            elif prop_b:
+            elif prop_b is not None:
                 output.set_property(pname, prop_b)
 
     def add_function_call(self, function, in_args, out_args):
@@ -422,6 +424,7 @@ class NodeConverterBase:
 
         for type_str, id_str in id_to_define:
             # don't define if they already in input sockets
+            assert isinstance(id_str, str) and id_str.isidentifier()
             if id_str not in self._defined_ids:
                 self._defined_ids.add(id_str)
                 self.output_definitions.append(
@@ -499,8 +502,7 @@ class BsdfNodeConverter(NodeConverterBase):
         output_shader_link = FragmentShaderLink()
         self.out_sockets_map[output_socket] = output_shader_link
 
-        if self.bl_node.bl_idname in \
-                ('ShaderNodeBsdfGlass', 'ShaderNodeBsdfPrincipled'):
+        if self.bl_node.bl_idname in ('ShaderNodeBsdfGlass',):
             self.flags.glass = True
 
         if self.bl_node.bl_idname in \
@@ -709,21 +711,24 @@ class ImageTextureNodeConverter(NodeConverterBase):
         function = find_node_function(self.bl_node)
 
         in_arguments = list()
-        tex_coord = self.in_sockets_map[self.bl_node.inputs[0]]
+        tex_coord_socket = self.bl_node.inputs[0]
+        tex_coord = self.in_sockets_map[tex_coord_socket]
+        if not tex_coord_socket.is_linked:
+            # TODO: once generated texture get supported, it should be changed
+            logging.warning(
+                "No texture coordinate input for '%s' "
+                "UV is used by default, at '%s'",
+                self.bl_node.bl_idname, self.bl_node.name)
+            self.local_code.append("%s = vec3(UV, 0.0)" % tex_coord)
 
+        tex_var = self.generate_tmp_texture_id(self.bl_node.name)
         if self.bl_node.image is not None:
-            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
-            )
+            is_normal = False
+        self.textures.append(
+            Texture(self.bl_node.image, tex_var, is_normal)
+        )
 
         in_arguments.append(tex_coord)
         in_arguments.append(tex_var)

+ 31 - 15
io_scene_godot/converters/material/script_shader/node_tree.py

@@ -45,8 +45,9 @@ class ScriptShader:
     def add_fragment_output(self, output_shader_link):
         """link the node tree output with godot fragment output"""
         # pylint: disable-msg=too-many-branches
+        # pylint: disable-msg=too-many-statements
 
-        # hack define those two variable at the begining
+        # 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(
@@ -126,23 +127,30 @@ class ScriptShader:
         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:
+                refraction_offset_value = (0.2, 0.2)
                 fresnel_func = find_function_by_name('refraction_fresnel')
                 self._functions.add(fresnel_func)
                 self._fragment_code_lines.append(
                     "refraction_fresnel(VERTEX, NORMAL, %s, %s)" %
                     (ior, alpha)
                 )
-            self._fragment_code_lines.append(
-                "EMISSION += textureLod(SCREEN_TEXTURE, SCREEN_UV - "
-                "NORMAL.xy * %s , ROUGHNESS).rgb * (1.0 - %s)" %
-                (refraction_offset, alpha)
-            )
+                refraction_offset = 'refraction_offset'
+                # just some magic random value, available for improvements
+                self._uniform_code_lines.append(
+                    'uniform vec2 %s = vec2(0.2, 0.2)' % refraction_offset
+                )
+                self._fragment_code_lines.append(
+                    "EMISSION += textureLod(SCREEN_TEXTURE, SCREEN_UV - "
+                    "NORMAL.xy * %s , ROUGHNESS).rgb * (1.0 - %s)" %
+                    (refraction_offset, alpha)
+                )
+            else:
+                self._fragment_code_lines.append(
+                    "EMISSION += textureLod(SCREEN_TEXTURE, SCREEN_UV, "
+                    "ROUGHNESS).rgb * (1.0 - %s)" % alpha
+                )
+
             self._fragment_code_lines.append(
                 "ALBEDO *= %s" % alpha
             )
@@ -233,12 +241,19 @@ class ScriptShader:
 
     def get_images(self):
         """return a set of all the images used in shader"""
-        return {tex.image for tex in self._textures}
+        image_set = set()
+        for tex in self._textures:
+            if tex.image is not None:
+                image_set.add(tex.image)
+        return image_set
 
     def get_image_texture_info(self):
         """return a list of tuple (image, texture uniform)"""
-        return [(tex.image, uniform)
-                for tex, uniform in self._textures.items()]
+        image_uniform_tuples = list()
+        for tex, uniform in self._textures.items():
+            if tex.image is not None:
+                image_uniform_tuples.append((tex.image, uniform))
+        return image_uniform_tuples
 
 
 def find_material_output_node(node_tree):
@@ -306,7 +321,8 @@ def export_texture(escn_file, export_settings, image):
             src_path = bpy.path.abspath(image.filepath_raw)
         else:
             src_path = image.filepath_raw
-        copyfile(src_path, dst_path)
+        if os.path.normpath(src_path) != os.path.normpath(dst_path):
+            copyfile(src_path, dst_path)
 
     img_resource = ExternalResource(dst_path, "Texture")
     return escn_file.add_external_resource(img_resource, image)

+ 7 - 3
io_scene_godot/converters/material/script_shader/shader_functions.py

@@ -128,12 +128,16 @@ void node_bsdf_principled(vec4 color, float subsurface, vec4 subsurface_color,
     BsdfShaderFunction(
         code="""
 void node_emission(vec4 emission_color, float strength,
-        out vec3 emission_out) {
+        out vec3 emission_out, out float alpha_out) {
     emission_out = emission_color.rgb * strength;
+    alpha_out = emission_color.a;
 }
 """,
         input_sockets=["Color", "Strength"],
-        output_properties=[FragmentShaderLink.EMISSION]
+        output_properties=[
+            FragmentShaderLink.EMISSION,
+            FragmentShaderLink.ALPHA,
+        ]
     ),
 
     BsdfShaderFunction(
@@ -179,7 +183,7 @@ void node_bsdf_glossy(vec4 color, float roughness, out vec3 albedo,
     BsdfShaderFunction(
         code="""
 void node_bsdf_transparent(vec4 color, out float alpha) {
-    alpha = 0.0;
+    alpha = clamp(1.0 - dot(color.rgb, vec3(0.3333334)), 0.0, 1.0);
 }
 """,
         input_sockets=['Color'],

+ 23 - 24
tests/reference_exports/material_cycle/material_cycle.escn

@@ -143,20 +143,20 @@ void fragment () {
 	float node2_shader_in7_roughness = node0_bsdf_out2_roughness;
 	vec3 node2_shader_in8_normal = node0_in2_normal;
 	// output sockets definitions
-	vec3 node2_var0_bsdf_albedo;
-	vec3 node2_var13_bsdf_normal;
+	vec3 node2_shader_out0_albedo;
+	vec3 node2_shader_out13_normal;
 	
-	node2_var0_bsdf_albedo = mix(node2_shader_in1_albedo, node2_shader_in5_albedo,
-		node2_in0_fac);
-	node2_var13_bsdf_normal = mix(node2_shader_in4_normal, node2_shader_in8_normal,
+	node2_shader_out0_albedo = mix(node2_shader_in1_albedo, node2_shader_in5_albedo,
 		node2_in0_fac);
+	node2_shader_out13_normal = mix(node2_shader_in4_normal,
+		node2_shader_in8_normal, node2_in0_fac);
 	
 	
-	ALBEDO = node2_var0_bsdf_albedo;
+	ALBEDO = node2_shader_out0_albedo;
 	SPECULAR = node2_shader_in2_specular;
 	METALLIC = node2_shader_in6_metallic;
 	ROUGHNESS = node2_shader_in7_roughness;
-	NORMAL = node2_var13_bsdf_normal;
+	NORMAL = node2_shader_out13_normal;
 	// uncomment it only when you set diffuse mode to oren nayar
 	// ROUGHNESS = node2_shader_in3_oren_nayar_roughness;
 }
@@ -1147,20 +1147,20 @@ void fragment () {
 	float node2_shader_in6_roughness = node0_bsdf_out2_roughness;
 	vec3 node2_shader_in7_normal = node0_in2_normal;
 	// output sockets definitions
-	vec3 node2_var0_bsdf_albedo;
-	vec3 node2_var13_bsdf_normal;
+	vec3 node2_shader_out0_albedo;
+	vec3 node2_shader_out13_normal;
 	
-	node2_var0_bsdf_albedo = mix(node2_shader_in0_albedo, node2_shader_in4_albedo,
-		0.5);
-	node2_var13_bsdf_normal = mix(node2_shader_in3_normal, node2_shader_in7_normal,
+	node2_shader_out0_albedo = mix(node2_shader_in0_albedo, node2_shader_in4_albedo,
 		0.5);
+	node2_shader_out13_normal = mix(node2_shader_in3_normal,
+		node2_shader_in7_normal, 0.5);
 	
 	
-	ALBEDO = node2_var0_bsdf_albedo;
+	ALBEDO = node2_shader_out0_albedo;
 	SPECULAR = node2_shader_in1_specular;
 	METALLIC = node2_shader_in5_metallic;
 	ROUGHNESS = node2_shader_in6_roughness;
-	NORMAL = node2_var13_bsdf_normal;
+	NORMAL = node2_shader_out13_normal;
 	// uncomment it only when you set diffuse mode to oren nayar
 	// ROUGHNESS = node2_shader_in2_oren_nayar_roughness;
 }
@@ -1219,13 +1219,13 @@ void fragment () {
 	vec3 node1_shader_in5_albedo = vec3(0.0, 0.0, 0.0);
 	vec3 node1_shader_in6_albedo = node1_shader_in5_albedo;
 	// output sockets definitions
-	vec3 node1_var0_bsdf_albedo;
+	vec3 node1_shader_out0_albedo;
 	
-	node1_var0_bsdf_albedo = mix(node1_shader_in1_albedo, node1_shader_in6_albedo,
+	node1_shader_out0_albedo = mix(node1_shader_in1_albedo, node1_shader_in6_albedo,
 		node1_in0_fac);
 	
 	
-	ALBEDO = node1_var0_bsdf_albedo;
+	ALBEDO = node1_shader_out0_albedo;
 	SPECULAR = node1_shader_in2_specular;
 	NORMAL = node1_shader_in4_normal;
 	// uncomment it only when you set diffuse mode to oren nayar
@@ -1244,7 +1244,6 @@ resource_name = "Shader Nodetree"
 code = "shader_type spatial;
 render_mode blend_mix, depth_draw_always, cull_back, diffuse_burley, specular_schlick_ggx;
 
-uniform vec2 refraction_offset = vec2(0.2, 0.2);
 
 
 void node_bsdf_diffuse(vec4 color, float roughness, out vec3 albedo,
@@ -1256,7 +1255,7 @@ void node_bsdf_diffuse(vec4 color, float roughness, out vec3 albedo,
 
 
 void node_bsdf_transparent(vec4 color, out float alpha) {
-    alpha = 0.0;
+    alpha = clamp(1.0 - dot(color.rgb, vec3(0.3333334)), 0.0, 1.0);
 }
 
 void vertex () {
@@ -1301,9 +1300,9 @@ void fragment () {
 	vec3 node2_shader_in4_normal = node1_in2_normal;
 	float node2_shader_in5_alpha = node0_bsdf_out0_alpha;
 	// output sockets definitions
-	float node2_var1_bsdf_alpha;
+	float node2_shader_out1_alpha;
 	
-	node2_var1_bsdf_alpha = mix(1.0, node2_shader_in5_alpha, node2_in0_fac);
+	node2_shader_out1_alpha = mix(1.0, node2_shader_in5_alpha, node2_in0_fac);
 	
 	
 	ALBEDO = node2_shader_in1_albedo;
@@ -1311,9 +1310,9 @@ void fragment () {
 	NORMAL = node2_shader_in4_normal;
 	// uncomment it only when you set diffuse mode to oren nayar
 	// ROUGHNESS = node2_shader_in3_oren_nayar_roughness;
-	EMISSION += textureLod(SCREEN_TEXTURE, SCREEN_UV - NORMAL.xy * refraction_offset
-		, ROUGHNESS).rgb * (1.0 - node2_var1_bsdf_alpha);
-	ALBEDO *= node2_var1_bsdf_alpha;
+	EMISSION += textureLod(SCREEN_TEXTURE, SCREEN_UV, ROUGHNESS).rgb * (1.0 -
+		node2_shader_out1_alpha);
+	ALBEDO *= node2_shader_out1_alpha;
 	ALPHA = 1.0;
 }
 "