From ddc12e860793c68bbd2e73af53117e09011d20ee Mon Sep 17 00:00:00 2001 From: Onek8 Date: Fri, 24 Jul 2026 17:02:00 -0700 Subject: [PATCH] BSDF Shaders --- leenkx/blender/lnx/material/cycles.py | 29 +- .../lnx/material/cycles_nodes/nodes_shader.py | 301 ++++++++++++++---- leenkx/blender/lnx/material/make_mesh.py | 16 + leenkx/blender/lnx/material/node_meta.py | 6 + leenkx/blender/lnx/material/parser_state.py | 44 ++- 5 files changed, 334 insertions(+), 62 deletions(-) diff --git a/leenkx/blender/lnx/material/cycles.py b/leenkx/blender/lnx/material/cycles.py index 12a6ac16..215ff2e0 100644 --- a/leenkx/blender/lnx/material/cycles.py +++ b/leenkx/blender/lnx/material/cycles.py @@ -219,7 +219,15 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types. curshader = state.frag state.curshader = curshader - out_basecol, out_roughness, out_metallic, out_occlusion, out_specular, out_opacity, out_ior, out_emission_col = parse_shader_input(node.inputs[0]) + outs = parse_shader_input(node.inputs[0]) + out_basecol = outs[0] + out_roughness = outs[1] + out_metallic = outs[2] + out_occlusion = outs[3] + out_specular = outs[4] + out_opacity = outs[5] + out_ior = outs[6] + out_emission_col = outs[7] if parse_surface: curshader.write(f'basecol = {out_basecol};') curshader.write(f'roughness = {out_roughness};') @@ -227,6 +235,21 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types. curshader.write(f'occlusion = {out_occlusion};') curshader.write(f'specular = {out_specular};') curshader.write(f'emissionCol = {out_emission_col};') + curshader.write(f'subsurface = {outs[8]};') + curshader.write(f'subsurfaceRadius = {outs[9]};') + curshader.write(f'subsurfaceColor = {outs[10]};') + curshader.write(f'specularTint = {outs[11]};') + curshader.write(f'anisotropy = {outs[12]};') + curshader.write(f'anisoRot = {outs[13]};') + curshader.write(f'sheen = {outs[14]};') + curshader.write(f'sheenRough = {outs[15]};') + curshader.write(f'sheenTint = {outs[16]};') + curshader.write(f'clearcoat = {outs[17]};') + curshader.write(f'clearcoatRough = {outs[18]};') + curshader.write(f'transmission = {outs[19]};') + curshader.write(f'transmissionRough = {outs[20]};') + curshader.write(f'thinWall = {outs[21]};') + curshader.write(f'tangent = {outs[22]};') if mat_state.emission_type == mat_state.EmissionType.SHADELESS: if '_EmissionShadeless' not in wrd.world_defs: @@ -330,6 +353,10 @@ def parse_shader(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> Tuple[st 'BSDF_GLASS', 'HOLDOUT', 'SUBSURFACE_SCATTERING', + 'BSDF_REFRACTION', + 'BSDF_TOON', + 'BSDF_HAIR', + 'BSDF_HAIR_PRINCIPLED', 'BSDF_TRANSLUCENT', 'BSDF_TRANSPARENT', 'BSDF_VELVET', diff --git a/leenkx/blender/lnx/material/cycles_nodes/nodes_shader.py b/leenkx/blender/lnx/material/cycles_nodes/nodes_shader.py index cdd7793a..ee00e30e 100644 --- a/leenkx/blender/lnx/material/cycles_nodes/nodes_shader.py +++ b/leenkx/blender/lnx/material/cycles_nodes/nodes_shader.py @@ -20,11 +20,41 @@ else: if bpy.app.version < (4, 0, 0): EMISSION_COLOR = 'Emission' SUBSURFACE = 'Subsurface' + SUBSURFACE_RADIUS = 'Subsurface Radius' + SUBSURFACE_COLOR = 'Subsurface Color' SPECULAR = 'Specular' + SPECULAR_TINT = 'Specular Tint' + ANISOTROPIC = 'Anisotropic' + ANISOTROPIC_ROT = 'Anisotropic Rotation' + SHEEN = 'Sheen' + SHEEN_TINT = 'Sheen Tint' + CLEARCOAT = 'Clearcoat' + CLEARCOAT_ROUGHNESS = 'Clearcoat Roughness' + CLEARCOAT_NORMAL = 'Clearcoat Normal' + TRANSMISSION = 'Transmission' + TRANSMISSION_ROUGHNESS = 'Transmission Roughness' + TANGENT = 'Tangent' else: EMISSION_COLOR = 'Emission Color' SUBSURFACE = 'Subsurface Weight' + SUBSURFACE_RADIUS = 'Subsurface Radius' + SUBSURFACE_COLOR = 'Subsurface Color' SPECULAR = 'Specular IOR Level' + SPECULAR_TINT = 'Specular Tint' + ANISOTROPIC = 'Anisotropic' + ANISOTROPIC_ROT = 'Anisotropic Rotation' + SHEEN = 'Sheen Weight' + SHEEN_TINT = 'Sheen Tint' + SHEEN_ROUGHNESS = 'Sheen Roughness' + CLEARCOAT = 'Coat Weight' + CLEARCOAT_ROUGHNESS = 'Coat Roughness' + CLEARCOAT_NORMAL = 'Coat Normal' + COAT_IOR = 'Coat IOR' + COAT_TINT = 'Coat Tint' + TRANSMISSION = 'Transmission Weight' + TRANSMISSION_ROUGHNESS = 'Transmission Roughness' + TANGENT = 'Tangent' + THIN_WALL = 'Thin Wall' def parse_mixshader(node: bpy.types.ShaderNodeMixShader, out_socket: NodeSocket, state: ParserState) -> None: @@ -46,54 +76,90 @@ def parse_mixshader(node: bpy.types.ShaderNodeMixShader, out_socket: NodeSocket, mat_state.emission_type = mat_state.EmissionType.NO_EMISSION sss_before_1 = mat_state.needs_sss - bc1, rough1, met1, occ1, spec1, opac1, ior1, emi1 = c.parse_shader_input(node.inputs[1]) + o1 = c.parse_shader_input(node.inputs[1]) sss_1 = mat_state.needs_sss ek1 = mat_state.emission_type mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.needs_sss = sss_before_1 # Reset to state before parsing input 1 - bc2, rough2, met2, occ2, spec2, opac2, ior2, emi2 = c.parse_shader_input(node.inputs[2]) + o2 = c.parse_shader_input(node.inputs[2]) sss_2 = mat_state.needs_sss ek2 = mat_state.emission_type mat_state.needs_sss = sss_1 or sss_2 if state.parse_surface: - state.out_basecol = '({0} * {3} + {1} * {2})'.format(bc1, bc2, fac_var, fac_inv_var) - state.out_roughness = '({0} * {3} + {1} * {2})'.format(rough1, rough2, fac_var, fac_inv_var) - state.out_metallic = '({0} * {3} + {1} * {2})'.format(met1, met2, fac_var, fac_inv_var) - state.out_occlusion = '({0} * {3} + {1} * {2})'.format(occ1, occ2, fac_var, fac_inv_var) - state.out_specular = '({0} * {3} + {1} * {2})'.format(spec1, spec2, fac_var, fac_inv_var) - state.out_emission_col = '({0} * {3} + {1} * {2})'.format(emi1, emi2, fac_var, fac_inv_var) + fm = '{0} * {3} + {1} * {2}' # fac mix template: a*fac_inv + b*fac + fv = (fac_var, fac_inv_var) + state.out_basecol = fm.format(o1[0], o2[0], fv[0], fv[1]) + state.out_roughness = fm.format(o1[1], o2[1], fv[0], fv[1]) + state.out_metallic = fm.format(o1[2], o2[2], fv[0], fv[1]) + state.out_occlusion = fm.format(o1[3], o2[3], fv[0], fv[1]) + state.out_specular = fm.format(o1[4], o2[4], fv[0], fv[1]) + state.out_emission_col = fm.format(o1[7], o2[7], fv[0], fv[1]) + state.out_subsurface = fm.format(o1[8], o2[8], fv[0], fv[1]) + state.out_subsurface_radius = fm.format(o1[9], o2[9], fv[0], fv[1]) + state.out_subsurface_color = fm.format(o1[10], o2[10], fv[0], fv[1]) + state.out_specular_tint = fm.format(o1[11], o2[11], fv[0], fv[1]) + state.out_anisotropy = fm.format(o1[12], o2[12], fv[0], fv[1]) + state.out_aniso_rot = fm.format(o1[13], o2[13], fv[0], fv[1]) + state.out_sheen = fm.format(o1[14], o2[14], fv[0], fv[1]) + state.out_sheen_rough = fm.format(o1[15], o2[15], fv[0], fv[1]) + state.out_sheen_tint = fm.format(o1[16], o2[16], fv[0], fv[1]) + state.out_clearcoat = fm.format(o1[17], o2[17], fv[0], fv[1]) + state.out_clearcoat_rough = fm.format(o1[18], o2[18], fv[0], fv[1]) + state.out_transmission = fm.format(o1[19], o2[19], fv[0], fv[1]) + state.out_transmission_rough = fm.format(o1[20], o2[20], fv[0], fv[1]) + state.out_thin_wall = fm.format(o1[21], o2[21], fv[0], fv[1]) + state.out_tangent = fm.format(o1[22], o2[22], fv[0], fv[1]) mat_state.emission_type = mat_state.EmissionType.get_effective_combination(ek1, ek2) if state.parse_opacity: - state.out_opacity = '({0} * {3} + {1} * {2})'.format(opac1, opac2, fac_var, fac_inv_var) - state.out_ior = '({0} * {3} + {1} * {2})'.format(ior1, ior2, fac_var, fac_inv_var) + fm = '{0} * {3} + {1} * {2}' + fv = (fac_var, fac_inv_var) + state.out_opacity = fm.format(o1[5], o2[5], fv[0], fv[1]) + state.out_ior = fm.format(o1[6], o2[6], fv[0], fv[1]) def parse_addshader(node: bpy.types.ShaderNodeAddShader, out_socket: NodeSocket, state: ParserState) -> None: mat_state.emission_type = mat_state.EmissionType.NO_EMISSION sss_before_1 = mat_state.needs_sss - bc1, rough1, met1, occ1, spec1, opac1, ior1, emi1 = c.parse_shader_input(node.inputs[0]) + o1 = c.parse_shader_input(node.inputs[0]) sss_1 = mat_state.needs_sss ek1 = mat_state.emission_type mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.needs_sss = sss_before_1 # Reset to state before parsing input 0 - bc2, rough2, met2, occ2, spec2, opac2, ior2, emi2 = c.parse_shader_input(node.inputs[1]) + o2 = c.parse_shader_input(node.inputs[1]) sss_2 = mat_state.needs_sss ek2 = mat_state.emission_type mat_state.needs_sss = sss_1 or sss_2 if state.parse_surface: - state.out_basecol = '({0} + {1})'.format(bc1, bc2) - state.out_roughness = '({0} * 0.5 + {1} * 0.5)'.format(rough1, rough2) - state.out_metallic = '({0} * 0.5 + {1} * 0.5)'.format(met1, met2) - state.out_occlusion = '({0} * 0.5 + {1} * 0.5)'.format(occ1, occ2) - state.out_specular = '({0} * 0.5 + {1} * 0.5)'.format(spec1, spec2) - state.out_emission_col = '({0} + {1})'.format(emi1, emi2) + am = '{0} * 0.5 + {1} * 0.5' # add mix template (average) + state.out_basecol = '({0} + {1})'.format(o1[0], o2[0]) + state.out_roughness = am.format(o1[1], o2[1]) + state.out_metallic = am.format(o1[2], o2[2]) + state.out_occlusion = am.format(o1[3], o2[3]) + state.out_specular = am.format(o1[4], o2[4]) + state.out_emission_col = '({0} + {1})'.format(o1[7], o2[7]) + state.out_subsurface = am.format(o1[8], o2[8]) + state.out_subsurface_radius = am.format(o1[9], o2[9]) + state.out_subsurface_color = am.format(o1[10], o2[10]) + state.out_specular_tint = am.format(o1[11], o2[11]) + state.out_anisotropy = am.format(o1[12], o2[12]) + state.out_aniso_rot = am.format(o1[13], o2[13]) + state.out_sheen = am.format(o1[14], o2[14]) + state.out_sheen_rough = am.format(o1[15], o2[15]) + state.out_sheen_tint = am.format(o1[16], o2[16]) + state.out_clearcoat = am.format(o1[17], o2[17]) + state.out_clearcoat_rough = am.format(o1[18], o2[18]) + state.out_transmission = am.format(o1[19], o2[19]) + state.out_transmission_rough = am.format(o1[20], o2[20]) + state.out_thin_wall = am.format(o1[21], o2[21]) + state.out_tangent = am.format(o1[22], o2[22]) mat_state.emission_type = mat_state.EmissionType.get_effective_combination(ek1, ek2) if state.parse_opacity: - state.out_opacity = '({0} * 0.5 + {1} * 0.5)'.format(opac1, opac2) - state.out_ior = '({0} * 0.5 + {1} * 0.5)'.format(ior1, ior2) + am = '{0} * 0.5 + {1} * 0.5' + state.out_opacity = am.format(o1[5], o2[5]) + state.out_ior = am.format(o1[6], o2[6]) # TODO: Refactor using c.get_*_input() @@ -105,18 +171,32 @@ if bpy.app.version < (2, 91, 0): state.out_basecol = c.parse_vector_input(node.inputs['Base Color']) if node.inputs[SUBSURFACE].is_linked or node.inputs[SUBSURFACE].default_value > 0.0: mat_state.needs_sss = True + state.out_subsurface = c.parse_value_input(node.inputs[SUBSURFACE]) + state.out_subsurface_radius = c.parse_vector_input(node.inputs[SUBSURFACE_RADIUS]) + state.out_subsurface_color = c.parse_vector_input(node.inputs[SUBSURFACE_COLOR]) state.out_metallic = c.parse_value_input(node.inputs['Metallic']) state.out_specular = c.parse_value_input(node.inputs[SPECULAR]) + state.out_specular_tint = 'vec3({})'.format(c.parse_value_input(node.inputs[SPECULAR_TINT])) state.out_roughness = c.parse_value_input(node.inputs['Roughness']) + state.out_anisotropy = c.parse_value_input(node.inputs[ANISOTROPIC]) + state.out_aniso_rot = c.parse_value_input(node.inputs[ANISOTROPIC_ROT]) + state.out_sheen = c.parse_value_input(node.inputs[SHEEN]) + state.out_sheen_tint = 'vec3({})'.format(c.parse_value_input(node.inputs[SHEEN_TINT])) + state.out_clearcoat = c.parse_value_input(node.inputs[CLEARCOAT]) + state.out_clearcoat_rough = c.parse_value_input(node.inputs[CLEARCOAT_ROUGHNESS]) + state.out_transmission = c.parse_value_input(node.inputs[TRANSMISSION]) + trans_rough_socket = node.inputs.get(TRANSMISSION_ROUGHNESS) + if trans_rough_socket is not None: + state.out_transmission_rough = c.parse_value_input(trans_rough_socket) + state.out_tangent = c.parse_vector_input(node.inputs[TANGENT]) if node.inputs[EMISSION_COLOR].is_linked or not mat_utils.equals_color_socket(node.inputs[EMISSION_COLOR], (0.0, 0.0, 0.0), comp_alpha=False): emission_col = c.parse_vector_input(node.inputs[EMISSION_COLOR]) state.out_emission_col = emission_col mat_state.emission_type = mat_state.EmissionType.SHADED else: - mat_state.emission_type = mat_state.EmissionType.NO_EMISSION + mat_state.emission_type = mat_state.EmissionType.NO_EMISSION if state.parse_opacity: state.out_ior = c.parse_value_input(node.inputs['IOR']) - # In Blender 2.83, Alpha socket is at index 18, not 19 if 'Alpha' in node.inputs: state.out_opacity = c.parse_value_input(node.inputs['Alpha']) else: @@ -128,21 +208,22 @@ if bpy.app.version >= (2, 91, 0) and bpy.app.version < (4, 0, 0): state.out_basecol = c.parse_vector_input(node.inputs['Base Color']) if node.inputs[SUBSURFACE].is_linked or node.inputs[SUBSURFACE].default_value > 0.0: mat_state.needs_sss = True - # subsurface_radius = c.parse_vector_input(node.inputs[2]) - # subsurface_color = c.parse_vector_input(node.inputs[3]) + state.out_subsurface = c.parse_value_input(node.inputs[SUBSURFACE]) + state.out_subsurface_radius = c.parse_vector_input(node.inputs[SUBSURFACE_RADIUS]) + state.out_subsurface_color = c.parse_vector_input(node.inputs[SUBSURFACE_COLOR]) state.out_metallic = c.parse_value_input(node.inputs['Metallic']) state.out_specular = c.parse_value_input(node.inputs[SPECULAR]) - # specular_tint = c.parse_vector_input(node.inputs[6]) + state.out_specular_tint = 'vec3({})'.format(c.parse_value_input(node.inputs[SPECULAR_TINT])) state.out_roughness = c.parse_value_input(node.inputs['Roughness']) - # 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]) + state.out_anisotropy = c.parse_value_input(node.inputs[ANISOTROPIC]) + state.out_aniso_rot = c.parse_value_input(node.inputs[ANISOTROPIC_ROT]) + state.out_sheen = c.parse_value_input(node.inputs[SHEEN]) + state.out_sheen_tint = 'vec3({})'.format(c.parse_value_input(node.inputs[SHEEN_TINT])) + state.out_clearcoat = c.parse_value_input(node.inputs[CLEARCOAT]) + state.out_clearcoat_rough = c.parse_value_input(node.inputs[CLEARCOAT_ROUGHNESS]) + state.out_transmission = c.parse_value_input(node.inputs[TRANSMISSION]) + state.out_transmission_rough = c.parse_value_input(node.inputs[TRANSMISSION_ROUGHNESS]) + state.out_tangent = c.parse_vector_input(node.inputs[TANGENT]) if (node.inputs['Emission Strength'].is_linked or node.inputs['Emission Strength'].default_value != 0.0)\ and (node.inputs[EMISSION_COLOR].is_linked or not mat_utils.equals_color_socket(node.inputs[EMISSION_COLOR], (0.0, 0.0, 0.0), comp_alpha=False)): emission_col = c.parse_vector_input(node.inputs[EMISSION_COLOR]) @@ -151,8 +232,6 @@ if bpy.app.version >= (2, 91, 0) and bpy.app.version < (4, 0, 0): mat_state.emission_type = mat_state.EmissionType.SHADED else: mat_state.emission_type = mat_state.EmissionType.NO_EMISSION - # clearcoar_normal = c.parse_vector_input(node.inputs[21]) - # tangent = c.parse_vector_input(node.inputs[22]) if state.parse_opacity: state.out_ior = c.parse_value_input(node.inputs['IOR']) if 'Alpha' in node.inputs: @@ -169,14 +248,18 @@ if bpy.app.version >= (4, 0, 0): if sss_input is not None: if sss_input.is_linked or sss_input.default_value > 0.0: mat_state.needs_sss = True - - subsurface = c.parse_value_input(node.inputs[SUBSURFACE]) - subsurface_radius = c.parse_vector_input(node.inputs['Subsurface Radius']) - subsurface_color_input = node.inputs.get('Subsurface Color') - if subsurface_color_input is not None: - subsurface_color = c.parse_vector_input(subsurface_color_input) + state.out_subsurface = c.parse_value_input(sss_input) + + sss_radius_input = node.inputs.get(SUBSURFACE_RADIUS) + if sss_radius_input is not None: + state.out_subsurface_radius = c.parse_vector_input(sss_radius_input) + + sss_color_input = node.inputs.get(SUBSURFACE_COLOR) + if sss_color_input is not None: + state.out_subsurface_color = c.parse_vector_input(sss_color_input) else: - subsurface_color = c.parse_vector_input(node.inputs['Base Color']) + state.out_subsurface_color = c.parse_vector_input(node.inputs['Base Color']) + state.out_metallic = c.parse_value_input(node.inputs['Metallic']) specular_socket = node.inputs.get(SPECULAR) @@ -188,7 +271,11 @@ if bpy.app.version >= (4, 0, 0): state.out_specular = c.parse_value_input(specular_socket) else: state.out_specular = '1.0' - + + spec_tint_socket = node.inputs.get(SPECULAR_TINT) + if spec_tint_socket is not None: + state.out_specular_tint = c.parse_vector_input(spec_tint_socket) + state.out_roughness = c.parse_value_input(node.inputs['Roughness']) # Prevent black material when metal = 1.0 and roughness = 0.0 try: @@ -196,6 +283,46 @@ if bpy.app.version >= (4, 0, 0): state.out_roughness = '0.001' except ValueError: pass + + aniso_socket = node.inputs.get(ANISOTROPIC) + if aniso_socket is not None: + state.out_anisotropy = c.parse_value_input(aniso_socket) + aniso_rot_socket = node.inputs.get(ANISOTROPIC_ROT) + if aniso_rot_socket is not None: + state.out_aniso_rot = c.parse_value_input(aniso_rot_socket) + + sheen_socket = node.inputs.get(SHEEN) + if sheen_socket is not None: + state.out_sheen = c.parse_value_input(sheen_socket) + sheen_rough_socket = node.inputs.get(SHEEN_ROUGHNESS) + if sheen_rough_socket is not None: + state.out_sheen_rough = c.parse_value_input(sheen_rough_socket) + sheen_tint_socket = node.inputs.get(SHEEN_TINT) + if sheen_tint_socket is not None: + state.out_sheen_tint = c.parse_vector_input(sheen_tint_socket) + + coat_socket = node.inputs.get(CLEARCOAT) + if coat_socket is not None: + state.out_clearcoat = c.parse_value_input(coat_socket) + coat_rough_socket = node.inputs.get(CLEARCOAT_ROUGHNESS) + if coat_rough_socket is not None: + state.out_clearcoat_rough = c.parse_value_input(coat_rough_socket) + + trans_socket = node.inputs.get(TRANSMISSION) + if trans_socket is not None: + state.out_transmission = c.parse_value_input(trans_socket) + trans_rough_socket = node.inputs.get(TRANSMISSION_ROUGHNESS) + if trans_rough_socket is not None: + state.out_transmission_rough = c.parse_value_input(trans_rough_socket) + + thin_wall_socket = node.inputs.get(THIN_WALL) + if thin_wall_socket is not None: + state.out_thin_wall = c.parse_value_input(thin_wall_socket) + + tangent_socket = node.inputs.get(TANGENT) + if tangent_socket is not None: + state.out_tangent = c.parse_vector_input(tangent_socket) + if (node.inputs['Emission Strength'].is_linked or node.inputs['Emission Strength'].default_value != 0.0)\ and (node.inputs[EMISSION_COLOR].is_linked or not mat_utils.equals_color_socket(node.inputs[EMISSION_COLOR], (0.0, 0.0, 0.0), comp_alpha=False)): emission_col = c.parse_vector_input(node.inputs[EMISSION_COLOR]) @@ -203,17 +330,7 @@ if bpy.app.version >= (4, 0, 0): state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength) mat_state.emission_type = mat_state.EmissionType.SHADED else: - mat_state.emission_type = mat_state.EmissionType.NO_EMISSION - #state.out_occlusion = state.out_roughness - #state.out_aniso = c.parse_vector_input(node.inputs[14]) - #state.out_aniso_rot = c.parse_vector_input(node.inputs[15]) - #state.out_sheen = c.parse_vector_input(node.inputs[23]) - #state.out_sheen_tint = c.parse_vector_input(node.inputs[25]) - #state.out_clearcoat = c.parse_vector_input(node.inputs[18]) - #state.out_clearcoat_rough = c.parse_vector_input(node.inputs[19]) - #state.out_ior = c.parse_value_input(node.inputs[3]) - #state.out_transmission = c.parse_vector_input(node.inputs[17]) - #state.out_transmission_roughness = state.out_roughness + mat_state.emission_type = mat_state.EmissionType.NO_EMISSION if state.parse_opacity: state.out_ior = c.parse_value_input(node.inputs['IOR']) state.out_opacity = c.parse_value_input(node.inputs['Alpha']) @@ -285,7 +402,68 @@ def parse_bsdfglass(node: bpy.types.ShaderNodeBsdfGlass, out_socket: NodeSocket, def parse_bsdfhair(node: bpy.types.ShaderNodeBsdfHair, out_socket: NodeSocket, state: ParserState) -> None: - pass + if state.parse_surface: + state.out_basecol = c.parse_vector_input(node.inputs['Color']) + state.out_roughness = c.parse_value_input(node.inputs['RoughnessU']) + state.out_specular = '0.0' + rough_v_socket = node.inputs.get('RoughnessV') + if rough_v_socket is not None: + state.out_aniso_rot = c.parse_value_input(rough_v_socket) + tangent_socket = node.inputs.get('Tangent') + if tangent_socket is not None: + state.out_tangent = c.parse_vector_input(tangent_socket) + + +if bpy.app.version >= (2, 83, 0): + def parse_bsdfhairprincipled(node: 'bpy.types.ShaderNodeBsdfHairPrincipled', out_socket: NodeSocket, state: ParserState) -> None: + if state.parse_surface: + c.write_normal(node.inputs['Normal']) + parametrization = getattr(node, 'parametrization', 'COLOR') + if parametrization == 'COLOR': + color_socket = node.inputs.get('Color') + if color_socket is not None: + state.out_basecol = c.parse_vector_input(color_socket) + elif parametrization == 'MELANIN': + melanin_socket = node.inputs.get('Melanin') + if melanin_socket is not None: + state.out_subsurface = c.parse_value_input(melanin_socket) + melanin_red_socket = node.inputs.get('Melanin Redness') + if melanin_red_socket is not None: + state.out_specular_tint = 'vec3({})'.format(c.parse_value_input(melanin_red_socket)) + elif parametrization == 'ABSORPTION': + absorption_socket = node.inputs.get('Absorption Coefficient') + if absorption_socket is not None: + state.out_specular_tint = c.parse_vector_input(absorption_socket) + tint_socket = node.inputs.get('Tint') + if tint_socket is not None: + state.out_subsurface_color = c.parse_vector_input(tint_socket) + rough_socket = node.inputs.get('Roughness') + if rough_socket is not None: + state.out_roughness = c.parse_value_input(rough_socket) + model = getattr(node, 'model', 'CHIANG') + if model == 'CHIANG': + radial_rough_socket = node.inputs.get('Radial Roughness') + if radial_rough_socket is not None: + state.out_aniso_rot = c.parse_value_input(radial_rough_socket) + coat_socket = node.inputs.get('Coat') + if coat_socket is not None: + state.out_clearcoat = c.parse_value_input(coat_socket) + random_color_socket = node.inputs.get('Random Color') + if random_color_socket is not None: + state.out_sheen = c.parse_value_input(random_color_socket) + random_rough_socket = node.inputs.get('Random Roughness') + if random_rough_socket is not None: + state.out_sheen_rough = c.parse_value_input(random_rough_socket) + offset_socket = node.inputs.get('Offset') + if offset_socket is not None: + state.out_anisotropy = c.parse_value_input(offset_socket) + state.out_specular = '0.0' + state.out_metallic = '0.0' + if state.parse_opacity: + ior_socket = node.inputs.get('IOR') + if ior_socket is not None: + state.out_ior = c.parse_value_input(ior_socket) + state.out_opacity = '1.0' def parse_holdout(node: bpy.types.ShaderNodeHoldout, out_socket: NodeSocket, state: ParserState) -> None: @@ -309,11 +487,18 @@ def parse_subsurfacescattering(node: bpy.types.ShaderNodeSubsurfaceScattering, o mat_state.needs_sss = True c.write_normal(node.inputs['Normal']) state.out_basecol = c.parse_vector_input(node.inputs['Color']) + state.out_subsurface = c.parse_value_input(node.inputs['Scale']) + state.out_subsurface_radius = c.parse_vector_input(node.inputs['Radius']) + state.out_subsurface_color = c.parse_vector_input(node.inputs['Color']) + state.out_specular = '0.0' def parse_bsdftoon(node: bpy.types.ShaderNodeBsdfToon, out_socket: NodeSocket, state: ParserState) -> None: - # c.write_normal(node.inputs[3]) - pass + if state.parse_surface: + c.write_normal(node.inputs['Normal']) + state.out_basecol = c.parse_vector_input(node.inputs['Color']) + state.out_roughness = c.parse_value_input(node.inputs['Size']) + state.out_specular = '0.0' def parse_bsdftranslucent(node: bpy.types.ShaderNodeBsdfTranslucent, out_socket: NodeSocket, state: ParserState) -> None: diff --git a/leenkx/blender/lnx/material/make_mesh.py b/leenkx/blender/lnx/material/make_mesh.py index cd92fc04..eade9ed2 100644 --- a/leenkx/blender/lnx/material/make_mesh.py +++ b/leenkx/blender/lnx/material/make_mesh.py @@ -856,6 +856,22 @@ def _write_material_attribs_default(frag: shader.Shader, parse_opacity: bool): # We may not use emission, but the attribute will then be removed # by the shader compiler frag.write('vec3 emissionCol;') + # Extended BRDF parameters + frag.write('float subsurface = 0.0;') + frag.write('vec3 subsurfaceRadius = vec3(0.0);') + frag.write('vec3 subsurfaceColor = vec3(0.8);') + frag.write('vec3 specularTint = vec3(1.0);') + frag.write('float anisotropy = 0.0;') + frag.write('float anisoRot = 0.0;') + frag.write('float sheen = 0.0;') + frag.write('float sheenRough = 0.5;') + frag.write('vec3 sheenTint = vec3(1.0);') + frag.write('float clearcoat = 0.0;') + frag.write('float clearcoatRough = 0.03;') + frag.write('float transmission = 0.0;') + frag.write('float transmissionRough = 0.0;') + frag.write('float thinWall = 0.0;') + frag.write('vec3 tangent = vec3(0.0);') if parse_opacity: frag.write('float opacity;') frag.write('float ior = 1.45;') diff --git a/leenkx/blender/lnx/material/node_meta.py b/leenkx/blender/lnx/material/node_meta.py index 1052ffff..7b5e88c4 100644 --- a/leenkx/blender/lnx/material/node_meta.py +++ b/leenkx/blender/lnx/material/node_meta.py @@ -165,6 +165,10 @@ ALL_NODES: Dict[str, MaterialNodeMeta] = { 'TRANSPARENT_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdftransparent), 'BSDF_REFRACTION': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfrefraction), 'REFRACTION_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfrefraction), + 'BSDF_TOON': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdftoon), + 'TOON_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdftoon), + 'BSDF_HAIR': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfhair), + 'HAIR_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfhair), 'EMISSION': MaterialNodeMeta(parse_func=nodes_shader.parse_emission), 'HOLDOUT': MaterialNodeMeta( parse_func=nodes_shader.parse_holdout, @@ -211,6 +215,8 @@ if bpy.app.version < (4, 1, 0): ALL_NODES['TEX_MUSGRAVE'] = MaterialNodeMeta(parse_func=nodes_texture.parse_tex_musgrave) if bpy.app.version >= (4, 0, 0): ALL_NODES['BSDF_SHEEN'] = MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfsheen) +if bpy.app.version >= (2, 83, 0): + ALL_NODES['BSDF_HAIR_PRINCIPLED'] = MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfhairprincipled) if bpy.app.version < (5, 0, 0): ALL_NODES['TEX_POINTDENSITY'] = MaterialNodeMeta(parse_func=nodes_texture.parse_tex_pointdensity, compute_dxdy_variants=ComputeDXDYVariant.NEVER) diff --git a/leenkx/blender/lnx/material/parser_state.py b/leenkx/blender/lnx/material/parser_state.py index f86d3a60..3d7e15be 100644 --- a/leenkx/blender/lnx/material/parser_state.py +++ b/leenkx/blender/lnx/material/parser_state.py @@ -99,6 +99,21 @@ class ParserState: self.out_opacity: floatstr = '1.0' self.out_ior: floatstr = '1.450' self.out_emission_col: vec3str = 'vec3(0.0)' + self.out_subsurface: floatstr = '0.0' + self.out_subsurface_radius: vec3str = 'vec3(0.0)' + self.out_subsurface_color: vec3str = 'vec3(0.8)' + self.out_specular_tint: vec3str = 'vec3(1.0)' + self.out_anisotropy: floatstr = '0.0' + self.out_aniso_rot: floatstr = '0.0' + self.out_sheen: floatstr = '0.0' + self.out_sheen_rough: floatstr = '0.5' + self.out_sheen_tint: vec3str = 'vec3(1.0)' + self.out_clearcoat: floatstr = '0.0' + self.out_clearcoat_rough: floatstr = '0.03' + self.out_transmission: floatstr = '0.0' + self.out_transmission_rough: floatstr = '0.0' + self.out_thin_wall: floatstr = '0.0' + self.out_tangent: vec3str = 'vec3(0.0)' def reset_outs(self): """Reset the shader output values to their default values.""" @@ -110,11 +125,34 @@ class ParserState: self.out_opacity = '1.0' self.out_ior = '1.450' self.out_emission_col = 'vec3(0.0)' + self.out_subsurface = '0.0' + self.out_subsurface_radius = 'vec3(0.0)' + self.out_subsurface_color = 'vec3(0.8)' + self.out_specular_tint = 'vec3(1.0)' + self.out_anisotropy = '0.0' + self.out_aniso_rot = '0.0' + self.out_sheen = '0.0' + self.out_sheen_rough = '0.5' + self.out_sheen_tint = 'vec3(1.0)' + self.out_clearcoat = '0.0' + self.out_clearcoat_rough = '0.03' + self.out_transmission = '0.0' + self.out_transmission_rough = '0.0' + self.out_thin_wall = '0.0' + self.out_tangent = 'vec3(0.0)' - def get_outs(self) -> Tuple[vec3str, floatstr, floatstr, floatstr, floatstr, floatstr, floatstr, vec3str]: + def get_outs(self) -> Tuple: """Return the shader output values as a tuple.""" - return (self.out_basecol, self.out_roughness, self.out_metallic, self.out_occlusion, self.out_specular, - self.out_opacity, self.out_ior, self.out_emission_col) + return (self.out_basecol, self.out_roughness, self.out_metallic, + self.out_occlusion, self.out_specular, self.out_opacity, + self.out_ior, self.out_emission_col, + self.out_subsurface, self.out_subsurface_radius, + self.out_subsurface_color, self.out_specular_tint, + self.out_anisotropy, self.out_aniso_rot, + self.out_sheen, self.out_sheen_rough, self.out_sheen_tint, + self.out_clearcoat, self.out_clearcoat_rough, + self.out_transmission, self.out_transmission_rough, + self.out_thin_wall, self.out_tangent) def get_parser_pass_suffix(self) -> str: