206 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			206 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|  | from typing import Union | ||
|  | 
 | ||
|  | import bpy | ||
|  | from mathutils import Euler, Vector | ||
|  | 
 | ||
|  | import lnx.log | ||
|  | import lnx.material.cycles as c | ||
|  | import lnx.material.cycles_functions as c_functions | ||
|  | from lnx.material.parser_state import ParserState, ParserPass | ||
|  | from lnx.material.shader import floatstr, vec3str | ||
|  | import lnx.utils as utils | ||
|  | 
 | ||
|  | if lnx.is_reload(__name__): | ||
|  |     lnx.log = lnx.reload_module(lnx.log) | ||
|  |     c = lnx.reload_module(c) | ||
|  |     c_functions = lnx.reload_module(c_functions) | ||
|  |     lnx.material.parser_state = lnx.reload_module(lnx.material.parser_state) | ||
|  |     from lnx.material.parser_state import ParserState, ParserPass | ||
|  |     lnx.material.shader = lnx.reload_module(lnx.material.shader) | ||
|  |     from lnx.material.shader import floatstr, vec3str | ||
|  |     utils = lnx.reload_module(utils) | ||
|  | else: | ||
|  |     lnx.enable_reload(__name__) | ||
|  | 
 | ||
|  | 
 | ||
