Repe [T3DU] Update - 31f26d171bba0355ce2a77031e3aad4c64dbc7e9

This commit is contained in:
2026-09-04 10:46:13 -07:00
parent c915312901
commit 0460b9d587
482 changed files with 27797 additions and 3226 deletions

View File

@ -94,15 +94,18 @@ def parse_tex_gradient(node: bpy.types.ShaderNodeTexGradient, out_socket: bpy.ty
if grad == 'LINEAR':
f = f'{co}.x'
elif grad == 'QUADRATIC':
f = '0.0'
f = f'max({co}.x, 0.0)'
f = f'({f} * {f})'
elif grad == 'EASING':
f = '0.0'
f = f'clamp({co}.x, 0.0, 1.0)'
f = f'({f} * {f} * (3.0 - 2.0 * {f}))'
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'
f = f'max(1.0 - sqrt({co}.x * {co}.x + {co}.y * {co}.y + {co}.z * {co}.z), 0.0)'
f = f'({f} * {f})'
else: # SPHERICAL
f = f'max(1.0 - sqrt({co}.x * {co}.x + {co}.y * {co}.y + {co}.z * {co}.z), 0.0)'
@ -246,13 +249,13 @@ def parse_tex_magic(node: bpy.types.ShaderNodeTexMagic, out_socket: bpy.types.No
co = 'bposition'
scale = c.get_value_input(node, ['Scale'])
distortion = c.get_value_input(node, ['Distortion'])
depth = node.turbulence_depth
# Color
if out_socket == node.outputs['Color']:
res = f'tex_magic({co} * {scale} * 4.0)'
# Fac
res = f'tex_magic({co} * {scale}, {distortion}, {depth})'
else:
res = f'tex_magic_f({co} * {scale} * 4.0)'
res = f'tex_magic_f({co} * {scale}, {distortion}, {depth})'
return res
@ -277,36 +280,70 @@ if bpy.app.version < (4, 1, 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(lnx.utils.get_sdk_path(), 'leenkx', 'Assets', 'noise256.png'))
c.assets_add_embedded_data('noise256.png')
state.curshader.add_uniform('sampler2D snoise256', link='$noise256.png')
if node.inputs['Vector'].is_linked:
co = c.get_vector_input(node, ['Vector'])
if 'Vector' in node.inputs and node.inputs['Vector'].is_linked:
co = c.parse_vector_input(node.inputs['Vector'])
elif node.inputs[0].is_linked:
co = c.parse_vector_input(node.inputs[0])
else:
co = 'bposition'
scale = c.get_value_input(node, ['Scale'])
detail = c.get_value_input(node, ['Detail'])
roughness = c.get_value_input(node, ['Roughness'])
distortion = c.get_value_input(node, ['Distortion'])
if bpy.app.version >= (4, 1, 0):
if node.noise_type == "FBM":
state.curshader.add_function(c_functions.str_tex_musgrave)
if out_socket == node.outputs['Color']:
res = 'vec3(tex_musgrave_f({0} * {1}, {2}, {3}), tex_musgrave_f({0} * {1} + 120.0, {2}, {3}), tex_musgrave_f({0} * {1} + 168.0, {2}, {3}))'.format(co, scale, detail, distortion)
else:
res = f'tex_musgrave_f({co} * {scale} * 1.0, {detail}, {distortion})'
w = c.parse_value_input(node.inputs['W']) if 'W' in node.inputs else '0.0'
scale = c.parse_value_input(node.inputs['Scale']) if 'Scale' in node.inputs else '1.0'
detail = c.parse_value_input(node.inputs['Detail']) if 'Detail' in node.inputs else '2.0'
roughness = c.parse_value_input(node.inputs['Roughness']) if 'Roughness' in node.inputs else '0.5'
lacunarity = c.parse_value_input(node.inputs['Lacunarity']) if 'Lacunarity' in node.inputs else '2.0'
offset = c.parse_value_input(node.inputs['Offset']) if 'Offset' in node.inputs else '0.0'
gain = c.parse_value_input(node.inputs['Gain']) if 'Gain' in node.inputs else '1.0'
distortion = c.parse_value_input(node.inputs['Distortion']) if 'Distortion' in node.inputs else '0.0'
dimensions = getattr(node, 'noise_dimensions', '3D')
noise_type = getattr(node, 'noise_type', 'FBM')
normalize = 'true' if getattr(node, 'normalize', True) else 'false'
type_map = {
'FBM': 'noise_fbm',
'MULTIFRACTAL': 'noise_multi_fractal',
'RIDGED_MULTIFRACTAL': 'noise_ridged_multi_fractal',
'HYBRID_MULTIFRACTAL': 'noise_hybrid_multi_fractal',
'HETERO_TERRAIN': 'noise_hetero_terrain'
}
func_name = type_map.get(noise_type, 'noise_fbm')
is_color = (out_socket == node.outputs[1]) or (getattr(out_socket, 'name', '') == 'Color')
if dimensions == '1D':
p_expr = f"({w}) * ({scale})"
dist_expr = f"({p_expr}) + snoise(({p_expr}) + random_float_offset(0.0)) * ({distortion})" if distortion != '0.0' else p_expr
if is_color:
res = f"vec3({func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_float_offset(1.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_float_offset(2.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}))"
else:
if out_socket == node.outputs['Color']:
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)
else:
res = 'tex_noise({0} * {1},{2},{3})'.format(co, scale, detail, distortion)
if node.normalize:
res = f'(1.0 - ({res}))'
res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})"
elif dimensions == '2D':
p_expr = f"({co}).xy * ({scale})"
dist_expr = f"({p_expr}) + vec2(snoise(({p_expr}) + random_vec2_offset(0.0)) * ({distortion}), snoise(({p_expr}) + random_vec2_offset(1.0)) * ({distortion}))" if distortion != '0.0' else p_expr
if is_color:
res = f"vec3({func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec2_offset(2.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec2_offset(3.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}))"
else:
res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})"
elif dimensions == '4D':
p_expr = f"vec4({co}, {w}) * ({scale})"
dist_expr = f"({p_expr}) + vec4(snoise(({p_expr}) + random_vec4_offset(0.0)) * ({distortion}), snoise(({p_expr}) + random_vec4_offset(1.0)) * ({distortion}), snoise(({p_expr}) + random_vec4_offset(2.0)) * ({distortion}), snoise(({p_expr}) + random_vec4_offset(3.0)) * ({distortion}))" if distortion != '0.0' else p_expr
if is_color:
res = f"vec3({func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec4_offset(4.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec4_offset(5.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}))"
else:
res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})"
else:
if out_socket == node.outputs['Color']:
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)
p_expr = f"({co}) * ({scale})"
dist_expr = f"({p_expr}) + vec3(snoise(({p_expr}) + random_vec3_offset(0.0)) * ({distortion}), snoise(({p_expr}) + random_vec3_offset(1.0)) * ({distortion}), snoise(({p_expr}) + random_vec3_offset(2.0)) * ({distortion}))" if distortion != '0.0' else p_expr
if is_color:
res = f"vec3({func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec3_offset(3.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec3_offset(4.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}))"
else:
res = 'tex_noise({0} * {1},{2},{3})'.format(co, scale, detail, distortion)
res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})"
return res
if bpy.app.version < (5, 0, 0):
@ -459,13 +496,65 @@ def parse_sky_multiple_scattering(node: bpy.types.ShaderNodeTexSky, state: Parse
def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
if state.context == ParserContext.OBJECT:
log.warn('Environment Texture node is not supported for object node trees, using default value')
return c.to_vec3([0.0, 0.0, 0.0])
if node.image is None:
return c.to_vec3([1.0, 0.0, 1.0])
image = node.image
# Object context: sample environment texture directly in material shader.
if state.context == ParserContext.OBJECT:
tex_store = c.store_var_name(node)
if c.node_need_reevaluation_for_screenspace_derivative(node):
tex_store += state.get_parser_pass_suffix()
if c.is_parsed(tex_store):
return f'{tex_store}.rgb'
state.parsed.add(tex_store)
tex_name = c.node_name(node.name)
tex_link = None
tex_default_file = None
is_lnx_mat_param = None
if node.lnx_material_param:
tex_link = node.name
is_lnx_mat_param = True
tex = c.make_texture(
image,
tex_name,
c.mat_get_material(),
getattr(node, 'interpolation', 'Smart'),
getattr(node, 'extension', 'REPEAT')
)
if tex is None:
log.warn(f'Object "{state.tree_name}": missing environment texture image "{node.name}"')
return c.to_vec3([1.0, 0.0, 1.0])
if is_lnx_mat_param is None:
c.mat_bind_texture(tex)
state.con.add_elem('tex', 'short2norm')
state.curshader.add_uniform(f'sampler2D {tex_name}', link=tex_link, default_value=tex_default_file, is_lnx_mat_param=is_lnx_mat_param)
state.curshader.add_include('std/math.glsl')
if node.inputs[0].is_linked:
co = c.parse_vector_input(node.inputs[0])
else:
state.curshader.add_uniform('vec3 cameraPos', link='_cameraPosition')
co = 'reflect(normalize(wposition - cameraPos), n)'
if node.projection == 'EQUIRECTANGULAR':
state.curshader.write(f'vec2 uv = envMapEquirect(normalize({co}));')
else:
state.curshader.write(f'vec2 uv = envMapMirror(normalize({co}));')
state.curshader.write(f'vec4 {tex_store} = textureLod({tex_name}, uv, 0.0);')
if image.colorspace_settings.name == 'sRGB':
state.curshader.write(f'{tex_store}.rgb = pow({tex_store}.rgb, vec3(2.2));')
return f'{tex_store}.rgb'
world = state.world
world.world_defs += '_EnvTex'
@ -474,7 +563,6 @@ def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket:
curshader.add_include('std/math.glsl')
curshader.add_uniform('sampler2D envmap', link='_envmap')
image = node.image
filepath = image.filepath
if image.packed_file is None and not os.path.isfile(lnx.utils.asset_path(filepath)):
@ -509,7 +597,7 @@ def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket:
if do_convert:
if not os.path.isfile(unpack_filepath):
lnx.utils.unpack_image(image, unpack_filepath, file_format=target_format)
lnx.utils.convert_image(image, unpack_filepath, target_format)
elif not os.path.isfile(unpack_filepath) or os.path.getsize(unpack_filepath) != image.packed_file.size:
with open(unpack_filepath, 'wb') as f:
@ -561,7 +649,15 @@ def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket:
wrd.world_defs += '_Rad'
assets.add_khafile_def("lnx_radiance")
return 'texture(envmap, envMapEquirect(pos)).rgb * envmapStrength'
if node.inputs[0].is_linked:
co = c.parse_vector_input(node.inputs[0])
else:
co = 'pos'
if node.projection == 'EQUIRECTANGULAR':
return f'texture(envmap, envMapEquirect({co})).rgb * envmapStrength'
else:
return f'texture(envmap, envMapMirror({co})).rgb * envmapStrength'
def parse_tex_voronoi(node: bpy.types.ShaderNodeTexVoronoi, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]:
@ -570,6 +666,9 @@ def parse_tex_voronoi(node: bpy.types.ShaderNodeTexVoronoi, out_socket: bpy.type
outp = 1
elif out_socket.type == 'VECTOR':
outp = 2
elif out_socket.name == 'W':
outp = 3
m = 0
if node.distance == 'MANHATTAN':
m = 1
@ -577,35 +676,50 @@ def parse_tex_voronoi(node: bpy.types.ShaderNodeTexVoronoi, out_socket: bpy.type
m = 2
elif node.distance == 'MINKOWSKI':
m = 3
# TODO: Add node.distance == 'MANHATHAN'
# Add node.feature
# Add node.voronoi_dimensions
exp = c.get_value_input(node, ['Exponent'])
f = 0
if node.feature == 'F2':
f = 1
elif node.feature == 'SMOOTH_F1':
f = 2
elif node.feature == 'DISTANCE_TO_EDGE':
f = 3
elif node.feature == 'N_SPHERE_RADIUS':
f = 4
dim = node.voronoi_dimensions
normalize = 1 if node.normalize else 0
c.write_procedurals()
state.curshader.add_function(c_functions.str_tex_voronoi)
state.curshader.add_function(getattr(c_functions, f'str_tex_voronoi_{bpy.app.version[0]}'))
if node.inputs['Vector'].is_linked:
co = c.get_vector_input(node, ['Vector'])
else:
co = 'bposition'
scale = c.get_value_input(node, ['Scale'])
exp = c.get_value_input(node, ['Exponent']) if m == 3 else '1.0'
randomness = c.get_value_input(node, ['Randomness'])
w = c.get_value_input(node, ['W']) if 'W' in node.inputs else '0.0'
scale = c.get_value_input(node, ['Scale']) if 'Scale' in node.inputs else '5.0'
detail = c.get_value_input(node, ['Detail']) if 'Detail' in node.inputs else '0.0'
roughness = c.get_value_input(node, ['Roughness']) if 'Roughness' in node.inputs else '0.5'
lacunarity = c.get_value_input(node, ['Lacunarity']) if 'Lacunarity' in node.inputs else '2.0'
smoothness = c.get_value_input(node, ['Smoothness']) if 'Smoothness' in node.inputs else '1.0'
randomness = c.get_value_input(node, ['Randomness']) if 'Randomness' in node.inputs else '1.0'
# Color or Position
if out_socket == node.outputs['Color'] or out_socket == node.outputs['Position']:
res = 'tex_voronoi({0}, {1}, {2}, {3}, {4}, {5})'.format(co, randomness, m, outp, scale, exp)
# Distance
res = 'tex_voronoi_{0}({1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13})'.format(dim.lower(), co, randomness, m, outp, scale, exp, w, detail, roughness, lacunarity, smoothness, f, normalize)
else:
res = 'tex_voronoi({0}, {1}, {2}, {3}, {4}, {5}).x'.format(co, randomness, m, outp, scale, exp)
res = 'tex_voronoi_{0}({1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}).x'.format(dim.lower(), co, randomness, m, outp, scale, exp, w, detail, roughness, lacunarity, smoothness, f, normalize)
return res
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_noise)
state.curshader.add_function(c_functions.str_tex_wave)
if node.inputs['Vector'].is_linked:
co = c.get_vector_input(node, ['Vector'])
else:
@ -614,22 +728,101 @@ def parse_tex_wave(node: bpy.types.ShaderNodeTexWave, out_socket: bpy.types.Node
distortion = c.get_value_input(node, ['Distortion'])
detail = c.get_value_input(node, ['Detail'])
detail_scale = c.get_value_input(node, ['Detail Scale'])
detail_roughness = c.get_value_input(node, ['Detail Roughness'])
phase_offset = c.get_value_input(node, ['Phase Offset'])
wave_type = 0 if node.wave_type == 'BANDS' else 1
dir_map = {'X': 0, 'Y': 1, 'Z': 2, 'DIAGONAL': 3}
if hasattr(node, 'wave_direction'):
wave_dir = dir_map.get(node.wave_direction, 0)
elif wave_type == 0:
wave_dir = dir_map.get(node.bands_direction, 0)
else:
wave_dir = dir_map.get(node.rings_direction, 0)
if node.wave_profile == 'SIN':
wave_profile = 0
else:
elif node.wave_profile == 'SAW':
wave_profile = 1
if node.wave_type == 'BANDS':
wave_type = 0
else:
wave_type = 1
wave_profile = 2
args = '{0} * {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}'.format(
co, scale, wave_type, wave_dir, wave_profile, distortion, detail, detail_scale, phase_offset, detail_roughness
)
# Color
if out_socket == node.outputs['Color']:
res = 'vec3(tex_wave_f({0} * {1},{2},{3},{4},{5},{6},{7}))'.format(co, scale, wave_type, wave_profile, distortion, detail, detail_scale, phase_offset)
# Fac
res = 'vec3(tex_wave_f({0}))'.format(args)
else:
res = 'tex_wave_f({0} * {1},{2},{3},{4},{5},{6},{7})'.format(co, scale, wave_type, wave_profile, distortion, detail, detail_scale, phase_offset)
res = 'tex_wave_f({0})'.format(args)
return res
def parse_tex_gabor(node: bpy.types.ShaderNodeTexGabor, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]:
c.write_procedurals()
state.curshader.add_function(c_functions.str_tex_gabor)
if node.inputs['Vector'].is_linked:
co = c.get_vector_input(node, ['Vector'])
else:
co = 'bposition'
scale = c.get_value_input(node, ['Scale'])
freq = c.get_value_input(node, ['Frequency'])
anisotropy = c.get_value_input(node, ['Anisotropy'])
gabor_type = '0.0' if node.gabor_type == '2D' else '1.0'
if node.gabor_type == '2D':
orientation_2d = c.get_value_input(node, ['Orientation'])
orientation_3d = 'vec3(0.0)'
else:
orientation_2d = '0.0'
orientation_3d = c.get_vector_input(node, ['Orientation'])
args = '{0}, {1}, {2}, {3}, {4}, {5}, {6}'.format(
co, scale, freq, anisotropy, orientation_2d, orientation_3d, gabor_type
)
if out_socket == node.outputs['Phase']:
return 'tex_gabor_phase({0})'.format(args)
elif out_socket == node.outputs['Intensity']:
return 'tex_gabor_intensity({0})'.format(args)
return 'tex_gabor_value({0})'.format(args)
def parse_tex_white_noise(node: bpy.types.ShaderNodeTexWhiteNoise, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]:
c.write_procedurals()
state.curshader.add_function(c_functions.str_tex_noise)
if node.inputs[0].is_linked:
co = c.parse_vector_input(node.inputs[0])
else:
co = 'bposition'
w = c.parse_value_input(node.inputs['W']) if 'W' in node.inputs else '0.0'
dimensions = getattr(node, 'noise_dimensions', '3D')
is_color = (out_socket == node.outputs[1]) or (getattr(out_socket, 'name', '') == 'Color')
if dimensions == '1D':
if is_color:
return f'hash_float_to_vec3({w})'
return f'hash_float_to_float({w})'
elif dimensions == '2D':
if is_color:
return f'hash_vec2_to_vec3(({co}).xy)'
return f'hash_vec2_to_float(({co}).xy)'
elif dimensions == '4D':
if is_color:
return f'hash_vec4_to_vec3(vec4({co}, {w}))'
return f'hash_vec4_to_float(vec4({co}, {w}))'
else:
if is_color:
return f'hash_vec3_to_vec3({co})'
return f'hash_vec3_to_float({co})'