def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None: if state.parse_surface: c.write_normal(node.inputs[20]) state.out_basecol = c.parse_vector_input(node.inputs[0]) # subsurface = c.parse_vector_input(node.inputs[1]) # subsurface_radius = c.parse_vector_input(node.inputs[2]) # subsurface_color = c.parse_vector_input(node.inputs[3]) state.out_metallic = c.parse_value_input(node.inputs[4]) state.out_specular = c.parse_value_input(node.inputs[5]) # specular_tint = c.parse_vector_input(node.inputs[6]) state.out_roughness = c.parse_value_input(node.inputs[7]) # aniso = c.parse_vector_input(node.inputs[8]) # aniso_rot = c.parse_vector_input(node.inputs[9]) # sheen = c.parse_vector_input(node.inputs[10]) # sheen_tint = c.parse_vector_input(node.inputs[11]) # clearcoat = c.parse_vector_input(node.inputs[12]) # clearcoat_rough = c.parse_vector_input(node.inputs[13]) # ior = c.parse_vector_input(node.inputs[14]) # transmission = c.parse_vector_input(node.inputs[15]) # transmission_roughness = c.parse_vector_input(node.inputs[16]) if node.inputs[17].is_linked or node.inputs[17].default_value[0] != 0.0: state.out_emission = '({0}.x)'.format(c.parse_vector_input(node.inputs[17])) state.emission_found = True # clearcoar_normal = c.parse_vector_input(node.inputs[21]) # tangent = c.parse_vector_input(node.inputs[22]) if state.parse_opacity: if len(node.inputs) > 21: state.out_opacity = c.parse_value_input(node.inputs[19])
def parse_tex_checker(node: bpy.types.ShaderNodeTexChecker, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: state.curshader.add_function(c_functions.str_tex_checker) if node.inputs[0].is_linked: co = c.parse_vector_input(node.inputs[0]) else: co = 'bposition' # Color if out_socket == node.outputs[0]: col1 = c.parse_vector_input(node.inputs[1]) col2 = c.parse_vector_input(node.inputs[2]) scale = c.parse_value_input(node.inputs[3]) res = f'tex_checker({co}, {col1}, {col2}, {scale})' # Fac else: scale = c.parse_value_input(node.inputs[3]) res = 'tex_checker_f({0}, {1})'.format(co, scale) if state.sample_bump: c.write_bump(node, out_socket, res) return res
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))'
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" 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_bsdfglossy(node: bpy.types.ShaderNodeBsdfGlossy, out_socket: NodeSocket, state: ParserState) -> None: if state.parse_surface: c.write_normal(node.inputs[2]) state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_metallic = '1.0'
def parse_bsdfdiffuse(node: bpy.types.ShaderNodeBsdfDiffuse, out_socket: NodeSocket, state: ParserState) -> None: if state.parse_surface: c.write_normal(node.inputs[2]) state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_specular = '0.0'
def parse_tex_noise(node: bpy.types.ShaderNodeTexNoise, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: c.write_procedurals() state.curshader.add_function(c_functions.str_tex_noise) c.assets_add(os.path.join(arm.utils.get_sdk_path(), 'armory', 'Assets', 'noise256.png')) c.assets_add_embedded_data('noise256.png') state.curshader.add_uniform('sampler2D snoise256', link='$noise256.png') if node.inputs[0].is_linked: co = c.parse_vector_input(node.inputs[0]) else: co = 'bposition' scale = c.parse_value_input(node.inputs[2]) detail = c.parse_value_input(node.inputs[3]) roughness = c.parse_value_input(node.inputs[4]) distortion = c.parse_value_input(node.inputs[5]) # Color if out_socket == node.outputs[1]: res = 'vec3(tex_noise({0} * {1},{2},{3}), tex_noise({0} * {1} + 120.0,{2},{3}), tex_noise({0} * {1} + 168.0,{2},{3}))'.format(co, scale, detail, distortion) # Fac else: res = 'tex_noise({0} * {1},{2},{3})'.format(co, scale, detail, distortion) if state.sample_bump: c.write_bump(node, out_socket, res, 0.1) return res
def parse_bump(node: bpy.types.ShaderNodeBump, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: # Interpolation strength strength = c.parse_value_input(node.inputs[0]) # Height multiplier # distance = c.parse_value_input(node.inputs[1]) state.sample_bump = True height = c.parse_value_input(node.inputs[2]) state.sample_bump = False nor = c.parse_vector_input(node.inputs[3]) if state.sample_bump_res != '': if node.invert: ext = ('1', '2', '3', '4') else: ext = ('2', '1', '4', '3') state.curshader.write( 'float {0}_fh1 = {0}_{1} - {0}_{2}; float {0}_fh2 = {0}_{3} - {0}_{4};' .format(state.sample_bump_res, ext[0], ext[1], ext[2], ext[3])) state.curshader.write( '{0}_fh1 *= ({1}) * 3.0; {0}_fh2 *= ({1}) * 3.0;'.format( state.sample_bump_res, strength)) state.curshader.write( 'vec3 {0}_a = normalize(vec3(2.0, 0.0, {0}_fh1));'.format( state.sample_bump_res)) state.curshader.write( 'vec3 {0}_b = normalize(vec3(0.0, 2.0, {0}_fh2));'.format( state.sample_bump_res)) res = 'normalize(mat3({0}_a, {0}_b, normalize(vec3({0}_fh1, {0}_fh2, 2.0))) * n)'.format( state.sample_bump_res) state.sample_bump_res = '' else: res = 'n' return res
def parse_gamma(node: bpy.types.ShaderNodeGamma, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: out_col = c.parse_vector_input(node.inputs[0]) gamma = c.parse_value_input(node.inputs[1]) return 'pow({0}, vec3({1}))'.format(out_col, gamma)
def parse_tex_gradient(node: bpy.types.ShaderNodeTexGradient, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: if node.inputs[0].is_linked: co = c.parse_vector_input(node.inputs[0]) else: co = 'bposition' grad = node.gradient_type if grad == 'LINEAR': f = f'{co}.x' elif grad == 'QUADRATIC': f = '0.0' elif grad == 'EASING': f = '0.0' elif grad == 'DIAGONAL': f = f'({co}.x + {co}.y) * 0.5' elif grad == 'RADIAL': f = f'atan({co}.y, {co}.x) / PI2 + 0.5' elif grad == 'QUADRATIC_SPHERE': f = '0.0' else: # SPHERICAL f = f'max(1.0 - sqrt({co}.x * {co}.x + {co}.y * {co}.y + {co}.z * {co}.z), 0.0)' # Color if out_socket == node.outputs[0]: res = f'vec3(clamp({f}, 0.0, 1.0))' # Fac else: res = f'(clamp({f}, 0.0, 1.0))' if state.sample_bump: c.write_bump(node, out_socket, res) return res
def parse_surface(world: bpy.types.World, node_surface: bpy.types.Node, frag: Shader): wrd = bpy.data.worlds['Arm'] rpdat = arm.utils.get_rp() solid_mat = rpdat.arm_material_model == 'Solid' if node_surface.type in ('BACKGROUND', 'EMISSION'): # Append irradiance define if rpdat.arm_irradiance and not solid_mat: wrd.world_defs += '_Irr' # Extract environment strength # Todo: follow/parse strength input world.arm_envtex_strength = node_surface.inputs[1].default_value # Color out = cycles.parse_vector_input(node_surface.inputs[0]) frag.write(f'fragColor.rgb = {out};') if not node_surface.inputs[0].is_linked: solid_mat = rpdat.arm_material_model == 'Solid' if rpdat.arm_irradiance and not solid_mat: world.world_defs += '_Irr' world.arm_envtex_color = node_surface.inputs[0].default_value world.arm_envtex_strength = 1.0 else: log.warn( f'World node type {node_surface.type} must not be connected to the world output node!' ) # Invalidate the parser state for subsequent executions cycles.state = None
def parse_bsdftranslucent(node: bpy.types.ShaderNodeBsdfTranslucent, out_socket: NodeSocket, state: ParserState) -> None: if state.parse_surface: c.write_normal(node.inputs[1]) if state.parse_opacity: state.out_opacity = '(1.0 - {0}.r)'.format( c.parse_vector_input(node.inputs[0]))
def parse_tex_wave(node: bpy.types.ShaderNodeTexWave, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: c.write_procedurals() state.curshader.add_function(c_functions.str_tex_wave) if node.inputs[0].is_linked: co = c.parse_vector_input(node.inputs[0]) else: co = 'bposition' scale = c.parse_value_input(node.inputs[1]) distortion = c.parse_value_input(node.inputs[2]) detail = c.parse_value_input(node.inputs[3]) detail_scale = c.parse_value_input(node.inputs[4]) if node.wave_profile == 'SIN': wave_profile = 0 else: wave_profile = 1 if node.wave_type == 'BANDS': wave_type = 0 else: wave_type = 1 # Color if out_socket == node.outputs[0]: res = 'vec3(tex_wave_f({0} * {1},{2},{3},{4},{5},{6}))'.format(co, scale, wave_type, wave_profile, distortion, detail, detail_scale) # Fac else: res = 'tex_wave_f({0} * {1},{2},{3},{4},{5},{6})'.format(co, scale, wave_type, wave_profile, distortion, detail, detail_scale) if state.sample_bump: c.write_bump(node, out_socket, res) return res
def parse_bsdfanisotropic(node: bpy.types.ShaderNodeBsdfAnisotropic, out_socket: NodeSocket, state: ParserState) -> None: if state.parse_surface: c.write_normal(node.inputs[4]) # Revert to glossy state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_metallic = '1.0'
def parse_invert(node: bpy.types.ShaderNodeInvert, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: fac = c.parse_value_input(node.inputs[0]) out_col = c.parse_vector_input(node.inputs[1]) return f'mix({out_col}, vec3(1.0) - ({out_col}), {fac})'
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_emission(node: bpy.types.ShaderNodeEmission, out_socket: NodeSocket, state: ParserState) -> None: if state.parse_surface: # Multiply basecol state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_emission = '1.0' state.emission_found = True emission_strength = c.parse_value_input(node.inputs[1]) state.out_basecol = '({0} * {1})'.format(state.out_basecol, emission_strength)
def parse_bsdfglass(node: bpy.types.ShaderNodeBsdfGlass, out_socket: NodeSocket, state: ParserState) -> None: if state.parse_surface: c.write_normal(node.inputs[3]) state.out_roughness = c.parse_value_input(node.inputs[1]) if state.parse_opacity: state.out_opacity = '(1.0 - {0}.r)'.format( c.parse_vector_input(node.inputs[0]))
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_brightcontrast(node: bpy.types.ShaderNodeBrightContrast, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: out_col = c.parse_vector_input(node.inputs[0]) bright = c.parse_value_input(node.inputs[1]) contr = c.parse_value_input(node.inputs[2]) state.curshader.add_function(c_functions.str_brightcontrast) return 'brightcontrast({0}, {1}, {2})'.format(out_col, bright, contr)
def parse_sepxyz(node: bpy.types.ShaderNodeSeparateXYZ, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: vec = c.parse_vector_input(node.inputs[0]) if out_socket == node.outputs[0]: return '{0}.x'.format(vec) elif out_socket == node.outputs[1]: return '{0}.y'.format(vec) elif out_socket == node.outputs[2]: return '{0}.z'.format(vec)
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_fresnel(node: bpy.types.ShaderNodeFresnel, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: state.curshader.add_function(c_functions.str_fresnel) ior = c.parse_value_input(node.inputs[0]) if node.inputs[1].is_linked: dotnv = 'dot({0}, vVec)'.format(c.parse_vector_input(node.inputs[1])) else: dotnv = 'dotNV' return 'fresnel({0}, {1})'.format(ior, dotnv)
def parse_seprgb(node: bpy.types.ShaderNodeSeparateRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: col = c.parse_vector_input(node.inputs[0]) if out_socket == node.outputs[0]: return '{0}.r'.format(col) elif out_socket == node.outputs[1]: return '{0}.g'.format(col) elif out_socket == node.outputs[2]: return '{0}.b'.format(col)
def parse_huesat(node: bpy.types.ShaderNodeHueSaturation, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: state.curshader.add_function(c_functions.str_hue_sat) hue = c.parse_value_input(node.inputs[0]) sat = c.parse_value_input(node.inputs[1]) val = c.parse_value_input(node.inputs[2]) fac = c.parse_value_input(node.inputs[3]) col = c.parse_vector_input(node.inputs[4]) return f'hue_sat({col}, vec4({hue}-0.5, {sat}, {val}, 1.0-{fac}))'
def parse_curvergb(node: bpy.types.ShaderNodeRGBCurve, 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 return '(sqrt(vec3({0}, {1}, {2}) * vec3({4}, {5}, {6})) * {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, c.vector_curve(name + '3a', vec + '.x', curves[3].points), c.vector_curve(name + '3b', vec + '.y', curves[3].points), c.vector_curve(name + '3c', vec + '.z', curves[3].points))
def parse_layerweight(node: bpy.types.ShaderNodeLayerWeight, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: blend = c.parse_value_input(node.inputs[0]) if node.inputs[1].is_linked: dotnv = 'dot({0}, vVec)'.format(c.parse_vector_input(node.inputs[1])) else: dotnv = 'dotNV' # Fresnel if out_socket == node.outputs[0]: state.curshader.add_function(c_functions.str_fresnel) return 'fresnel(1.0 / (1.0 - {0}), {1})'.format(blend, dotnv) # Facing elif out_socket == node.outputs[1]: return '(1.0 - pow({0}, ({1} < 0.5) ? 2.0 * {1} : 0.5 / (1.0 - {1})))'.format( dotnv, blend)
def parse_tex_musgrave(node: bpy.types.ShaderNodeTexMusgrave, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: state.curshader.add_function(c_functions.str_tex_musgrave) if node.inputs[0].is_linked: co = c.parse_vector_input(node.inputs[0]) else: co = 'bposition' scale = c.parse_value_input(node.inputs['Scale']) # detail = c.parse_value_input(node.inputs[2]) # distortion = c.parse_value_input(node.inputs[3]) res = f'tex_musgrave_f({co} * {scale} * 0.5)' if state.sample_bump: c.write_bump(node, out_socket, res) return res