|  | def parse_curvevec(node: bpy.types.ShaderNodeVectorCurve, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: | ||
|  |     fac = c.parse_value_input(node.inputs[0]) | ||
|  |     vec = c.parse_vector_input(node.inputs[1]) | ||
|  |     curves = node.mapping.curves | ||
|  |     name = c.node_name(node.name) | ||
|  |     # mapping.curves[0].points[0].handle_type # bezier curve | ||
|  |     return '(vec3({0}, {1}, {2}) * {3})'.format( | ||
|  |         c.vector_curve(name + '0', vec + '.x', curves[0].points), | ||
|  |         c.vector_curve(name + '1', vec + '.y', curves[1].points), | ||
|  |         c.vector_curve(name + '2', vec + '.z', curves[2].points), fac) | ||
|  | 
 | ||
|  | 
 | ||
|  | def parse_bump(node: bpy.types.ShaderNodeBump, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: | ||
|  |     if state.curshader.shader_type != 'frag': | ||
|  |         lnx.log.warn("Bump node not supported outside of fragment shaders") | ||
|  |         return 'vec3(0.0)' | ||
|  | 
 | ||
|  |     # Interpolation strength | ||
|  |     strength = c.parse_value_input(node.inputs[0]) | ||
|  |     # Height multiplier | ||
|  |     # distance = c.parse_value_input(node.inputs[1]) | ||
|  |     height = c.parse_value_input(node.inputs[2]) | ||
|  | 
 | ||
|  |     state.current_pass = ParserPass.DX_SCREEN_SPACE | ||
|  |     height_dx = c.parse_value_input(node.inputs[2]) | ||
|  |     state.current_pass = ParserPass.DY_SCREEN_SPACE | ||
|  |     height_dy = c.parse_value_input(node.inputs[2]) | ||
|  |     state.current_pass = ParserPass.REGULAR | ||
|  | 
 | ||
|  |     # nor = c.parse_vector_input(node.inputs[3]) | ||
|  | 
 | ||
|  |     if height_dx != height or height_dy != height: | ||
|  |         tangent = f'{c.dfdx_fine("wposition")} + n * ({height_dx} - {height})' | ||
|  |         bitangent = f'{c.dfdy_fine("wposition")} + n * ({height_dy} - {height})' | ||
|  | 
 | ||
|  |         # Cross-product operand order, dFdy is flipped on d3d11 | ||
|  |         bitangent_first = utils.get_gapi() == 'direct3d11' | ||
|  | 
 | ||
|  |         if node.invert: | ||
|  |             bitangent_first = not bitangent_first | ||
|  | 
 | ||
|  |         if bitangent_first: | ||
|  |             # We need to normalize twice, once for the correct "weight" of the strength, | ||
|  |             # once for having a normalized output vector (lerping vectors does not preserve magnitude) | ||
|  |             res = f'normalize(mix(n, normalize(cross({bitangent}, {tangent})), {strength}))' | ||
|  |         else: | ||
|  |             res = f'normalize(mix(n, normalize(cross({tangent}, {bitangent})), {strength}))' | ||
|  | 
 | ||
|  |     else: | ||
|  |         res = 'n' | ||
|  | 
 | ||
|  |     return res | ||
|  | 
 | ||
|  | 
 | ||
|  | def parse_mapping(node: bpy.types.ShaderNodeMapping, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: | ||
|  |     # Only "Point", "Texture" and "Vector" types supported for now.. | ||
|  |     # More information about the order of operations for this node: | ||
|  |     # https://docs.blender.org/manual/en/latest/render/shader_nodes/vector/mapping.html#properties | ||
|  | 
 | ||
|  |     input_vector: bpy.types.NodeSocket = node.inputs[0] | ||
|  |     input_location: bpy.types.NodeSocket = node.inputs['Location'] | ||
|  |     input_rotation: bpy.types.NodeSocket = node.inputs['Rotation'] | ||
|  |     input_scale: bpy.types.NodeSocket = node.inputs['Scale'] | ||
|  |     out = c.parse_vector_input(input_vector) if input_vector.is_linked else c.to_vec3(input_vector.default_value) | ||
|  |     location = c.parse_vector_input(input_location) if input_location.is_linked else c.to_vec3(input_location.default_value) | ||
|  |     rotation = c.parse_vector_input(input_rotation) if input_rotation.is_linked else c.to_vec3(input_rotation.default_value) | ||
|  |     scale = c.parse_vector_input(input_scale) if input_scale.is_linked else c.to_vec3(input_scale.default_value) | ||
|  | 
 | ||
|  |     # Use inner functions because the order of operations varies between | ||
|  |     # mapping node vector types. This adds a slight overhead but makes | ||
|  |     # the code much more readable. | ||
|  |     # - "Point" and "Vector" use Scale -> Rotate -> Translate | ||
|  |     # - "Texture" uses Translate -> Rotate -> Scale | ||
|  |     def calc_location(output: str) -> str: | ||
|  |         # Vectors and Eulers support the "!=" operator | ||
|  |         if input_scale.is_linked or input_scale.default_value != Vector((1, 1, 1)): | ||
|  |             if node.vector_type == 'TEXTURE': | ||
|  |                 output = f'({output} / {scale})' | ||
|  |             else: | ||
|  |                 output = f'({output} * {scale})' | ||
|  | 
 | ||
|  |         return output | ||
|  | 
 | ||
|  |     def calc_scale(output: str) -> str: | ||
|  |         if input_location.is_linked or input_location.default_value != Vector((0, 0, 0)): | ||
|  |             # z location is a little off sometimes?... | ||
|  |             if node.vector_type == 'TEXTURE': | ||
|  |                 output = f'({output} - {location})' | ||
|  |             else: | ||
|  |                 output = f'({output} + {location})' | ||
|  |         return output | ||
|  | 
 | ||
|  |     out = calc_location(out) if node.vector_type == 'TEXTURE' else calc_scale(out) | ||
|  | 
 | ||
|  |     if input_rotation.is_linked or input_rotation.default_value != Euler((0, 0, 0)): | ||
|  |         var_name = c.node_name(node.name) + "_rotation" + state.get_parser_pass_suffix() | ||
|  |         if node.vector_type == 'TEXTURE': | ||
|  |             state.curshader.write(f'mat3 {var_name}X = mat3(1.0, 0.0, 0.0, 0.0, cos({rotation}.x), sin({rotation}.x), 0.0, -sin({rotation}.x), cos({rotation}.x));') | ||
|  |             state.curshader.write(f'mat3 {var_name}Y = mat3(cos({rotation}.y), 0.0, -sin({rotation}.y), 0.0, 1.0, 0.0, sin({rotation}.y), 0.0, cos({rotation}.y));') | ||
|  |             state.curshader.write(f'mat3 {var_name}Z = mat3(cos({rotation}.z), sin({rotation}.z), 0.0, -sin({rotation}.z), cos({rotation}.z), 0.0, 0.0, 0.0, 1.0);') | ||
|  |         else: | ||
|  |             # A little bit redundant, but faster than 12 more multiplications to make it work dynamically | ||
|  |             state.curshader.write(f'mat3 {var_name}X = mat3(1.0, 0.0, 0.0, 0.0, cos(-{rotation}.x), sin(-{rotation}.x), 0.0, -sin(-{rotation}.x), cos(-{rotation}.x));') | ||
|  |             state.curshader.write(f'mat3 {var_name}Y = mat3(cos(-{rotation}.y), 0.0, -sin(-{rotation}.y), 0.0, 1.0, 0.0, sin(-{rotation}.y), 0.0, cos(-{rotation}.y));') | ||
|  |             state.curshader.write(f'mat3 {var_name}Z = mat3(cos(-{rotation}.z), sin(-{rotation}.z), 0.0, -sin(-{rotation}.z), cos(-{rotation}.z), 0.0, 0.0, 0.0, 1.0);') | ||
|  | 
 | ||
|  |         # XYZ-order euler rotation | ||
|  |         out = f'{out} * {var_name}X * {var_name}Y * {var_name}Z' | ||
|  | 
 | ||
|  |     out = calc_scale(out) if node.vector_type == 'TEXTURE' else calc_location(out) | ||
|  | 
 | ||
|  |     return out | ||
|  | 
 | ||
|  | 
 | ||
|  | def parse_normal(node: bpy.types.ShaderNodeNormal, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: | ||
|  |     nor1 = c.to_vec3(node.outputs['Normal'].default_value) | ||
|  | 
 | ||
|  |     if out_socket == node.outputs['Normal']: | ||
|  |         return nor1 | ||
|  | 
 | ||
|  |     elif out_socket == node.outputs['Dot']: | ||
|  |         nor2 = c.parse_vector_input(node.inputs["Normal"]) | ||
|  |         return f'dot({nor1}, {nor2})' | ||
|  | 
 | ||
|  | 
 | ||
|  | def parse_normalmap(node: bpy.types.ShaderNodeNormalMap, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: | ||
|  |     if state.curshader == state.tese: | ||
|  |         return c.parse_vector_input(node.inputs[1]) | ||
|  |     else: | ||
|  |         # space = node.space | ||
|  |         # map = node.uv_map | ||
|  |         # Color | ||
|  |         c.parse_normal_map_color_input(node.inputs[1], node.inputs[0]) | ||
|  |         return 'n' | ||
|  | 
 | ||
|  | 
 | ||
|  | def parse_vectortransform(node: bpy.types.ShaderNodeVectorTransform, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: | ||
|  |     # type = node.vector_type | ||
|  |     # conv_from = node.convert_from | ||
|  |     # conv_to = node.convert_to | ||
|  |     # Pass through | ||
|  |     return c.parse_vector_input(node.inputs[0]) | ||
|  | 
 | ||
|  | 
 | ||
|  | def parse_displacement(node: bpy.types.ShaderNodeDisplacement, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: | ||
|  |     height = c.parse_value_input(node.inputs[0]) | ||
|  |     midlevel = c.parse_value_input(node.inputs[1]) | ||
|  |     scale = c.parse_value_input(node.inputs[2]) | ||
|  |     nor = c.parse_vector_input(node.inputs[3]) | ||
|  |     return f'(vec3({height}) * {scale})' | ||
|  | 
 | ||
|  | def parse_vectorrotate(node: bpy.types.ShaderNodeVectorRotate, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: | ||
|  | 
 | ||
|  |     type = node.rotation_type | ||
|  |     input_vector: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[0]) | ||
|  |     input_center: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[1]) | ||
|  |     input_axis: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[2]) | ||
|  |     input_angle: bpy.types.NodeSocket = c.parse_value_input(node.inputs[3]) | ||
|  |     input_rotation: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[4]) | ||
|  | 
 | ||
|  |     if node.invert: | ||
|  |         input_invert = "0" | ||
|  |     else: | ||
|  |         input_invert = "1" | ||
|  | 
 | ||
|  |     state.curshader.add_function(c_functions.str_rotate_around_axis) | ||
|  | 
 | ||
|  |     if type == 'AXIS_ANGLE': | ||
|  |         return f'vec3( (length({input_axis}) != 0.0) ? rotate_around_axis({input_vector} - {input_center}, normalize({input_axis}), {input_angle} * {input_invert}) + {input_center} : {input_vector} )' | ||
|  |     elif type == 'X_AXIS': | ||
|  |         return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(1.0, 0.0, 0.0), {input_angle} * {input_invert}) + {input_center} )' | ||
|  |     elif type == 'Y_AXIS': | ||
|  |         return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 1.0, 0.0), {input_angle} * {input_invert}) + {input_center} )' | ||
|  |     elif type == 'Z_AXIS': | ||
|  |         return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 0.0, 1.0), {input_angle} * {input_invert}) + {input_center} )' | ||
|  |     elif type == 'EULER_XYZ': | ||
|  |         state.curshader.add_function(c_functions.str_euler_to_mat3) | ||
|  |         return f'vec3( mat3(({input_invert} < 0.0) ? transpose(euler_to_mat3({input_rotation})) : euler_to_mat3({input_rotation})) * ({input_vector} - {input_center}) + {input_center})' | ||
|  | 
 | ||
|  |     return f'(vec3(1.0, 0.0, 0.0))' |