forked from LeenkxTeam/LNXSDK
Group Nodes, Shaders, Textures, Displacement
This commit is contained in:
@ -97,59 +97,76 @@ def _parse_mixrgb(node: bpy.types.ShaderNodeMixRGB, out_socket: bpy.types.NodeSo
|
||||
col1 = c.parse_vector_input(node.inputs[6])
|
||||
col2 = c.parse_vector_input(node.inputs[7])
|
||||
|
||||
# Store factor in variable for linked factor input
|
||||
if node.inputs[0].is_linked:
|
||||
fac = c.node_name(node.name) + '_fac' + state.get_parser_pass_suffix()
|
||||
state.curshader.write('float {0} = {1};'.format(fac, c.parse_value_input(node.inputs[0])))
|
||||
else:
|
||||
fac = c.parse_value_input(node.inputs[0])
|
||||
|
||||
fac = _mixrgb_fac(node, state)
|
||||
if node.clamp_factor:
|
||||
fac = f'clamp({fac}, 0.0, 1.0)'
|
||||
|
||||
# TODO: Do not mix if factor is constant 0.0 or 1.0?
|
||||
|
||||
blend = node.blend_type
|
||||
out_col = _mixrgb_blend(col1, col2, fac, node.blend_type, state)
|
||||
if node.clamp_result:
|
||||
return 'clamp({0}, vec3(0.0), vec3(1.0))'.format(out_col)
|
||||
return out_col
|
||||
|
||||
|
||||
def _mixrgb_blend(col1: vec3str, col2: vec3str, fac: str, blend: str, state: ParserState) -> vec3str:
|
||||
if blend == 'MIX':
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac)
|
||||
return 'mix({0}, {1}, {2})'.format(col1, col2, fac)
|
||||
elif blend == 'ADD':
|
||||
out_col = 'mix({0}, {0} + {1}, {2})'.format(col1, col2, fac)
|
||||
return 'mix({0}, {0} + {1}, {2})'.format(col1, col2, fac)
|
||||
elif blend == 'MULTIPLY':
|
||||
out_col = 'mix({0}, {0} * {1}, {2})'.format(col1, col2, fac)
|
||||
return 'mix({0}, {0} * {1}, {2})'.format(col1, col2, fac)
|
||||
elif blend == 'SUBTRACT':
|
||||
out_col = 'mix({0}, {0} - {1}, {2})'.format(col1, col2, fac)
|
||||
return 'mix({0}, {0} - {1}, {2})'.format(col1, col2, fac)
|
||||
elif blend == 'SCREEN':
|
||||
out_col = 'mix({0}, vec3(1.0) - (vec3(1.0) - {0}) * (vec3(1.0) - {1}), {2})'.format(col1, col2, fac)
|
||||
return 'mix({0}, vec3(1.0) - (vec3(1.0) - {0}) * (vec3(1.0) - {1}), {2})'.format(col1, col2, fac)
|
||||
elif blend == 'DIVIDE':
|
||||
out_col = 'mix({0}, {0} / max({1}, vec3(0.000001)), {2})'.format(col1, col2, fac)
|
||||
return 'mix({0}, {0} / max({1}, vec3(0.000001)), {2} * vec3(notEqual({1}, vec3(0.0))))'.format(col1, col2, fac)
|
||||
elif blend == 'DIFFERENCE':
|
||||
out_col = 'mix({0}, abs({0} - {1}), {2})'.format(col1, col2, fac)
|
||||
return 'mix({0}, abs({0} - {1}), {2})'.format(col1, col2, fac)
|
||||
elif blend == 'DARKEN':
|
||||
out_col = 'mix({0}, min({0}, {1}), {2})'.format(col1, col2, fac)
|
||||
return 'mix({0}, min({0}, {1}), {2})'.format(col1, col2, fac)
|
||||
elif blend == 'LIGHTEN':
|
||||
out_col = 'mix({0}, max({0}, {1}), {2})'.format(col1, col2, fac)
|
||||
return 'mix({0}, max({0}, {1}), {2})'.format(col1, col2, fac)
|
||||
elif blend == 'OVERLAY':
|
||||
overlay = 'vec3({0}.r < 0.5 ? 2.0 * {0}.r * {1}.r : 1.0 - 2.0 * (1.0 - {0}.r) * (1.0 - {1}.r), {0}.g < 0.5 ? 2.0 * {0}.g * {1}.g : 1.0 - 2.0 * (1.0 - {0}.g) * (1.0 - {1}.g), {0}.b < 0.5 ? 2.0 * {0}.b * {1}.b : 1.0 - 2.0 * (1.0 - {0}.b) * (1.0 - {1}.b))'.format(col1, col2)
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, overlay, fac)
|
||||
return 'mix({0}, {1}, {2})'.format(col1, overlay, fac)
|
||||
elif blend == 'DODGE':
|
||||
dodge = '{0} / max(vec3(1.0) - {1}, vec3(0.000001))'.format(col1, col2)
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, dodge, fac)
|
||||
return 'clamp({0} / max(vec3(1.0) - {2} * {1}, vec3(0.000001)), vec3(0.0), vec3(1.0))'.format(col1, col2, fac)
|
||||
elif blend == 'BURN':
|
||||
burn = 'vec3(1.0) - (vec3(1.0) - {0}) / max({1}, vec3(0.000001))'.format(col1, col2)
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, burn, fac)
|
||||
return 'clamp(vec3(1.0) - (vec3(1.0) - {0}) / max(vec3(1.0 - {2}) + {2} * {1}, vec3(0.000001)), vec3(0.0), vec3(1.0))'.format(col1, col2, fac)
|
||||
elif blend == 'SOFT_LIGHT':
|
||||
soft = '(vec3(1.0) - {1}) * {0} * {0} + {1} * (2.0 * {0} * (vec3(1.0) - {0}) + sqrt({0}) * (2.0 * {0} - vec3(1.0)))'.format(col1, col2)
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, soft, fac)
|
||||
return 'mix({0}, {1}, {2})'.format(col1, soft, fac)
|
||||
elif blend == 'LINEAR_LIGHT':
|
||||
linear = '{0} + 2.0 * {1} - vec3(1.0)'.format(col1, col2)
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, linear, fac)
|
||||
return 'mix({0}, {1}, {2})'.format(col1, linear, fac)
|
||||
elif blend == 'EXCLUSION':
|
||||
return 'max(mix({0}, {0} + {1} - 2.0 * {0} * {1}, {2}), vec3(0.0))'.format(col1, col2, fac)
|
||||
elif blend in ['HUE', 'SATURATION', 'COLOR', 'VALUE']:
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac)
|
||||
state.curshader.add_function(c_functions.str_hue_sat)
|
||||
state.curshader.add_function(c_functions.str_mix_hsv)
|
||||
func = {'HUE': 'mix_hue', 'SATURATION': 'mix_sat', 'COLOR': 'mix_color', 'VALUE': 'mix_val'}[blend]
|
||||
return '{3}({2}, {0}, {1})'.format(col1, col2, fac, func)
|
||||
else:
|
||||
log.warn(f'MixRGB node: unsupported blend type {node.blend_type}.')
|
||||
out_col = col1
|
||||
log.warn(f'MixRGB node: unsupported blend type {blend}.')
|
||||
return col1
|
||||
|
||||
if node.clamp_result:
|
||||
|
||||
def _mixrgb_fac(node: bpy.types.Node, state: ParserState) -> str:
|
||||
if node.inputs[0].is_linked:
|
||||
fac = c.node_uid(node) + '_fac' + state.get_parser_pass_suffix()
|
||||
state.curshader.write('float {0} = {1};'.format(fac, c.parse_value_input(node.inputs[0])))
|
||||
return fac
|
||||
return c.parse_value_input(node.inputs[0])
|
||||
|
||||
|
||||
def parse_mixrgb(node: bpy.types.ShaderNodeMixRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
|
||||
col1 = c.parse_vector_input(node.inputs[1])
|
||||
col2 = c.parse_vector_input(node.inputs[2])
|
||||
fac = f'clamp({_mixrgb_fac(node, state)}, 0.0, 1.0)'
|
||||
out_col = _mixrgb_blend(col1, col2, fac, node.blend_type, state)
|
||||
if node.use_clamp:
|
||||
return 'clamp({0}, vec3(0.0), vec3(1.0))'.format(out_col)
|
||||
return out_col
|
||||
|
||||
@ -158,7 +175,7 @@ def parse_curvergb(node: bpy.types.ShaderNodeRGBCurve, out_socket: bpy.types.Nod
|
||||
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)
|
||||
name = c.node_uid(node)
|
||||
# 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,
|
||||
|
||||
@ -89,7 +89,7 @@ def parse_valtorgb(node: bpy.types.ShaderNodeValToRGB, out_socket: bpy.types.Nod
|
||||
elems = node.color_ramp.elements
|
||||
|
||||
use_color_out = out_socket == node.outputs[0]
|
||||
ramp_store = c.node_name(node.name) + '_res' + state.get_parser_pass_suffix()
|
||||
ramp_store = c.node_uid(node) + '_res' + state.get_parser_pass_suffix()
|
||||
|
||||
if c.is_parsed(ramp_store):
|
||||
return f'{ramp_store}.rgb' if use_color_out else f'{ramp_store}.a'
|
||||
@ -100,26 +100,26 @@ def parse_valtorgb(node: bpy.types.ShaderNodeValToRGB, out_socket: bpy.types.Nod
|
||||
state.parsed.add(ramp_store)
|
||||
return f'{ramp_store}.rgb' if use_color_out else f'{ramp_store}.a'
|
||||
|
||||
cols_var = c.node_name(node.name).upper() + '_COLS'
|
||||
cols_var = c.node_uid(node).upper() + '_COLS'
|
||||
if state.current_pass == ParserPass.REGULAR:
|
||||
cols_entries = ', '.join(f'vec4({e.color[0]}, {e.color[1]}, {e.color[2]}, {e.color[3]})' for e in elems)
|
||||
cols_entries += f', vec4({elems[-1].color[0]}, {elems[-1].color[1]}, {elems[-1].color[2]}, {elems[-1].color[3]})'
|
||||
state.curshader.add_const("vec4", cols_var, cols_entries, array_size=len(elems) + 1)
|
||||
|
||||
fac_var = c.node_name(node.name) + '_fac' + state.get_parser_pass_suffix()
|
||||
fac_var = c.node_uid(node) + '_fac' + state.get_parser_pass_suffix()
|
||||
state.curshader.write(f'float {fac_var} = {fac};')
|
||||
|
||||
index = '0'
|
||||
for i in range(1, len(elems)):
|
||||
index += f' + ({fac_var} > {elems[i].position} ? 1 : 0)'
|
||||
|
||||
index_var = c.node_name(node.name) + '_i' + state.get_parser_pass_suffix()
|
||||
index_var = c.node_uid(node) + '_i' + state.get_parser_pass_suffix()
|
||||
state.curshader.write(f'int {index_var} = {index};')
|
||||
|
||||
if interp == 'CONSTANT':
|
||||
state.curshader.write(f'vec4 {ramp_store} = {cols_var}[{index_var}];')
|
||||
else:
|
||||
facs_var = c.node_name(node.name).upper() + '_FACS'
|
||||
facs_var = c.node_uid(node).upper() + '_FACS'
|
||||
if state.current_pass == ParserPass.REGULAR:
|
||||
facs_entries = ', '.join(str(e.position) for e in elems)
|
||||
facs_entries += ', 1.0'
|
||||
@ -369,7 +369,7 @@ if bpy.app.version > (3, 2, 0):
|
||||
def parse_sephsv(node: bpy.types.ShaderNodeSeparateHSV, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr:
|
||||
state.curshader.add_function(c_functions.str_hue_sat)
|
||||
|
||||
hsv_var = c.node_name(node.name) + '_hsv' + state.get_parser_pass_suffix()
|
||||
hsv_var = c.node_uid(node) + '_hsv' + state.get_parser_pass_suffix()
|
||||
if not state.curshader.contains(hsv_var): # Already written if a second output is parsed
|
||||
state.curshader.write(f'const vec3 {hsv_var} = rgb_to_hsv({c.parse_vector_input(node.inputs["Color"])}.rgb);')
|
||||
|
||||
|
||||
@ -109,7 +109,7 @@ def parse_attribute(node: bpy.types.ShaderNodeAttribute, out_socket: bpy.types.N
|
||||
|
||||
def parse_rgb(node: bpy.types.ShaderNodeRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
|
||||
if node.lnx_material_param:
|
||||
nn = 'param_' + c.node_name(node.name)
|
||||
nn = 'param_' + c.node_uid(node)
|
||||
v = out_socket.default_value
|
||||
value = [float(v[0]), float(v[1]), float(v[2])]
|
||||
state.curshader.add_uniform(f'vec3 {nn}', link=f'{node.name}', default_value=value, is_lnx_mat_param=True)
|
||||
@ -163,7 +163,17 @@ def parse_geometry(node: bpy.types.ShaderNodeNewGeometry, out_socket: bpy.types.
|
||||
# Incoming
|
||||
elif out_socket == node.outputs[4]:
|
||||
state.dxdy_varying_input_value = True
|
||||
return 'vVec'
|
||||
if state.curshader.shader_type == 'frag':
|
||||
return 'vVec'
|
||||
state.curshader.add_uniform('vec3 eye', link='_cameraPosition')
|
||||
if state.curshader.shader_type == 'tese':
|
||||
return 'normalize(eye - wposition)'
|
||||
if state.curshader.shader_type == 'vert':
|
||||
state.curshader.add_uniform('mat4 W', link='_worldMatrix')
|
||||
return 'normalize(eye - (W * spos).xyz)'
|
||||
if state.curshader.shader_type == 'tesc':
|
||||
return 'normalize(eye - wposition[gl_InvocationID])'
|
||||
return 'normalize(eye - wposition[0])'
|
||||
# Parametric
|
||||
elif out_socket == node.outputs[5]:
|
||||
state.dxdy_varying_input_value = True
|
||||
@ -450,7 +460,7 @@ def parse_lightpath(node: bpy.types.ShaderNodeLightPath, out_socket: bpy.types.N
|
||||
|
||||
def parse_value(node: bpy.types.ShaderNodeValue, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr:
|
||||
if node.lnx_material_param:
|
||||
nn = 'param_' + c.node_name(node.name)
|
||||
nn = 'param_' + c.node_uid(node)
|
||||
value = node.outputs[0].default_value
|
||||
is_lnx_mat_param = True
|
||||
state.curshader.add_uniform('float {0}'.format(nn), link='{0}'.format(node.name), default_value=value, is_lnx_mat_param=is_lnx_mat_param)
|
||||
@ -463,3 +473,9 @@ def parse_wireframe(node: bpy.types.ShaderNodeWireframe, out_socket: bpy.types.N
|
||||
# node.use_pixel_size
|
||||
# size = c.parse_value_input(node.inputs[0])
|
||||
return '0.0'
|
||||
|
||||
|
||||
def parse_volumeinfo(node: bpy.types.ShaderNodeVolumeInfo, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]:
|
||||
if out_socket == node.outputs['Color']:
|
||||
return 'vec3(0.0)'
|
||||
return '0.0'
|
||||
|
||||
@ -69,12 +69,14 @@ def parse_mixshader(node: bpy.types.ShaderNodeMixShader, out_socket: NodeSocket,
|
||||
c.parse_shader_input(node.inputs[2])
|
||||
return
|
||||
|
||||
prefix = '' if node.inputs['Fac'].is_linked else 'const '
|
||||
fac = c.parse_value_input(node.inputs['Fac'])
|
||||
fac_var = c.node_name(node.name) + '_fac' + state.get_parser_pass_suffix()
|
||||
fac_inv_var = c.node_name(node.name) + '_fac_inv'
|
||||
state.curshader.write('{0}float {1} = clamp({2}, 0.0, 1.0);'.format(prefix, fac_var, fac))
|
||||
state.curshader.write('{0}float {1} = 1.0 - {2};'.format(prefix, fac_inv_var, fac_var))
|
||||
fac_var = c.node_uid(node) + '_fac' + state.get_parser_pass_suffix()
|
||||
fac_inv_var = c.node_uid(node) + '_fac_inv' + state.get_parser_pass_suffix()
|
||||
if not c.is_parsed(fac_var):
|
||||
state.parsed.add(fac_var)
|
||||
prefix = '' if node.inputs['Fac'].is_linked else 'const '
|
||||
fac = c.parse_value_input(node.inputs['Fac'])
|
||||
state.curshader.write('{0}float {1} = clamp({2}, 0.0, 1.0);'.format(prefix, fac_var, fac))
|
||||
state.curshader.write('{0}float {1} = 1.0 - {2};'.format(prefix, fac_inv_var, fac_var))
|
||||
|
||||
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
|
||||
o1 = c.parse_shader_input(node.inputs[1])
|
||||
@ -435,7 +437,7 @@ 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')
|
||||
parametrization = node.parametrization
|
||||
if parametrization == 'COLOR':
|
||||
color_socket = node.inputs.get('Color')
|
||||
if color_socket is not None:
|
||||
@ -457,7 +459,7 @@ if bpy.app.version >= (2, 83, 0):
|
||||
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')
|
||||
model = node.model if bpy.app.version >= (4, 0, 0) else 'CHIANG'
|
||||
if model == 'CHIANG':
|
||||
radial_rough_socket = node.inputs.get('Radial Roughness')
|
||||
if radial_rough_socket is not None:
|
||||
|
||||
@ -136,7 +136,7 @@ def parse_tex_image(node: bpy.types.ShaderNodeTexImage, out_socket: bpy.types.No
|
||||
else:
|
||||
return f'{tex_store}.a'
|
||||
|
||||
tex_name = c.node_name(node.name)
|
||||
tex_name = c.node_uid(node)
|
||||
tex = c.make_texture_from_image_node(node, tex_name)
|
||||
tex_link = None
|
||||
tex_default_file = None
|
||||
@ -259,7 +259,7 @@ def parse_tex_magic(node: bpy.types.ShaderNodeTexMagic, out_socket: bpy.types.No
|
||||
|
||||
return res
|
||||
|
||||
if bpy.app.version < (4, 1, 0):
|
||||
if bpy.app.version < (4, 2, 0):
|
||||
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)
|
||||
|
||||
@ -297,52 +297,65 @@ def parse_tex_noise(node: bpy.types.ShaderNodeTexNoise, out_socket: bpy.types.No
|
||||
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'
|
||||
dimensions = node.noise_dimensions
|
||||
noise_type = node.noise_type if bpy.app.version >= (4, 1, 0) else 'FBM'
|
||||
normalize = 'true' if bpy.app.version < (4, 0, 0) or node.normalize 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')
|
||||
if noise_type == 'MULTIFRACTAL':
|
||||
func_name = 'noise_multi_fractal'
|
||||
fractal_src = c_functions.str_tex_noise_multi_fractal
|
||||
elif noise_type == 'RIDGED_MULTIFRACTAL':
|
||||
func_name = 'noise_ridged_multi_fractal'
|
||||
fractal_src = c_functions.str_tex_noise_ridged_multi_fractal
|
||||
elif noise_type == 'HYBRID_MULTIFRACTAL':
|
||||
func_name = 'noise_hybrid_multi_fractal'
|
||||
fractal_src = c_functions.str_tex_noise_hybrid_multi_fractal
|
||||
elif noise_type == 'HETERO_TERRAIN':
|
||||
func_name = 'noise_hetero_terrain'
|
||||
fractal_src = c_functions.str_tex_noise_hetero_terrain
|
||||
else:
|
||||
func_name = 'noise_fbm'
|
||||
fractal_src = c_functions.str_tex_noise_fbm
|
||||
|
||||
is_color = (out_socket == node.outputs[1]) or (getattr(out_socket, 'name', '') == 'Color')
|
||||
is_color = (out_socket == node.outputs[1]) or (out_socket.name == 'Color')
|
||||
|
||||
if dimensions == '1D':
|
||||
coord_type = 'float'
|
||||
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:
|
||||
res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})"
|
||||
|
||||
off_a = 'random_float_offset(1.0)'
|
||||
off_b = 'random_float_offset(2.0)'
|
||||
elif dimensions == '2D':
|
||||
coord_type = 'vec2'
|
||||
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})"
|
||||
|
||||
off_a = 'random_vec2_offset(2.0)'
|
||||
off_b = 'random_vec2_offset(3.0)'
|
||||
elif dimensions == '4D':
|
||||
coord_type = 'vec4'
|
||||
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})"
|
||||
|
||||
off_a = 'random_vec4_offset(4.0)'
|
||||
off_b = 'random_vec4_offset(5.0)'
|
||||
else:
|
||||
coord_type = 'vec3'
|
||||
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 = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})"
|
||||
off_a = 'random_vec3_offset(3.0)'
|
||||
off_b = 'random_vec3_offset(4.0)'
|
||||
|
||||
state.curshader.add_function('// ' + func_name + ' ' + coord_type + '\n' + fractal_src.replace('@T@', coord_type))
|
||||
|
||||
co_var = c.node_uid(node) + '_co' + state.get_parser_pass_suffix()
|
||||
if not c.is_parsed(co_var):
|
||||
state.parsed.add(co_var)
|
||||
state.curshader.write(f'{coord_type} {co_var} = {dist_expr};')
|
||||
|
||||
fbm_args = f"clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}"
|
||||
if is_color:
|
||||
res = f"vec3({func_name}({co_var}, {fbm_args}), {func_name}(({co_var}) + {off_a}, {fbm_args}), {func_name}(({co_var}) + {off_b}, {fbm_args}))"
|
||||
else:
|
||||
res = f"{func_name}({co_var}, {fbm_args})"
|
||||
|
||||
return res
|
||||
|
||||
@ -513,7 +526,7 @@ def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket:
|
||||
|
||||
state.parsed.add(tex_store)
|
||||
|
||||
tex_name = c.node_name(node.name)
|
||||
tex_name = c.node_uid(node)
|
||||
tex_link = None
|
||||
tex_default_file = None
|
||||
is_lnx_mat_param = None
|
||||
@ -525,8 +538,8 @@ def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket:
|
||||
image,
|
||||
tex_name,
|
||||
c.mat_get_material(),
|
||||
getattr(node, 'interpolation', 'Smart'),
|
||||
getattr(node, 'extension', 'REPEAT')
|
||||
node.interpolation,
|
||||
node.extension if bpy.app.version >= (3, 1, 0) else 'REPEAT'
|
||||
)
|
||||
if tex is None:
|
||||
log.warn(f'Object "{state.tree_name}": missing environment texture image "{node.name}"')
|
||||
@ -670,6 +683,7 @@ def parse_tex_voronoi(node: bpy.types.ShaderNodeTexVoronoi, out_socket: bpy.type
|
||||
outp = 3
|
||||
|
||||
m = 0
|
||||
exp = '0.0'
|
||||
if node.distance == 'MANHATTAN':
|
||||
m = 1
|
||||
elif node.distance == 'CHEBYCHEV':
|
||||
@ -689,10 +703,13 @@ def parse_tex_voronoi(node: bpy.types.ShaderNodeTexVoronoi, out_socket: bpy.type
|
||||
f = 4
|
||||
|
||||
dim = node.voronoi_dimensions
|
||||
normalize = 1 if node.normalize else 0
|
||||
normalize = 1 if bpy.app.version >= (4, 0, 0) and node.normalize else 0
|
||||
|
||||
c.write_procedurals()
|
||||
state.curshader.add_function(getattr(c_functions, f'str_tex_voronoi_{bpy.app.version[0]}'))
|
||||
if bpy.app.version >= (5, 0, 0):
|
||||
state.curshader.add_function(c_functions.str_tex_voronoi_5)
|
||||
else:
|
||||
state.curshader.add_function(c_functions.str_tex_voronoi_4)
|
||||
|
||||
if node.inputs['Vector'].is_linked:
|
||||
co = c.get_vector_input(node, ['Vector'])
|
||||
@ -718,6 +735,7 @@ def parse_tex_voronoi(node: bpy.types.ShaderNodeTexVoronoi, out_socket: bpy.type
|
||||
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('// noise_fbm vec3\n' + c_functions.str_tex_noise_fbm.replace('@T@', 'vec3'))
|
||||
state.curshader.add_function(c_functions.str_tex_wave)
|
||||
|
||||
if node.inputs['Vector'].is_linked:
|
||||
@ -735,9 +753,7 @@ def parse_tex_wave(node: bpy.types.ShaderNodeTexWave, out_socket: bpy.types.Node
|
||||
|
||||
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:
|
||||
if wave_type == 0:
|
||||
wave_dir = dir_map.get(node.bands_direction, 0)
|
||||
else:
|
||||
wave_dir = dir_map.get(node.rings_direction, 0)
|
||||
@ -804,8 +820,8 @@ def parse_tex_white_noise(node: bpy.types.ShaderNodeTexWhiteNoise, out_socket: b
|
||||
|
||||
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')
|
||||
dimensions = node.noise_dimensions
|
||||
is_color = (out_socket == node.outputs[1]) or (out_socket.name == 'Color')
|
||||
|
||||
if dimensions == '1D':
|
||||
if is_color:
|
||||
|
||||
@ -28,7 +28,7 @@ def parse_curvevec(node: bpy.types.ShaderNodeVectorCurve, out_socket: bpy.types.
|
||||
fac = c.get_value_input(node, ['Fac'])
|
||||
vec = c.get_vector_input(node, ['Vector'])
|
||||
curves = node.mapping.curves
|
||||
name = c.node_name(node.name)
|
||||
name = c.node_uid(node)
|
||||
|
||||
res_x = c.vector_curve(name + '0', vec + '.x', curves[0].points)
|
||||
res_y = c.vector_curve(name + '1', vec + '.y', curves[1].points)
|
||||
@ -47,33 +47,39 @@ def parse_bump(node: bpy.types.ShaderNodeBump, out_socket: bpy.types.NodeSocket,
|
||||
# Interpolation strength
|
||||
strength = c.get_value_input(node, ['Strength'])
|
||||
distance = c.get_value_input(node, ['Distance'])
|
||||
filter_width = c.get_value_input(node, ['Filter Width']) if 'Filter Width' in node.inputs else '1.0'
|
||||
height = c.get_value_input(node, ['Height'])
|
||||
|
||||
prev_pass = state.current_pass
|
||||
prev_scale = state.dxdy_scale
|
||||
state.dxdy_scale = filter_width
|
||||
state.current_pass = ParserPass.DX_SCREEN_SPACE
|
||||
height_dx = c.get_value_input(node, ['Height'])
|
||||
state.current_pass = ParserPass.DY_SCREEN_SPACE
|
||||
height_dy = c.get_value_input(node, ['Height'])
|
||||
state.current_pass = ParserPass.REGULAR
|
||||
state.current_pass = prev_pass
|
||||
state.dxdy_scale = prev_scale
|
||||
|
||||
nor = c.get_vector_input(node, ['Normal']) if node.inputs['Normal'].is_linked else 'n'
|
||||
|
||||
if height_dx != height or height_dy != height:
|
||||
tangent = f'{c.dfdx_fine("wposition")} + {nor} * (({height_dx} - {height}) * {distance})'
|
||||
bitangent = f'{c.dfdy_fine("wposition")} + {nor} * (({height_dy} - {height}) * {distance})'
|
||||
|
||||
# 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({nor}, normalize(cross({bitangent}, {tangent})), {strength}))'
|
||||
else:
|
||||
res = f'normalize(mix({nor}, normalize(cross({tangent}, {bitangent})), {strength}))'
|
||||
|
||||
name = c.node_uid(node)
|
||||
if '__' in name:
|
||||
name = name.replace('_', '_x')
|
||||
sfx = state.get_parser_pass_suffix()
|
||||
invert = '-1.0' if node.invert else '1.0'
|
||||
w = state.curshader.write
|
||||
w(f'vec3 {name}_N{sfx} = normalize({nor});')
|
||||
w(f'float {name}_dist{sfx} = ({distance}) * (gl_FrontFacing ? {invert} : -({invert}));')
|
||||
w(f'vec3 {name}_dPdx{sfx} = {c.dfdx_fine("wposition")};')
|
||||
w(f'vec3 {name}_dPdy{sfx} = {c.dfdy_fine("wposition")};')
|
||||
w(f'vec3 {name}_Rx{sfx} = cross({name}_dPdy{sfx}, {name}_N{sfx});')
|
||||
w(f'vec3 {name}_Ry{sfx} = cross({name}_N{sfx}, {name}_dPdx{sfx});')
|
||||
w(f'float {name}_det{sfx} = dot({name}_dPdx{sfx}, {name}_Rx{sfx});')
|
||||
w(f'vec2 {name}_dHd{sfx} = vec2({height_dx}, {height_dy}) - vec2({height});')
|
||||
w(f'vec3 {name}_surfgrad{sfx} = {name}_dHd{sfx}.x * {name}_Rx{sfx} + {name}_dHd{sfx}.y * {name}_Ry{sfx};')
|
||||
w(f'vec3 {name}_res{sfx} = normalize(({filter_width}) * abs({name}_det{sfx}) * {name}_N{sfx} - {name}_dist{sfx} * sign({name}_det{sfx}) * {name}_surfgrad{sfx});')
|
||||
res = f'normalize(mix({name}_N{sfx}, {name}_res{sfx}, max({strength}, 0.0)))'
|
||||
else:
|
||||
res = nor
|
||||
|
||||
@ -96,7 +102,7 @@ def parse_mapping(node: bpy.types.ShaderNodeMapping, out_socket: bpy.types.NodeS
|
||||
out = f"({out} - {location})"
|
||||
|
||||
if input_rotation.is_linked or any(v != 0.0 for v in input_rotation.default_value):
|
||||
var_name = c.node_name(node.name) + "_rotation" + state.get_parser_pass_suffix()
|
||||
var_name = c.node_uid(node) + "_rotation" + state.get_parser_pass_suffix()
|
||||
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);")
|
||||
@ -110,7 +116,7 @@ def parse_mapping(node: bpy.types.ShaderNodeMapping, out_socket: bpy.types.NodeS
|
||||
out = f"({out} * {scale})"
|
||||
|
||||
if input_rotation.is_linked or any(v != 0.0 for v in input_rotation.default_value):
|
||||
var_name = c.node_name(node.name) + "_rotation" + state.get_parser_pass_suffix()
|
||||
var_name = c.node_uid(node) + "_rotation" + state.get_parser_pass_suffix()
|
||||
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);")
|
||||
@ -203,7 +209,8 @@ def parse_displacement(node: bpy.types.ShaderNodeDisplacement, out_socket: bpy.t
|
||||
nor = 'wnormal'
|
||||
|
||||
if node.space == 'OBJECT':
|
||||
nor = f'(inverse(mat3(W)) * {nor})'
|
||||
state.curshader.add_uniform('mat4 W', link='_worldMatrix')
|
||||
nor = f'(transpose(mat3(W)) * {nor})'
|
||||
|
||||
disp = f'normalize({nor}) * (vec3({height}) - vec3({midlevel})) * {scale}'
|
||||
|
||||
@ -221,12 +228,14 @@ def parse_vector_displacement(node: bpy.types.ShaderNodeVectorDisplacement, out_
|
||||
offset = f'(({vector} - vec3({midlevel})) * {scale})'
|
||||
|
||||
if node.space == 'TANGENT':
|
||||
state.curshader.add_uniform('mat4 W', link='_worldMatrix')
|
||||
t_obj = 'normalize(inverse(mat3(W)) * wtangent)'
|
||||
n_obj = 'normalize(inverse(mat3(W)) * wnormal)'
|
||||
n_obj = 'normalize(transpose(mat3(W)) * wnormal)'
|
||||
b_obj = f'normalize(cross({n_obj}, {t_obj}))'
|
||||
disp = f'({t_obj} * {offset}.x + {n_obj} * {offset}.y + {b_obj} * {offset}.z)'
|
||||
disp = f'({t_obj} * {offset}.x + {b_obj} * {offset}.y + {n_obj} * {offset}.z)'
|
||||
return f'(vec4(W * vec4({disp}, 0.0)).xyz)'
|
||||
elif node.space == 'OBJECT':
|
||||
state.curshader.add_uniform('mat4 W', link='_worldMatrix')
|
||||
return f'(vec4(W * vec4({offset}, 0.0)).xyz)'
|
||||
else:
|
||||
return offset
|
||||
@ -262,3 +271,9 @@ def parse_vectorrotate(node: bpy.types.ShaderNodeVectorRotate, out_socket: bpy.t
|
||||
return f'vec3( euler_to_mat3({rot_val}) * ({input_vector} - {input_center}) + {input_center})'
|
||||
|
||||
return f'vec3(0.0, 0.0, 0.0)'
|
||||
|
||||
|
||||
def parse_bevel(node: bpy.types.ShaderNodeBevel, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
|
||||
if node.inputs['Normal'].is_linked:
|
||||
return c.get_vector_input(node, ['Normal'])
|
||||
return 'n' if state.curshader.shader_type == 'frag' else 'wnormal'
|
||||
|
||||
Reference in New Issue
Block a user