forked from LeenkxTeam/LNXSDK
Repe [T3DU] Update - 31f26d171bba0355ce2a77031e3aad4c64dbc7e9
This commit is contained in:
@ -283,6 +283,11 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types.
|
||||
|
||||
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
|
||||
|
||||
#for debugDraw color in deferred
|
||||
if '_EmissionShaded' not in wrd.world_defs:
|
||||
wrd.world_defs += '_EmissionShaded'
|
||||
lnx.assets.add_khafile_def('rp_gbuffer_emission')
|
||||
|
||||
# Surface
|
||||
if parse_surface or parse_opacity:
|
||||
state.parents = []
|
||||
@ -559,6 +564,8 @@ def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str:
|
||||
'TEX_SKY',
|
||||
'TEX_VORONOI',
|
||||
'TEX_WAVE',
|
||||
'TEX_GABOR',
|
||||
'TEX_WHITE_NOISE',
|
||||
'VERTEX_COLOR',
|
||||
'BRIGHTCONTRAST',
|
||||
'GAMMA',
|
||||
@ -591,6 +598,7 @@ def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str:
|
||||
'COMBXYZ',
|
||||
'VECT_MATH',
|
||||
'DISPLACEMENT',
|
||||
'VECTOR_DISPLACEMENT',
|
||||
'VECTOR_ROTATE',
|
||||
)
|
||||
|
||||
@ -625,7 +633,7 @@ def get_value_input(node: bpy.types.Node, socket_names: Tuple[str, ...]) -> floa
|
||||
return '1.0'
|
||||
|
||||
|
||||
def parse_normal_map_color_input(inp, strength_input=None):
|
||||
def parse_normal_map_color_input(inp, strength_input=None, space='TANGENT'):
|
||||
frag = state.frag
|
||||
|
||||
if state.basecol_only or not inp.is_linked or state.normal_parsed:
|
||||
@ -633,20 +641,40 @@ def parse_normal_map_color_input(inp, strength_input=None):
|
||||
|
||||
state.normal_parsed = True
|
||||
frag.write_normal += 1
|
||||
if not get_lnx_export_tangents() or mat_get_material().lnx_decal: # Compute TBN matrix
|
||||
frag.write('vec3 texn = ({0}) * 2.0 - 1.0;'.format(parse_vector_input(inp)))
|
||||
frag.write('texn.y = -texn.y;')
|
||||
frag.add_include('std/normals.glsl')
|
||||
frag.write('mat3 TBN = cotangentFrame(n, -vVec, texCoord);')
|
||||
frag.write('n = TBN * normalize(texn);')
|
||||
else:
|
||||
frag.write('n = ({0}) * 2.0 - 1.0;'.format(parse_vector_input(inp)))
|
||||
if strength_input is not None:
|
||||
strength = parse_value_input(strength_input)
|
||||
|
||||
color_val = parse_vector_input(inp)
|
||||
strength = parse_value_input(strength_input) if strength_input is not None else '1.0'
|
||||
|
||||
if space == 'TANGENT':
|
||||
if not get_lnx_export_tangents() or mat_get_material().lnx_decal:
|
||||
frag.write(f'vec3 texn = ({color_val}) * 2.0 - 1.0;')
|
||||
frag.write('texn.y = -texn.y;')
|
||||
frag.add_include('std/normals.glsl')
|
||||
frag.write('mat3 TBN = cotangentFrame(n, -vVec, texCoord);')
|
||||
if strength != '1.0':
|
||||
frag.write('n.xy *= {0};'.format(strength))
|
||||
frag.write('n = normalize(TBN * n);')
|
||||
state.con.add_elem('tang', 'short4norm')
|
||||
frag.write(f'texn.xy *= {strength};')
|
||||
frag.write('n = TBN * normalize(texn);')
|
||||
else:
|
||||
frag.write(f'vec3 texn = ({color_val}) * 2.0 - 1.0;')
|
||||
if strength != '1.0':
|
||||
frag.write(f'texn.xy *= {strength};')
|
||||
frag.write('n = normalize(TBN * texn);')
|
||||
state.con.add_elem('tang', 'short4norm')
|
||||
|
||||
elif space in ['OBJECT', 'BLENDER_OBJECT']:
|
||||
frag.add_uniform('mat3 N', '_normalMatrix')
|
||||
frag.write(f'vec3 objn = ({color_val}) * 2.0 - 1.0;')
|
||||
if strength != '1.0':
|
||||
frag.write(f'objn = mix(vec3(0.0, 0.0, 1.0), objn, {strength});')
|
||||
frag.write('n = normalize(N * objn);')
|
||||
|
||||
elif space in ['WORLD', 'BLENDER_WORLD']:
|
||||
frag.write(f'vec3 worldn = ({color_val}) * 2.0 - 1.0;')
|
||||
if strength != '1.0':
|
||||
frag.write(f'n = normalize(mix(n, worldn, {strength}));')
|
||||
else:
|
||||
frag.write('n = normalize(worldn);')
|
||||
|
||||
frag.write_normal -= 1
|
||||
|
||||
|
||||
@ -700,6 +728,8 @@ def parse_value(node, socket):
|
||||
'TEX_POINTDENSITY',
|
||||
'TEX_VORONOI',
|
||||
'TEX_WAVE',
|
||||
'TEX_GABOR',
|
||||
'TEX_WHITE_NOISE',
|
||||
'LIGHT_FALLOFF',
|
||||
'NORMAL',
|
||||
'CLAMP',
|
||||
@ -828,7 +858,6 @@ def write_result(link: bpy.types.NodeLink) -> Optional[str]:
|
||||
|
||||
def write_procedurals():
|
||||
if state.curshader not in state.procedurals_written:
|
||||
state.curshader.add_function(c_functions.str_tex_proc)
|
||||
state.procedurals_written.add(state.curshader)
|
||||
|
||||
|
||||
@ -869,14 +898,18 @@ def texture_store(node, tex, tex_name, to_linear=False, unpremultiply=False, tex
|
||||
state.con.add_elem('tex', 'short2norm')
|
||||
curshader.add_uniform('sampler2D {0}'.format(tex_name), link=tex_link, default_value=default_value, is_lnx_mat_param=is_lnx_mat_param)
|
||||
triplanar = node.projection == 'BOX'
|
||||
spherical = node.projection == 'SPHERE'
|
||||
tube = node.projection == 'TUBE'
|
||||
if node.inputs[0].is_linked:
|
||||
uv_name = parse_vector_input(node.inputs[0])
|
||||
if triplanar:
|
||||
uv_name = 'vec3({0}.x, 1.0 - {0}.y, {0}.z)'.format(uv_name)
|
||||
uv_name = 'vec3({0}.x, {0}.y, 1.0 - {0}.z)'.format(uv_name)
|
||||
elif spherical or tube:
|
||||
uv_name = 'vec3({0}.x, {0}.y, {0}.z)'.format(uv_name)
|
||||
else:
|
||||
uv_name = 'vec2({0}.x, 1.0 - {0}.y)'.format(uv_name)
|
||||
else:
|
||||
uv_name = 'vec3(texCoord.xy, 0.0)' if triplanar else 'texCoord'
|
||||
uv_name = 'vec3(texCoord.xy, 0.0)' if triplanar or spherical or tube else 'texCoord'
|
||||
if triplanar:
|
||||
if not curshader.has_include('std/mapping.glsl'):
|
||||
curshader.add_include('std/mapping.glsl')
|
||||
@ -884,7 +917,16 @@ def texture_store(node, tex, tex_name, to_linear=False, unpremultiply=False, tex
|
||||
nor = 'TBN[2]'
|
||||
else:
|
||||
nor = 'n'
|
||||
curshader.write('vec4 {0} = vec4(triplanarMapping({1}, {2}, {3}), 0.0);'.format(tex_store, tex_name, nor, uv_name))
|
||||
blend = getattr(node, 'projection_blend', 0.0)
|
||||
curshader.write('vec4 {0} = boxProjection({1}, {2}, {3}, clamp({4}, 0.0, 1.0));'.format(tex_store, tex_name, nor, uv_name, blend))
|
||||
elif spherical:
|
||||
if not curshader.has_include('std/mapping.glsl'):
|
||||
curshader.add_include('std/mapping.glsl')
|
||||
curshader.write('vec4 {0} = texture({1}, sphericalMapping({2}));'.format(tex_store, tex_name, uv_name))
|
||||
elif tube:
|
||||
if not curshader.has_include('std/mapping.glsl'):
|
||||
curshader.add_include('std/mapping.glsl')
|
||||
curshader.write('vec4 {0} = texture({1}, tubeMapping({2}));'.format(tex_store, tex_name, uv_name))
|
||||
else:
|
||||
if mat_state.texture_grad:
|
||||
curshader.write('vec4 {0} = textureGrad({1}, {2}.xy, g2.xy, g2.zw);'.format(tex_store, tex_name, uv_name))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -119,37 +119,35 @@ def _parse_mixrgb(node: bpy.types.ShaderNodeMixRGB, out_socket: bpy.types.NodeSo
|
||||
elif blend == 'SUBTRACT':
|
||||
out_col = 'mix({0}, {0} - {1}, {2})'.format(col1, col2, fac)
|
||||
elif blend == 'SCREEN':
|
||||
out_col = '(vec3(1.0) - (vec3(1.0 - {2}) + {2} * (vec3(1.0) - {1})) * (vec3(1.0) - {0}))'.format(col1, col2, fac)
|
||||
out_col = 'mix({0}, vec3(1.0) - (vec3(1.0) - {0}) * (vec3(1.0) - {1}), {2})'.format(col1, col2, fac)
|
||||
elif blend == 'DIVIDE':
|
||||
out_col = '(vec3((1.0 - {2}) * {0} + {2} * {0} / {1}))'.format(col1, col2, fac)
|
||||
out_col = 'mix({0}, {0} / max({1}, vec3(0.000001)), {2})'.format(col1, col2, fac)
|
||||
elif blend == 'DIFFERENCE':
|
||||
out_col = 'mix({0}, abs({0} - {1}), {2})'.format(col1, col2, fac)
|
||||
elif blend == 'DARKEN':
|
||||
out_col = 'min({0}, {1} * {2})'.format(col1, col2, fac)
|
||||
out_col = 'mix({0}, min({0}, {1}), {2})'.format(col1, col2, fac)
|
||||
elif blend == 'LIGHTEN':
|
||||
out_col = 'max({0}, {1} * {2})'.format(col1, col2, fac)
|
||||
out_col = 'mix({0}, max({0}, {1}), {2})'.format(col1, col2, fac)
|
||||
elif blend == 'OVERLAY':
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix
|
||||
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)
|
||||
elif blend == 'DODGE':
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix
|
||||
dodge = '{0} / max(vec3(1.0) - {1}, vec3(0.000001))'.format(col1, col2)
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, dodge, fac)
|
||||
elif blend == 'BURN':
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix
|
||||
elif blend == 'HUE':
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix
|
||||
elif blend == 'SATURATION':
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix
|
||||
elif blend == 'VALUE':
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix
|
||||
elif blend == 'COLOR':
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix
|
||||
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)
|
||||
elif blend == 'SOFT_LIGHT':
|
||||
out_col = '((1.0 - {2}) * {0} + {2} * ((vec3(1.0) - {0}) * {1} * {0} + {0} * (vec3(1.0) - (vec3(1.0) - {1}) * (vec3(1.0) - {0}))))'.format(col1, col2, fac)
|
||||
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)
|
||||
elif blend == 'LINEAR_LIGHT':
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix
|
||||
# out_col = '({0} + {2} * (2.0 * ({1} - vec3(0.5))))'.format(col1, col2, fac_var)
|
||||
linear = '{0} + 2.0 * {1} - vec3(1.0)'.format(col1, col2)
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, linear, fac)
|
||||
elif blend in ['HUE', 'SATURATION', 'COLOR', 'VALUE']:
|
||||
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac)
|
||||
else:
|
||||
log.warn(f'MixRGB node: unsupported blend type {node.blend_type}.')
|
||||
return col1
|
||||
out_col = col1
|
||||
|
||||
if node.clamp_result:
|
||||
return 'clamp({0}, vec3(0.0), vec3(1.0))'.format(out_col)
|
||||
|
||||
@ -83,62 +83,58 @@ def parse_clamp(node: bpy.types.ShaderNodeClamp, out_socket: bpy.types.NodeSocke
|
||||
|
||||
|
||||
def parse_valtorgb(node: bpy.types.ShaderNodeValToRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]:
|
||||
# Alpha (TODO: make ColorRamp calculation vec4-based and split afterwards)
|
||||
if out_socket == node.outputs[1]:
|
||||
return '1.0'
|
||||
|
||||
input_fac: bpy.types.NodeSocket = node.inputs[0]
|
||||
|
||||
fac: str = c.parse_value_input(input_fac) if input_fac.is_linked else c.to_vec1(input_fac.default_value)
|
||||
interp = node.color_ramp.interpolation
|
||||
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()
|
||||
|
||||
if c.is_parsed(ramp_store):
|
||||
return f'{ramp_store}.rgb' if use_color_out else f'{ramp_store}.a'
|
||||
|
||||
if len(elems) == 1:
|
||||
return c.to_vec3(elems[0].color)
|
||||
res = f'vec4({elems[0].color[0]}, {elems[0].color[1]}, {elems[0].color[2]}, {elems[0].color[3]})'
|
||||
state.curshader.write(f'vec4 {ramp_store} = {res};')
|
||||
state.parsed.add(ramp_store)
|
||||
return f'{ramp_store}.rgb' if use_color_out else f'{ramp_store}.a'
|
||||
|
||||
# Write color array
|
||||
# The last entry is included twice so that the interpolation
|
||||
# between indices works (no out of bounds error)
|
||||
cols_var = c.node_name(node.name).upper() + '_COLS'
|
||||
|
||||
if state.current_pass == ParserPass.REGULAR:
|
||||
cols_entries = ', '.join(f'vec3({elem.color[0]}, {elem.color[1]}, {elem.color[2]})' for elem in elems)
|
||||
cols_entries += f', vec3({elems[len(elems) - 1].color[0]}, {elems[len(elems) - 1].color[1]}, {elems[len(elems) - 1].color[2]})'
|
||||
state.curshader.add_const("vec3", cols_var, cols_entries, array_size=len(elems) + 1)
|
||||
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()
|
||||
state.curshader.write(f'float {fac_var} = {fac};')
|
||||
|
||||
# Get index of the nearest left element relative to the factor
|
||||
index = '0 + '
|
||||
index += ' + '.join([f'(({fac_var} > {elems[i].position}) ? 1 : 0)' for i in range(1, len(elems))])
|
||||
index = '0'
|
||||
for i in range(1, len(elems)):
|
||||
index += f' + ({fac_var} > {elems[i].position} ? 1 : 0)'
|
||||
|
||||
# Write index
|
||||
index_var = c.node_name(node.name) + '_i' + state.get_parser_pass_suffix()
|
||||
state.curshader.write(f'int {index_var} = {index};')
|
||||
|
||||
if interp == 'CONSTANT':
|
||||
return f'{cols_var}[{index_var}]'
|
||||
|
||||
# Linear interpolation
|
||||
state.curshader.write(f'vec4 {ramp_store} = {cols_var}[{index_var}];')
|
||||
else:
|
||||
# Write factor array
|
||||
facs_var = c.node_name(node.name).upper() + '_FACS'
|
||||
if state.current_pass == ParserPass.REGULAR:
|
||||
facs_entries = ', '.join(str(elem.position) for elem in elems)
|
||||
# Add one more entry at the rightmost position so that the
|
||||
# interpolation between indices works (no out of bounds error)
|
||||
facs_entries = ', '.join(str(e.position) for e in elems)
|
||||
facs_entries += ', 1.0'
|
||||
state.curshader.add_const("float", facs_var, facs_entries, array_size=len(elems) + 1)
|
||||
|
||||
# Mix color
|
||||
prev_stop_fac = f'{facs_var}[{index_var}]'
|
||||
next_stop_fac = f'{facs_var}[{index_var} + 1]'
|
||||
prev_stop_col = f'{cols_var}[{index_var}]'
|
||||
next_stop_col = f'{cols_var}[{index_var} + 1]'
|
||||
rel_pos = f'({fac_var} - {prev_stop_fac}) * (1.0 / ({next_stop_fac} - {prev_stop_fac}))'
|
||||
f_a = f'{facs_var}[{index_var}]'
|
||||
f_b = f'{facs_var}[{index_var} + 1]'
|
||||
c_a = f'{cols_var}[{index_var}]'
|
||||
c_b = f'{cols_var}[{index_var} + 1]'
|
||||
|
||||
return f'mix({prev_stop_col}, {next_stop_col}, max({rel_pos}, 0.0))'
|
||||
rel_pos = f'({fac_var} - {f_a}) * (1.0 / ({f_b} - {f_a}))'
|
||||
state.curshader.write(f'vec4 {ramp_store} = mix({c_a}, {c_b}, max({rel_pos}, 0.0));')
|
||||
|
||||
state.parsed.add(ramp_store)
|
||||
return f'{ramp_store}.rgb' if use_color_out else f'{ramp_store}.a'
|
||||
|
||||
if bpy.app.version > (3, 2, 0):
|
||||
def parse_combine_color(node: bpy.types.ShaderNodeCombineColor, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr:
|
||||
@ -314,8 +310,9 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket,
|
||||
out_val = 'trunc({0})'.format(val1)
|
||||
elif op == 'FRACT':
|
||||
out_val = 'fract({0})'.format(val1)
|
||||
elif op == 'MODULO':
|
||||
# out_val = 'float({0} % {1})'.format(val1, val2)
|
||||
elif op == 'MODULO': #TRUNCATED MODULO
|
||||
out_val = '({0} - {1} * trunc({0} / {1}))'.format(val1, val2)
|
||||
elif op == 'FLOORED_MODULO':
|
||||
out_val = 'mod({0}, {1})'.format(val1, val2)
|
||||
elif op == 'WRAP':
|
||||
val3 = c.parse_value_input(node.inputs[2])
|
||||
|
||||
@ -37,7 +37,7 @@ def parse_attribute(node: bpy.types.ShaderNodeAttribute, out_socket: bpy.types.N
|
||||
return c.cast_value('time', from_type='float', to_type=out_type)
|
||||
|
||||
# UV maps (higher priority) and vertex colors
|
||||
if node.attribute_type == 'GEOMETRY':
|
||||
if node.attribute_type == 'GEOMETRY' and state.context == ParserContext.OBJECT:
|
||||
|
||||
# Alpha output. Leenkx doesn't support vertex colors with alpha
|
||||
# values yet and UV maps don't have an alpha channel
|
||||
@ -159,7 +159,7 @@ def parse_geometry(node: bpy.types.ShaderNodeNewGeometry, out_socket: bpy.types.
|
||||
# True Normal
|
||||
elif out_socket == node.outputs[3]:
|
||||
state.dxdy_varying_input_value = True
|
||||
return 'n' if state.curshader.shader_type == 'frag' else 'wnormal'
|
||||
return 'normalize(cross(dFdy(wposition), dFdx(wposition)))' if state.curshader.shader_type == 'frag' else 'wnormal'
|
||||
# Incoming
|
||||
elif out_socket == node.outputs[4]:
|
||||
state.dxdy_varying_input_value = True
|
||||
@ -173,10 +173,18 @@ def parse_geometry(node: bpy.types.ShaderNodeNewGeometry, out_socket: bpy.types.
|
||||
return '(1.0 - float(gl_FrontFacing))' if state.context == ParserContext.OBJECT else '0.0'
|
||||
# Pointiness
|
||||
elif out_socket == node.outputs[7]:
|
||||
return '0.0'
|
||||
state.dxdy_varying_input_value = True
|
||||
if state.curshader.shader_type == 'frag':
|
||||
return 'length(cross(n, normalize(cross(dFdy(wposition), dFdx(wposition)))))'
|
||||
else:
|
||||
return '0.0'
|
||||
# Random Per Island
|
||||
elif out_socket == node.outputs[8]:
|
||||
return '0.0'
|
||||
state.dxdy_varying_input_value = True
|
||||
if state.curshader.shader_type == 'frag':
|
||||
return '((sin(mposition.x * 5.12 + sin(mposition.y * 8.63)) * sin(mposition.y * 6.41 + sin(mposition.z * 9.15)) * sin(mposition.z * 7.34 + sin(mposition.x * 4.82))) * 0.5 + 0.5)'
|
||||
else:
|
||||
return '0.0'
|
||||
|
||||
|
||||
def parse_hairinfo(node: bpy.types.ShaderNodeHairInfo, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]:
|
||||
@ -196,7 +204,8 @@ def parse_objectinfo(node: bpy.types.ShaderNodeObjectInfo, out_socket: bpy.types
|
||||
if out_socket == node.outputs[0]:
|
||||
if state.context == ParserContext.WORLD:
|
||||
return c.to_vec3((0.0, 0.0, 0.0))
|
||||
return 'wposition'
|
||||
state.curshader.add_uniform('mat4 W', link='_worldMatrix')
|
||||
return 'vec3(W[3][0], W[3][1], W[3][2])'
|
||||
|
||||
# Color
|
||||
elif out_socket == node.outputs[1]:
|
||||
@ -207,15 +216,35 @@ def parse_objectinfo(node: bpy.types.ShaderNodeObjectInfo, out_socket: bpy.types
|
||||
return c.to_vec3((0.0, 0.0, 0.0))
|
||||
return c.to_vec3([background_node.inputs[1].default_value] * 3)
|
||||
|
||||
# TODO: Implement object color in Iron
|
||||
# state.curshader.add_uniform('vec3 objectInfoColor', link='_objectInfoColor')
|
||||
# return 'objectInfoColor'
|
||||
return c.to_vec3((1.0, 1.0, 1.0))
|
||||
if mat_state.uses_instancing:
|
||||
state.vert.add_out(f'vec3 irandc')
|
||||
state.frag.add_in(f'vec3 irandc')
|
||||
state.vert.write("irandc = vec3(" \
|
||||
"fract(sin(fract(sin(gl_InstanceID) * 43758.5453) * 12.9898 + 78.233) * 43758.5453), " \
|
||||
"fract(sin(fract(sin(gl_InstanceID) * 43758.5453) * 39.3456 + 12.123) * 43758.5453), " \
|
||||
"fract(sin(fract(sin(gl_InstanceID) * 43758.5453) * 78.2330 + 45.678) * 43758.5453)" \
|
||||
");"
|
||||
)
|
||||
return 'irandc'
|
||||
|
||||
state.curshader.add_uniform('float objRandom', link='_objectInfoRandom')
|
||||
return "vec3(" \
|
||||
"fract(sin(objRandom * 12.9898 + 78.233) * 43758.5453), " \
|
||||
"fract(sin(objRandom * 39.3456 + 12.123) * 43758.5453), " \
|
||||
"fract(sin(objRandom * 78.2330 + 45.678) * 43758.5453)" \
|
||||
")"
|
||||
#return c.to_vec3((1.0, 1.0, 1.0))
|
||||
|
||||
# Alpha
|
||||
elif out_socket == node.outputs[2]:
|
||||
# TODO, see color output above
|
||||
return '0.0'
|
||||
if mat_state.uses_instancing:
|
||||
state.vert.add_out(f'float iranda')
|
||||
state.frag.add_in(f'float iranda')
|
||||
state.vert.write("iranda = fract(sin(fract(sin(gl_InstanceID) * 43758.5453) * 12.9898) * 43758.5453);")
|
||||
return 'iranda'
|
||||
|
||||
state.curshader.add_uniform('float objAlphaRandom', link='_objectInfoRandom')
|
||||
return 'fract(sin(objAlphaRandom * 12.9898) * 43758.5453)'
|
||||
|
||||
# Object Index
|
||||
elif out_socket == node.outputs[3]:
|
||||
@ -316,7 +345,8 @@ def parse_texcoord(node: bpy.types.ShaderNodeTexCoord, out_socket: bpy.types.Nod
|
||||
state.dxdy_varying_input_value = True
|
||||
return 'mposition'
|
||||
elif out_socket == node.outputs[4]: # Camera
|
||||
return 'vec3(0.0)' # 'vposition'
|
||||
state.curshader.add_uniform('mat4 V', link='_viewMatrix')
|
||||
return '(V * vec4(wposition, 1.0)).xyz'
|
||||
elif out_socket == node.outputs[5]: # Window
|
||||
# TODO: Don't use gl_FragCoord here, it uses different axes on different graphics APIs
|
||||
state.frag.add_uniform('vec2 screenSize', link='_screenSize')
|
||||
@ -326,7 +356,8 @@ def parse_texcoord(node: bpy.types.ShaderNodeTexCoord, out_socket: bpy.types.Nod
|
||||
if state.context == ParserContext.WORLD:
|
||||
state.dxdy_varying_input_value = True
|
||||
return 'n'
|
||||
return 'vec3(0.0)'
|
||||
state.curshader.add_uniform('vec3 cameraPos', link='_cameraPosition')
|
||||
return 'reflect(normalize(wposition - cameraPos), n)'
|
||||
|
||||
|
||||
def parse_uvmap(node: bpy.types.ShaderNodeUVMap, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
|
||||
@ -352,10 +383,11 @@ def parse_uvmap(node: bpy.types.ShaderNodeUVMap, out_socket: bpy.types.NodeSocke
|
||||
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]))
|
||||
dotnv = 'clamp(dot({0}, vVec), 0.0, 1.0)'.format(c.parse_vector_input(node.inputs[1]))
|
||||
else:
|
||||
dotnv = 'dotNV'
|
||||
dotnv = 'clamp(dotNV, 0.0, 1.0)'
|
||||
|
||||
state.dxdy_varying_input_value = True
|
||||
return 'fresnel({0}, {1})'.format(ior, dotnv)
|
||||
@ -363,20 +395,24 @@ def parse_fresnel(node: bpy.types.ShaderNodeFresnel, out_socket: bpy.types.NodeS
|
||||
|
||||
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]))
|
||||
dotnv = 'clamp(dot({0}, vVec), 0.0, 1.0)'.format(c.parse_vector_input(node.inputs[1]))
|
||||
else:
|
||||
dotnv = 'dotNV'
|
||||
dotnv = 'clamp(dotNV, 0.0, 1.0)'
|
||||
|
||||
state.dxdy_varying_input_value = True
|
||||
|
||||
# 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)
|
||||
return 'fresnel((1.0 + sqrt(clamp({0}, 0.0, 0.99))) / max(1.0 - sqrt(clamp({0}, 0.0, 0.99)), 1e-5), {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)
|
||||
return '({0} < 0.5) ? 1.0 - pow({1}, 2.0 * {0}) : 1.0 - pow({1}, 0.5 / max(1.0 - {0}, 1e-5))'.format(blend, dotnv)
|
||||
|
||||
return '0.0'
|
||||
|
||||
|
||||
def parse_lightpath(node: bpy.types.ShaderNodeLightPath, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr:
|
||||
|
||||
@ -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})'
|
||||
|
||||
@ -29,11 +29,14 @@ def parse_curvevec(node: bpy.types.ShaderNodeVectorCurve, out_socket: bpy.types.
|
||||
vec = c.get_vector_input(node, ['Vector'])
|
||||
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)
|
||||
|
||||
res_x = c.vector_curve(name + '0', vec + '.x', curves[0].points)
|
||||
res_y = c.vector_curve(name + '1', vec + '.y', curves[1].points)
|
||||
res_z = c.vector_curve(name + '2', vec + '.z', curves[2].points)
|
||||
|
||||
res_vec = f'vec3({res_x}, {res_y}, {res_z})'
|
||||
|
||||
return f'mix({vec}, {res_vec}, {fac})'
|
||||
|
||||
|
||||
def parse_bump(node: bpy.types.ShaderNodeBump, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
|
||||
@ -43,9 +46,8 @@ 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'])
|
||||
distance = c.get_value_input(node, ['Distance'])
|
||||
height = c.get_value_input(node, ['Height'])
|
||||
# normal = c.get_vector_input(node, ['Normal'])
|
||||
|
||||
state.current_pass = ParserPass.DX_SCREEN_SPACE
|
||||
height_dx = c.get_value_input(node, ['Height'])
|
||||
@ -53,9 +55,11 @@ def parse_bump(node: bpy.types.ShaderNodeBump, out_socket: bpy.types.NodeSocket,
|
||||
height_dy = c.get_value_input(node, ['Height'])
|
||||
state.current_pass = ParserPass.REGULAR
|
||||
|
||||
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")} + n * ({height_dx} - {height})'
|
||||
bitangent = f'{c.dfdy_fine("wposition")} + n * ({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'
|
||||
@ -66,73 +70,58 @@ def parse_bump(node: bpy.types.ShaderNodeBump, out_socket: bpy.types.NodeSocket,
|
||||
if bitangent_first:
|
||||
# We need to normalize twice, once for the correct "weight" of the strength,
|
||||
# once for having a normalized output vector (lerping vectors does not preserve magnitude)
|
||||
res = f'normalize(mix(n, normalize(cross({bitangent}, {tangent})), {strength}))'
|
||||
res = f'normalize(mix({nor}, normalize(cross({bitangent}, {tangent})), {strength}))'
|
||||
else:
|
||||
res = f'normalize(mix(n, normalize(cross({tangent}, {bitangent})), {strength}))'
|
||||
res = f'normalize(mix({nor}, normalize(cross({tangent}, {bitangent})), {strength}))'
|
||||
|
||||
else:
|
||||
res = 'n'
|
||||
res = nor
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def parse_mapping(node: bpy.types.ShaderNodeMapping, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
|
||||
# TODO: Add support for "Normal" type
|
||||
# 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 = node.inputs['Vector']
|
||||
input_location = node.inputs['Location']
|
||||
input_rotation = node.inputs['Rotation']
|
||||
input_scale = node.inputs['Scale']
|
||||
|
||||
input_vector: bpy.types.NodeSocket = node.inputs['Vector']
|
||||
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})'
|
||||
if node.vector_type == 'TEXTURE':
|
||||
if input_location.is_linked or any(v != 0.0 for v in input_location.default_value):
|
||||
out = f"({out} - {location})"
|
||||
|
||||
return output
|
||||
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()
|
||||
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);")
|
||||
out = f"({out} * {var_name}Z * {var_name}Y * {var_name}X)"
|
||||
|
||||
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
|
||||
if input_scale.is_linked or any(v != 1.0 for v in input_scale.default_value):
|
||||
out = f"({out} / {scale})"
|
||||
|
||||
out = calc_location(out) if node.vector_type == 'TEXTURE' else calc_scale(out)
|
||||
elif node.vector_type in ['POINT', 'VECTOR', 'NORMAL']:
|
||||
if input_scale.is_linked or any(v != 1.0 for v in input_scale.default_value):
|
||||
out = f"({out} * {scale})"
|
||||
|
||||
if input_rotation.is_linked or input_rotation.default_value != Euler((0, 0, 0)):
|
||||
var_name = c.node_name(node.name) + "_rotation" + state.get_parser_pass_suffix()
|
||||
if node.vector_type == 'TEXTURE':
|
||||
state.curshader.write(f'mat3 {var_name}X = mat3(1.0, 0.0, 0.0, 0.0, cos({rotation}.x), sin({rotation}.x), 0.0, -sin({rotation}.x), cos({rotation}.x));')
|
||||
state.curshader.write(f'mat3 {var_name}Y = mat3(cos({rotation}.y), 0.0, -sin({rotation}.y), 0.0, 1.0, 0.0, sin({rotation}.y), 0.0, cos({rotation}.y));')
|
||||
state.curshader.write(f'mat3 {var_name}Z = mat3(cos({rotation}.z), sin({rotation}.z), 0.0, -sin({rotation}.z), cos({rotation}.z), 0.0, 0.0, 0.0, 1.0);')
|
||||
else:
|
||||
# A little bit redundant, but faster than 12 more multiplications to make it work dynamically
|
||||
state.curshader.write(f'mat3 {var_name}X = mat3(1.0, 0.0, 0.0, 0.0, cos(-{rotation}.x), sin(-{rotation}.x), 0.0, -sin(-{rotation}.x), cos(-{rotation}.x));')
|
||||
state.curshader.write(f'mat3 {var_name}Y = mat3(cos(-{rotation}.y), 0.0, -sin(-{rotation}.y), 0.0, 1.0, 0.0, sin(-{rotation}.y), 0.0, cos(-{rotation}.y));')
|
||||
state.curshader.write(f'mat3 {var_name}Z = mat3(cos(-{rotation}.z), sin(-{rotation}.z), 0.0, -sin(-{rotation}.z), cos(-{rotation}.z), 0.0, 0.0, 0.0, 1.0);')
|
||||
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()
|
||||
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);")
|
||||
out = f"({out} * {var_name}X * {var_name}Y * {var_name}Z)"
|
||||
|
||||
# XYZ-order euler rotation
|
||||
out = f'{out} * {var_name}X * {var_name}Y * {var_name}Z'
|
||||
if node.vector_type == 'POINT':
|
||||
if input_location.is_linked or any(v != 0.0 for v in input_location.default_value):
|
||||
out = f"({out} + {location})"
|
||||
|
||||
out = calc_scale(out) if node.vector_type == 'TEXTURE' else calc_location(out)
|
||||
if node.vector_type == 'NORMAL':
|
||||
out = f"normalize({out})"
|
||||
|
||||
return out
|
||||
|
||||
@ -152,58 +141,124 @@ def parse_normalmap(node: bpy.types.ShaderNodeNormalMap, out_socket: bpy.types.N
|
||||
if state.curshader == state.tese:
|
||||
return c.get_vector_input(node, ["Normal"])
|
||||
else:
|
||||
# TODO:
|
||||
# space = node.space
|
||||
# map = node.uv_map
|
||||
# Color
|
||||
c.parse_normal_map_color_input(node.inputs['Color'], node.inputs['Strength'])
|
||||
c.parse_normal_map_color_input(node.inputs['Color'], node.inputs['Strength'], space=node.space)
|
||||
return 'n'
|
||||
|
||||
|
||||
def parse_vectortransform(node: bpy.types.ShaderNodeVectorTransform, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
|
||||
# TODO:
|
||||
# vector_type = node.vector_type
|
||||
# conv_from = node.convert_from
|
||||
# conv_to = node.convert_to
|
||||
# Pass through
|
||||
return c.get_vector_input(node, ['Vector'])
|
||||
vec = c.get_vector_input(node, ['Vector'])
|
||||
v_type = node.vector_type
|
||||
v_from = node.convert_from
|
||||
v_to = node.convert_to
|
||||
|
||||
if v_from == v_to:
|
||||
return vec
|
||||
|
||||
shader = state.curshader
|
||||
|
||||
if v_from == 'OBJECT' or v_to == 'OBJECT':
|
||||
shader.add_uniform('mat4 W', link='_worldMatrix')
|
||||
shader.add_uniform('mat4 IW', link='_inverseWorldMatrix')
|
||||
if v_from == 'CAMERA' or v_to == 'CAMERA':
|
||||
shader.add_uniform('mat4 V', link='_viewMatrix')
|
||||
shader.add_uniform('mat4 IV', link='_inverseViewMatrix')
|
||||
|
||||
w = '1.0' if v_type == 'POINT' else '0.0'
|
||||
res = f'vec4({vec}, {w})'
|
||||
|
||||
if v_from == 'OBJECT':
|
||||
shader.write('mat4 Wn = W;')
|
||||
shader.write('Wn[0] = normalize(Wn[0]);')
|
||||
shader.write('Wn[1] = normalize(Wn[1]);')
|
||||
shader.write('Wn[2] = normalize(Wn[2]);')
|
||||
res = f'(Wn * {res})'
|
||||
elif v_from == 'CAMERA':
|
||||
res = f'(IV * {res})'
|
||||
|
||||
if v_to == 'OBJECT':
|
||||
shader.write('mat4 IWn = IW;')
|
||||
shader.write('IWn[0] = normalize(IWn[0]);')
|
||||
shader.write('IWn[1] = normalize(IWn[1]);')
|
||||
shader.write('IWn[2] = normalize(IWn[2]);')
|
||||
res = f'(IWn * {res})'
|
||||
elif v_to == 'CAMERA':
|
||||
res = f'(V * {res})'
|
||||
|
||||
out = f'({res}).xyz'
|
||||
|
||||
if v_type == 'NORMAL':
|
||||
out = f'normalize({out})'
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def parse_displacement(node: bpy.types.ShaderNodeDisplacement, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
|
||||
# TODO:
|
||||
# space = node.space
|
||||
height = c.get_value_input(node, ['Height'])
|
||||
midlevel = c.get_value_input(node, ['Midlevel'])
|
||||
scale = c.get_value_input(node, ['Scale'])
|
||||
nor = c.get_vector_input(node, ['Normal'])
|
||||
return f'(vec3({height}) * {scale})'
|
||||
|
||||
if node.inputs['Normal'].is_linked:
|
||||
nor = c.get_vector_input(node, ['Normal'])
|
||||
else:
|
||||
nor = 'wnormal'
|
||||
|
||||
if node.space == 'OBJECT':
|
||||
nor = f'(inverse(mat3(W)) * {nor})'
|
||||
|
||||
disp = f'normalize({nor}) * (vec3({height}) - vec3({midlevel})) * {scale}'
|
||||
|
||||
if node.space == 'OBJECT':
|
||||
return f'(vec4(W * vec4({disp}, 0.0)).xyz)'
|
||||
else:
|
||||
return f'({disp})'
|
||||
|
||||
|
||||
def parse_vector_displacement(node: bpy.types.ShaderNodeVectorDisplacement, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
|
||||
vector = c.get_vector_input(node, ['Vector'])
|
||||
midlevel = c.get_value_input(node, ['Midlevel'])
|
||||
scale = c.get_value_input(node, ['Scale'])
|
||||
|
||||
offset = f'(({vector} - vec3({midlevel})) * {scale})'
|
||||
|
||||
if node.space == 'TANGENT':
|
||||
t_obj = 'normalize(inverse(mat3(W)) * wtangent)'
|
||||
n_obj = 'normalize(inverse(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)'
|
||||
return f'(vec4(W * vec4({disp}, 0.0)).xyz)'
|
||||
elif node.space == 'OBJECT':
|
||||
return f'(vec4(W * vec4({offset}, 0.0)).xyz)'
|
||||
else:
|
||||
return offset
|
||||
|
||||
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.get_vector_input(node, ['Vector'])
|
||||
input_center: bpy.types.NodeSocket = c.get_vector_input(node, ['Center'])
|
||||
input_axis: bpy.types.NodeSocket = c.get_vector_input(node, ['Axis'])
|
||||
input_angle: bpy.types.NodeSocket = c.get_value_input(node, ['Angle'])
|
||||
input_rotation: bpy.types.NodeSocket = c.get_vector_input(node, ['Rotation'])
|
||||
|
||||
if node.invert:
|
||||
input_invert = "0"
|
||||
else:
|
||||
input_invert = "1"
|
||||
input_vector = c.get_vector_input(node, ['Vector'])
|
||||
input_center = c.get_vector_input(node, ['Center'])
|
||||
input_axis = c.get_vector_input(node, ['Axis'])
|
||||
input_angle = c.get_value_input(node, ['Angle'])
|
||||
input_rotation = c.get_vector_input(node, ['Rotation'])
|
||||
|
||||
inv = "-1.0" if node.invert else "1.0"
|
||||
|
||||
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} )'
|
||||
return f'vec3( (length({input_axis}) > 0.001) ? rotate_around_axis({input_vector} - {input_center}, normalize({input_axis}), {input_angle} * {inv}) + {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} )'
|
||||
return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(1.0, 0.0, 0.0), {input_angle} * {inv}) + {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} )'
|
||||
return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 1.0, 0.0), {input_angle} * {inv}) + {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} )'
|
||||
return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 0.0, 1.0), {input_angle} * {inv}) + {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})'
|
||||
rot_val = f'({input_rotation} * {inv})'
|
||||
return f'vec3( euler_to_mat3({rot_val}) * ({input_vector} - {input_center}) + {input_center})'
|
||||
|
||||
return f'(vec3(1.0, 0.0, 0.0))'
|
||||
return f'vec3(0.0, 0.0, 0.0)'
|
||||
|
||||
@ -53,7 +53,6 @@ class ShaderDataNode(Node):
|
||||
def draw_buttons(self, context, layout):
|
||||
col = layout.column(align=True)
|
||||
col.label(text="Input Type:")
|
||||
# Use a row to expand horizontally
|
||||
col.row().prop(self, "input_type", expand=True)
|
||||
|
||||
split = layout.split(factor=0.5, align=True)
|
||||
@ -72,13 +71,18 @@ class ShaderDataNode(Node):
|
||||
def init(self, context):
|
||||
self.outputs.new('NodeSocketColor', 'Color')
|
||||
self.outputs.new('NodeSocketVector', 'Vector')
|
||||
self.outputs.new('NodeSocketFloat', 'Float')
|
||||
self.outputs.new('NodeSocketInt', 'Int')
|
||||
self.outputs.new('NodeSocketFloat', 'Float/Int')
|
||||
|
||||
def __parse(self, out_socket: NodeSocket, state: ParserState) -> str:
|
||||
if self.input_type == "uniform":
|
||||
state.frag.add_uniform(f'{self.variable_type} {self.variable_name}', link=self.variable_name)
|
||||
state.vert.add_uniform(f'{self.variable_type} {self.variable_name}', link=self.variable_name)
|
||||
if self.variable_type == "vec4":
|
||||
state.frag.add_uniform(f'vec3 {self.variable_name}_xyz', link=f'{self.variable_name}_xyz')
|
||||
state.frag.add_uniform(f'float {self.variable_name}_w', link=f'{self.variable_name}_w')
|
||||
state.vert.add_uniform(f'vec3 {self.variable_name}_xyz', link=f'{self.variable_name}_xyz')
|
||||
state.vert.add_uniform(f'float {self.variable_name}_w', link=f'{self.variable_name}_w')
|
||||
else:
|
||||
state.frag.add_uniform(f'{self.variable_type} {self.variable_name}', link=self.variable_name)
|
||||
state.vert.add_uniform(f'{self.variable_type} {self.variable_name}', link=self.variable_name)
|
||||
|
||||
if self.variable_type == "sampler2D":
|
||||
state.frag.add_uniform('vec2 screenSize', link='_screenSize')
|
||||
@ -87,20 +91,41 @@ class ShaderDataNode(Node):
|
||||
if self.variable_type == "vec2":
|
||||
return f'vec3({self.variable_name}.xy, 0)'
|
||||
|
||||
if self.variable_type == "vec4":
|
||||
if out_socket == self.outputs[2]:
|
||||
return f'{self.variable_name}_w'
|
||||
else:
|
||||
return f'{self.variable_name}_xyz'
|
||||
|
||||
return self.variable_name
|
||||
|
||||
else:
|
||||
if self.input_source == "frag":
|
||||
state.frag.add_in(f'{self.variable_type} {self.variable_name}')
|
||||
return self.variable_name
|
||||
if self.variable_type == "vec4":
|
||||
if self.input_source == "frag":
|
||||
state.frag.add_in(f'vec3 {self.variable_name}_xyz')
|
||||
state.frag.add_in(f'float {self.variable_name}_w')
|
||||
else:
|
||||
state.vert.add_out(f'vec3 out_{self.variable_name}_xyz')
|
||||
state.frag.add_in(f'vec3 out_{self.variable_name}_xyz')
|
||||
state.vert.write(f'out_{self.variable_name}_xyz = {self.variable_name}_xyz;')
|
||||
|
||||
# Reroute input from vertex shader to fragment shader (input must exist!)
|
||||
state.vert.add_out(f'float out_{self.variable_name}_w')
|
||||
state.frag.add_in(f'float out_{self.variable_name}_w')
|
||||
state.vert.write(f'out_{self.variable_name}_w = {self.variable_name}_w;')
|
||||
|
||||
if out_socket == self.outputs[2]:
|
||||
return f'out_{self.variable_name}_w' if self.input_source != "frag" else f'{self.variable_name}_w'
|
||||
else:
|
||||
return f'out_{self.variable_name}_xyz' if self.input_source != "frag" else f'{self.variable_name}_xyz'
|
||||
else:
|
||||
state.vert.add_out(f'{self.variable_type} out_{self.variable_name}')
|
||||
state.frag.add_in(f'{self.variable_type} out_{self.variable_name}')
|
||||
|
||||
state.vert.write(f'out_{self.variable_name} = {self.variable_name};')
|
||||
return 'out_' + self.variable_name
|
||||
if self.input_source == "frag":
|
||||
state.frag.add_in(f'{self.variable_type} {self.variable_name}')
|
||||
return self.variable_name
|
||||
else:
|
||||
state.vert.add_out(f'{self.variable_type} out_{self.variable_name}')
|
||||
state.frag.add_in(f'{self.variable_type} out_{self.variable_name}')
|
||||
state.vert.write(f'out_{self.variable_name} = {self.variable_name};')
|
||||
return 'out_' + self.variable_name
|
||||
|
||||
@staticmethod
|
||||
def parse(node: 'ShaderDataNode', out_socket: NodeSocket, state: ParserState) -> str:
|
||||
|
||||
@ -305,7 +305,7 @@ def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]],
|
||||
if wrd.lnx_single_data_file:
|
||||
mat_data['shader'] = shader_data_name
|
||||
else:
|
||||
# Make sure that custom materials are not expected to be in .arm format
|
||||
# Make sure that custom materials are not expected to be in .lnx format
|
||||
ext = '' if wrd.lnx_minimize and material.lnx_custom_material == "" else '.json'
|
||||
mat_data['shader'] = shader_data_name + ext + '/' + shader_data_name
|
||||
|
||||
|
||||
@ -112,7 +112,7 @@ def write_norpos(con_mesh: ShaderContext, vert: Shader, declare=False, write_nor
|
||||
|
||||
if is_morph:
|
||||
|
||||
make_morph_target.morph_pos(vert)
|
||||
make_morph_target.morph_pos(con_mesh, vert)
|
||||
|
||||
if is_bone:
|
||||
|
||||
@ -124,7 +124,7 @@ def write_norpos(con_mesh: ShaderContext, vert: Shader, declare=False, write_nor
|
||||
|
||||
if is_morph:
|
||||
|
||||
make_morph_target.morph_nor(vert, is_bone, prep)
|
||||
make_morph_target.morph_nor(con_mesh, vert, is_bone, prep)
|
||||
|
||||
if is_bone:
|
||||
|
||||
|
||||
@ -100,7 +100,7 @@ def make(context_id, rpasses, shadowmap=False, shadowmap_transparent=False):
|
||||
frag.write('float ior;')
|
||||
|
||||
if(con_depth).is_elem('morph'):
|
||||
make_morph_target.morph_pos(vert)
|
||||
make_morph_target.morph_pos(con_depth, vert)
|
||||
|
||||
if con_depth.is_elem('bone'):
|
||||
make_skin.skin_pos(vert)
|
||||
@ -187,11 +187,11 @@ def make(context_id, rpasses, shadowmap=False, shadowmap_transparent=False):
|
||||
|
||||
if shadowmap:
|
||||
tese.add_uniform('mat4 LVP', '_lightViewProjectionMatrix')
|
||||
tese.write('wposition += wnormal * disp;')
|
||||
tese.write('wposition += disp;')
|
||||
tese.write('gl_Position = LVP * vec4(wposition, 1.0);')
|
||||
else:
|
||||
tese.add_uniform('mat4 VP', '_viewProjectionMatrix')
|
||||
tese.write('wposition += wnormal * disp;')
|
||||
tese.write('wposition += disp;')
|
||||
tese.write('gl_Position = VP * vec4(wposition, 1.0);')
|
||||
# No displacement
|
||||
else:
|
||||
|
||||
@ -23,32 +23,31 @@ def make(con_mesh: ShaderContext):
|
||||
|
||||
# Additional values referenced in cycles
|
||||
# TODO: enable from cycles.py
|
||||
if (frag.contains('dotNV') or frag.contains('n')) and not frag.contains('vec3 n'):
|
||||
vert.add_out('vec3 wnormal')
|
||||
billboard = mat_state.material.lnx_billboard
|
||||
if billboard == 'spherical':
|
||||
vert.add_uniform('mat3 N', '_normalMatrixSphere')
|
||||
elif billboard == 'cylindrical':
|
||||
vert.add_uniform('mat3 N', '_normalMatrixCylinder')
|
||||
else:
|
||||
vert.add_uniform('mat3 N', '_normalMatrix')
|
||||
vert.write_attrib('wnormal = normalize(N * vec3(nor.xy, pos.w));')
|
||||
frag.write_attrib('vec3 n = normalize(wnormal);')
|
||||
|
||||
# If not yet added, add nor vertex data
|
||||
vertex_elems = con_mesh.data['vertex_elements']
|
||||
has_normals = False
|
||||
for elem in vertex_elems:
|
||||
if elem['name'] == 'nor':
|
||||
has_normals = True
|
||||
break
|
||||
if not has_normals:
|
||||
vertex_elems.append({'name': 'nor', 'data': 'short2norm'})
|
||||
|
||||
if frag.contains('dotNV') and not frag.contains('float dotNV'):
|
||||
frag.write_init('float dotNV = max(dot(n, vVec), 0.0);')
|
||||
|
||||
# n is not always defined yet (in some shadowmap shaders e.g.)
|
||||
if not frag.contains('vec3 n'):
|
||||
vert.add_out('vec3 wnormal')
|
||||
billboard = mat_state.material.lnx_billboard
|
||||
if billboard == 'spherical':
|
||||
vert.add_uniform('mat3 N', '_normalMatrixSphere')
|
||||
elif billboard == 'cylindrical':
|
||||
vert.add_uniform('mat3 N', '_normalMatrixCylinder')
|
||||
else:
|
||||
vert.add_uniform('mat3 N', '_normalMatrix')
|
||||
vert.write_attrib('wnormal = normalize(N * vec3(nor.xy, pos.w));')
|
||||
frag.write_attrib('vec3 n = normalize(wnormal);')
|
||||
|
||||
# If not yet added, add nor vertex data
|
||||
vertex_elems = con_mesh.data['vertex_elements']
|
||||
has_normals = False
|
||||
for elem in vertex_elems:
|
||||
if elem['name'] == 'nor':
|
||||
has_normals = True
|
||||
break
|
||||
if not has_normals:
|
||||
vertex_elems.append({'name': 'nor', 'data': 'short2norm'})
|
||||
|
||||
write_wpos = False
|
||||
export_wpos = False
|
||||
if frag.contains('wposition') and not frag.contains('vec3 wposition'):
|
||||
|
||||
@ -9,15 +9,19 @@ def inst_pos(con, vert):
|
||||
vert.write('float srotz = sin(irot.z);')
|
||||
vert.write('float crotz = cos(irot.z);')
|
||||
vert.write('mat3 mirot = mat3(')
|
||||
vert.write(' croty * crotz, srotz, -sroty * crotz,')
|
||||
vert.write(' -croty * srotz * crotx + sroty * srotx, crotz * crotx, sroty * srotz * crotx + croty * srotx,')
|
||||
vert.write(' croty * srotz * srotx + sroty * crotx, -crotz * srotx, -sroty * srotz * srotx + croty * crotx')
|
||||
if con.material.lnx_instanced_rot_type == 'Local':
|
||||
vert.write(' croty * crotz, srotz, -sroty * crotz,')
|
||||
vert.write(' -croty * srotz * crotx + sroty * srotx, crotz * crotx, sroty * srotz * crotx + croty * srotx,')
|
||||
vert.write(' croty * srotz * srotx + sroty * crotx, -crotz * srotx, -sroty * srotz * srotx + croty * crotx')
|
||||
else:
|
||||
vert.write(' croty * crotz, croty * srotz, -sroty,')
|
||||
vert.write(' srotx * sroty * crotz - crotx * srotz, srotx * sroty * srotz + crotx * crotz, srotx * croty,')
|
||||
vert.write(' crotx * sroty * crotz + srotx * srotz, crotx * sroty * srotz - srotx * crotz, crotx * croty')
|
||||
vert.write(');')
|
||||
vert.write('spos.xyz = mirot * spos.xyz;')
|
||||
if (con.data['name'] == 'mesh' or con.data['name'] == 'translucent' or con.data['name'] == 'refraction') and vert.contains('wnormal'):
|
||||
vert.write('wnormal = normalize(N * mirot * vec3(nor.xy, pos.w));')
|
||||
|
||||
|
||||
if con.is_elem('iscl'):
|
||||
vert.write('spos.xyz *= iscl;')
|
||||
|
||||
|
||||
@ -90,6 +90,10 @@ def make(context_id, rpasses):
|
||||
elif rid == 'Raytracer':
|
||||
make_raytracer(con_mesh)
|
||||
|
||||
if mat_state.material.lnx_solid:
|
||||
if rid == 'Forward':
|
||||
con_mesh.frag.write('fragColor[0].rgb = mix(basecol, fragColor[0].rgb, mixColor);')
|
||||
|
||||
make_finalize.make(con_mesh)
|
||||
|
||||
assets.vs_equal(con_mesh, assets.shader_cons['mesh_vert'])
|
||||
@ -107,6 +111,10 @@ def make_base(con_mesh, parse_opacity):
|
||||
tesc = None
|
||||
tese = None
|
||||
|
||||
if mat_state.material.lnx_solid:
|
||||
if lnx.utils.get_rp().rp_renderer == 'Forward':
|
||||
frag.add_uniform('float mixColor', '_mixColor')
|
||||
|
||||
billboard = mat_state.material.lnx_billboard
|
||||
if billboard == 'spherical':
|
||||
vert.add_uniform('mat3 N', '_normalMatrixSphere')
|
||||
@ -192,7 +200,7 @@ def make_base(con_mesh, parse_opacity):
|
||||
if(con_mesh.is_elem('ipos')):
|
||||
vert.write('wposition = vec4(W * spos).xyz;')
|
||||
sh.add_uniform('mat4 VP', '_viewProjectionMatrix')
|
||||
sh.write('wposition += wnormal * disp;')
|
||||
sh.write('wposition += disp;')
|
||||
sh.write('gl_Position = VP * vec4(wposition, 1.0);')
|
||||
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ if lnx.is_reload(__name__):
|
||||
else:
|
||||
lnx.enable_reload(__name__)
|
||||
|
||||
def morph_pos(vert):
|
||||
def morph_pos(con, vert):
|
||||
if vert.has_attrib('vec2 texCoordMorph = morph * texUnpack;'):
|
||||
return
|
||||
rpdat = lnx.utils.get_rp()
|
||||
@ -20,13 +20,19 @@ def morph_pos(vert):
|
||||
vert.add_uniform('float posUnpack', link='_posUnpack')
|
||||
vert.write_attrib('vec2 texCoordMorph = morph * texUnpack;')
|
||||
vert.write_attrib('spos.xyz *= posUnpack;')
|
||||
vert.write_attrib('getMorphedVertex(texCoordMorph, spos.xyz);')
|
||||
if con.is_elem('imorph'):
|
||||
vert.write_attrib('getMorphedVertex(texCoordMorph, spos.xyz, imorph);')
|
||||
else:
|
||||
vert.write_attrib('getMorphedVertex(texCoordMorph, spos.xyz);')
|
||||
vert.write_attrib('spos.xyz /= posUnpack;')
|
||||
|
||||
def morph_nor(vert, is_bone, prep):
|
||||
def morph_nor(con, vert, is_bone, prep):
|
||||
if vert.has_attrib('vec3 morphNor = vec3(0, 0, 0);'):
|
||||
return
|
||||
vert.write_attrib('vec3 morphNor = vec3(0, 0, 0);')
|
||||
vert.write_attrib('getMorphedNormal(texCoordMorph, vec3(nor.xy, pos.w), morphNor);')
|
||||
if con.is_elem('imorph'):
|
||||
vert.write_attrib('getMorphedNormal(texCoordMorph, vec3(nor.xy, pos.w), morphNor, imorph);')
|
||||
else:
|
||||
vert.write_attrib('getMorphedNormal(texCoordMorph, vec3(nor.xy, pos.w), morphNor);')
|
||||
if not is_bone:
|
||||
vert.write_attrib(prep + 'wnormal = normalize(N * morphNor);')
|
||||
|
||||
@ -22,35 +22,41 @@ def write(vert, particle_info=None, shadowmap=False):
|
||||
rotation_factor_random = 0
|
||||
phase_factor = 0
|
||||
phase_factor_random = 0
|
||||
normal_factor = 0.0
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
for psys in obj.particle_systems:
|
||||
psettings = psys.settings
|
||||
|
||||
if psettings.instance_object:
|
||||
if psettings.instance_object.active_material:
|
||||
# FIXME: Different particle systems may share the same particle object. This ideally should check the correct `ParticleSystem` using an id or name in the particle's object material.
|
||||
if psettings.instance_object.active_material.name.replace(".", "_") == vert.context.matname:
|
||||
# Rotation data
|
||||
use_rotations = psettings.use_rotations
|
||||
rotation_mode = psettings.rotation_mode
|
||||
rotation_factor_random = psettings.rotation_factor_random
|
||||
phase_factor = psettings.phase_factor
|
||||
phase_factor_random = psettings.phase_factor_random
|
||||
mat_found = False
|
||||
for slot in psettings.instance_object.material_slots:
|
||||
if slot.material and slot.material.name.replace(".", "_") == vert.context.matname:
|
||||
mat_found = True
|
||||
break
|
||||
if mat_found:
|
||||
# Rotation data
|
||||
use_rotations = psettings.use_rotations
|
||||
rotation_mode = psettings.rotation_mode
|
||||
rotation_factor_random = psettings.rotation_factor_random
|
||||
phase_factor = psettings.phase_factor
|
||||
phase_factor_random = psettings.phase_factor_random
|
||||
|
||||
normal_factor = psettings.normal_factor
|
||||
|
||||
# Texture slots data
|
||||
if psettings.texture_slots and len(psettings.texture_slots.items()) != 0:
|
||||
for tex_slot in psettings.texture_slots:
|
||||
if not tex_slot: break
|
||||
if not tex_slot.use_map_size: break # TODO: check also for other influences
|
||||
if tex_slot.texture.use_color_ramp:
|
||||
if tex_slot.texture.color_ramp and tex_slot.texture.color_ramp.elements:
|
||||
ramp_el_len = len(tex_slot.texture.color_ramp.elements.items())
|
||||
for element in tex_slot.texture.color_ramp.elements:
|
||||
ramp_positions.append(element.position)
|
||||
ramp_colors_b.append(element.color[2])
|
||||
size_over_time_factor = tex_slot.size_factor
|
||||
break
|
||||
# Texture slots data
|
||||
if psettings.texture_slots and len(psettings.texture_slots.items()) != 0:
|
||||
for tex_slot in psettings.texture_slots:
|
||||
if not tex_slot: break
|
||||
if not tex_slot.use_map_size: break # TODO: check also for other influences
|
||||
if tex_slot.texture and tex_slot.texture.use_color_ramp:
|
||||
if tex_slot.texture.color_ramp and tex_slot.texture.color_ramp.elements:
|
||||
ramp_el_len = len(tex_slot.texture.color_ramp.elements.items())
|
||||
for element in tex_slot.texture.color_ramp.elements:
|
||||
ramp_positions.append(element.position)
|
||||
ramp_colors_b.append(element.color[2])
|
||||
size_over_time_factor = tex_slot.size_factor
|
||||
break
|
||||
|
||||
|
||||
# Outs
|
||||
@ -72,6 +78,12 @@ def write(vert, particle_info=None, shadowmap=False):
|
||||
vert.add_uniform('float pd_random', '_particleRandom')
|
||||
vert.add_uniform('float pd_size', '_particleSize')
|
||||
|
||||
vert.add_const('float', 'P_NORMAL_FACTOR', str(normal_factor))
|
||||
|
||||
if (shadowmap and normal_factor != 0.0):
|
||||
vert.add_out('vec3 N')
|
||||
|
||||
|
||||
if ramp_el_len != 0:
|
||||
vert.add_const('float', 'P_SIZE_OVER_TIME_FACTOR', str(size_over_time_factor))
|
||||
for i in range(ramp_el_len):
|
||||
@ -143,6 +155,13 @@ def write(vert, particle_info=None, shadowmap=False):
|
||||
vert.write('p_velocity.y += (fhash(gl_InstanceID + pd_random + pd[0][3]) * 2.0 / pd_size - 1.0 / pd_size) * pd[1][3];')
|
||||
vert.write('p_velocity.z += (fhash(gl_InstanceID + pd_random + 2 * pd[0][3]) * 2.0 / pd_size - 1.0 / pd_size) * pd[1][3];')
|
||||
|
||||
if normal_factor != 0.0:
|
||||
vert.write('if (P_NORMAL_FACTOR != 0.0) {')
|
||||
vert.write(' vec3 local_normal_dir = normalize(ipos);')
|
||||
vert.write(' vec3 world_emitter_normal = normalize(N * local_normal_dir);')
|
||||
vert.write(' p_velocity += world_emitter_normal * P_NORMAL_FACTOR;')
|
||||
vert.write('}')
|
||||
|
||||
# factor_random = pd[1][3]
|
||||
# p.i = gl_InstanceID
|
||||
# particles.length = pd[0][3]
|
||||
@ -262,6 +281,10 @@ def write(vert, particle_info=None, shadowmap=False):
|
||||
vert.add_out('float p_random')
|
||||
vert.write('p_random = fract(sin(gl_InstanceID) * 43758.5453);')
|
||||
|
||||
if out_size:
|
||||
vert.add_out('float p_size')
|
||||
vert.write('p_size = pd_size * (1 - (fhash(gl_InstanceID + 3 * pd[0][3] + pd_random) * pd_size_random));')
|
||||
|
||||
|
||||
def write_tilesheet(vert):
|
||||
# tilesx, tilesy, framerate - pd[3][0], pd[3][1], pd[3][2]
|
||||
|
||||
@ -328,6 +328,12 @@ def make_instancing_and_skinning(mat: Material, mat_users: Dict[Material, List[O
|
||||
if 'Scale' in inst:
|
||||
global_elems.append({'name': 'iscl', 'data': 'float3'})
|
||||
|
||||
for attr in bo.lnx_instanced_attrs:
|
||||
global_elems.append({'name': attr.name, 'data': f"float{attr.size}"})
|
||||
|
||||
if lnx.utils.export_morph_targets(bo):
|
||||
global_elems.append({'name': 'imorph', 'data': 'float4'})
|
||||
|
||||
elif inst == 'Off':
|
||||
# Ignore children of instanced objects, they are instanced even when set to 'Off'
|
||||
instancing_usage[1] = bo.parent is None or bo.parent.lnx_instanced == 'Off'
|
||||
|
||||
@ -188,11 +188,14 @@ ALL_NODES: Dict[str, MaterialNodeMeta] = {
|
||||
'TEX_SKY': MaterialNodeMeta(parse_func=nodes_texture.parse_tex_sky),
|
||||
'TEX_VORONOI': MaterialNodeMeta(parse_func=nodes_texture.parse_tex_voronoi),
|
||||
'TEX_WAVE': MaterialNodeMeta(parse_func=nodes_texture.parse_tex_wave),
|
||||
'TEX_GABOR': MaterialNodeMeta(parse_func=nodes_texture.parse_tex_gabor),
|
||||
'TEX_WHITE_NOISE': MaterialNodeMeta(parse_func=nodes_texture.parse_tex_white_noise),
|
||||
|
||||
# --- nodes_vector
|
||||
'BUMP': MaterialNodeMeta(parse_func=nodes_vector.parse_bump),
|
||||
'CURVE_VEC': MaterialNodeMeta(parse_func=nodes_vector.parse_curvevec),
|
||||
'DISPLACEMENT': MaterialNodeMeta(parse_func=nodes_vector.parse_displacement),
|
||||
'VECTOR_DISPLACEMENT': MaterialNodeMeta(parse_func=nodes_vector.parse_vector_displacement),
|
||||
'MAPPING': MaterialNodeMeta(parse_func=nodes_vector.parse_mapping),
|
||||
'NORMAL': MaterialNodeMeta(parse_func=nodes_vector.parse_normal),
|
||||
'NORMAL_MAP': MaterialNodeMeta(parse_func=nodes_vector.parse_normalmap),
|
||||
|
||||
@ -86,6 +86,10 @@ class ShaderContext:
|
||||
self.data['constants'] = []
|
||||
self.constants = self.data['constants']
|
||||
|
||||
if hasattr(self.material, 'lnx_solid'):
|
||||
if self.material.lnx_solid:
|
||||
self.constants.append({'floatValue': self.material.lnx_solid_factor, "is_lnx_parameter": True, 'link': '_mixColor', 'name': 'mixColor', 'type': 'float'})
|
||||
|
||||
def add_elem(self, name, data):
|
||||
elem = { 'name': name, 'data': data }
|
||||
if elem not in self.data['vertex_elements']:
|
||||
@ -95,6 +99,12 @@ class ShaderContext:
|
||||
def sort_vs(self):
|
||||
vs = []
|
||||
ar = ['pos', 'nor', 'tex', 'tex1', 'morph', 'col', 'tang', 'bone', 'weight', 'ipos', 'irot', 'iscl']
|
||||
|
||||
if 'vertex_elements' in self.data:
|
||||
for elem in self.data['vertex_elements']:
|
||||
if elem['name'] not in ar:
|
||||
ar.append(elem['name'])
|
||||
|
||||
for ename in ar:
|
||||
elem = self.get_elem(ename)
|
||||
if elem != None:
|
||||
@ -399,7 +409,11 @@ class Shader:
|
||||
return
|
||||
vs = self.context.data['vertex_elements']
|
||||
for e in vs:
|
||||
self.add_in('vec' + self.data_size(e['data']) + ' ' + e['name'])
|
||||
size = self.data_size(e['data'])
|
||||
if size == '1':
|
||||
self.add_in('float ' + e['name'])
|
||||
else:
|
||||
self.add_in('vec' + size + ' ' + e['name'])
|
||||
|
||||
def validate(self):
|
||||
import re
|
||||
|
||||
Reference in New Issue
Block a user