Applied the same fix that is used for uvs, since it seems to solve the issue of not updating correctly for exporting of materials too. Also added support for the Vertex Color node.
1763 lines
68 KiB
Python
1763 lines
68 KiB
Python
#
|
|
# This module builds upon Cycles nodes work licensed as
|
|
# Copyright 2011-2013 Blender Foundation
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
import math
|
|
import bpy
|
|
import os
|
|
import arm.assets
|
|
import arm.utils
|
|
import arm.make_state
|
|
import arm.log
|
|
import arm.material.mat_state as mat_state
|
|
import arm.material.cycles_functions as c_functions
|
|
import shutil
|
|
|
|
emission_found = False
|
|
particle_info = None # Particle info export
|
|
|
|
def parse(nodes, con, vert, frag, geom, tesc, tese, parse_surface=True, parse_opacity=True, parse_displacement=True, basecol_only=False):
|
|
output_node = node_by_type(nodes, 'OUTPUT_MATERIAL')
|
|
if output_node != None:
|
|
parse_output(output_node, con, vert, frag, geom, tesc, tese, parse_surface, parse_opacity, parse_displacement, basecol_only)
|
|
|
|
def parse_output(node, _con, _vert, _frag, _geom, _tesc, _tese, _parse_surface, _parse_opacity, _parse_displacement, _basecol_only):
|
|
global parsed # Compute nodes only once
|
|
global parents
|
|
global normal_parsed
|
|
global curshader # Active shader - frag for surface / tese for displacement
|
|
global con
|
|
global vert
|
|
global frag
|
|
global geom
|
|
global tesc
|
|
global tese
|
|
global parse_surface
|
|
global parse_opacity
|
|
global basecol_only
|
|
global emission_found
|
|
global particle_info
|
|
global sample_bump
|
|
global sample_bump_res
|
|
con = _con
|
|
vert = _vert
|
|
frag = _frag
|
|
geom = _geom
|
|
tesc = _tesc
|
|
tese = _tese
|
|
parse_surface = _parse_surface
|
|
parse_opacity = _parse_opacity
|
|
basecol_only = _basecol_only
|
|
emission_found = False
|
|
particle_info = {}
|
|
particle_info['index'] = False
|
|
particle_info['age'] = False
|
|
particle_info['lifetime'] = False
|
|
particle_info['location'] = False
|
|
particle_info['size'] = False
|
|
particle_info['velocity'] = False
|
|
particle_info['angular_velocity'] = False
|
|
sample_bump = False
|
|
sample_bump_res = ''
|
|
wrd = bpy.data.worlds['Arm']
|
|
|
|
# Surface
|
|
if parse_surface or parse_opacity:
|
|
parsed = {}
|
|
parents = []
|
|
normal_parsed = False
|
|
curshader = frag
|
|
|
|
out_basecol, out_roughness, out_metallic, out_occlusion, out_specular, out_opacity, out_emission = parse_shader_input(node.inputs[0])
|
|
if parse_surface:
|
|
frag.write('basecol = {0};'.format(out_basecol))
|
|
frag.write('roughness = {0};'.format(out_roughness))
|
|
frag.write('metallic = {0};'.format(out_metallic))
|
|
frag.write('occlusion = {0};'.format(out_occlusion))
|
|
frag.write('specular = {0};'.format(out_specular))
|
|
if '_Emission' in wrd.world_defs:
|
|
frag.write('emission = {0};'.format(out_emission))
|
|
if parse_opacity:
|
|
frag.write('opacity = {0} - 0.0002;'.format(out_opacity))
|
|
|
|
# Volume
|
|
# parse_volume_input(node.inputs[1])
|
|
|
|
# Displacement
|
|
if _parse_displacement and disp_enabled() and node.inputs[2].is_linked:
|
|
parsed = {}
|
|
parents = []
|
|
normal_parsed = False
|
|
rpdat = arm.utils.get_rp()
|
|
if rpdat.arm_rp_displacement == 'Tessellation' and tese != None:
|
|
curshader = tese
|
|
else:
|
|
curshader = vert
|
|
out_disp = parse_displacement_input(node.inputs[2])
|
|
curshader.write('vec3 disp = {0};'.format(out_disp))
|
|
|
|
def parse_group(node, socket): # Entering group
|
|
index = socket_index(node, socket)
|
|
output_node = node_by_type(node.node_tree.nodes, 'GROUP_OUTPUT')
|
|
if output_node == None:
|
|
return
|
|
inp = output_node.inputs[index]
|
|
parents.append(node)
|
|
out_group = parse_input(inp)
|
|
parents.pop()
|
|
return out_group
|
|
|
|
def parse_group_input(node, socket):
|
|
index = socket_index(node, socket)
|
|
parent = parents.pop() # Leaving group
|
|
inp = parent.inputs[index]
|
|
res = parse_input(inp)
|
|
parents.append(parent) # Return to group
|
|
return res
|
|
|
|
def parse_input(inp):
|
|
if inp.type == 'SHADER':
|
|
return parse_shader_input(inp)
|
|
elif inp.type == 'RGB':
|
|
return parse_vector_input(inp)
|
|
elif inp.type == 'RGBA':
|
|
return parse_vector_input(inp)
|
|
elif inp.type == 'VECTOR':
|
|
return parse_vector_input(inp)
|
|
elif inp.type == 'VALUE':
|
|
return parse_value_input(inp)
|
|
|
|
def parse_shader_input(inp):
|
|
if inp.is_linked:
|
|
l = inp.links[0]
|
|
if l.from_node.type == 'REROUTE':
|
|
return parse_shader_input(l.from_node.inputs[0])
|
|
return parse_shader(l.from_node, l.from_socket)
|
|
else:
|
|
out_basecol = 'vec3(0.8)'
|
|
out_roughness = '0.0'
|
|
out_metallic = '0.0'
|
|
out_occlusion = '1.0'
|
|
out_specular = '1.0'
|
|
out_opacity = '1.0'
|
|
out_emission = '0.0'
|
|
return out_basecol, out_roughness, out_metallic, out_occlusion, out_specular, out_opacity, out_emission
|
|
|
|
def parse_shader(node, socket):
|
|
global emission_found
|
|
out_basecol = 'vec3(0.8)'
|
|
out_roughness = '0.0'
|
|
out_metallic = '0.0'
|
|
out_occlusion = '1.0'
|
|
out_specular = '1.0'
|
|
out_opacity = '1.0'
|
|
out_emission = '0.0'
|
|
|
|
if node.type == 'GROUP':
|
|
if node.node_tree.name.startswith('Armory PBR'):
|
|
if parse_surface:
|
|
# Base color
|
|
out_basecol = parse_vector_input(node.inputs[0])
|
|
# Occlusion
|
|
out_occlusion = parse_value_input(node.inputs[2])
|
|
# Roughness
|
|
out_roughness = parse_value_input(node.inputs[3])
|
|
# Metallic
|
|
out_metallic = parse_value_input(node.inputs[4])
|
|
# Normal
|
|
if node.inputs[5].is_linked and node.inputs[5].links[0].from_node.type == 'NORMAL_MAP':
|
|
warn(mat_name() + ' - Do not use Normal Map node with Armory PBR, connect Image Texture directly')
|
|
parse_normal_map_color_input(node.inputs[5])
|
|
# Emission
|
|
if node.inputs[6].is_linked or node.inputs[6].default_value != 0.0:
|
|
out_emission = parse_value_input(node.inputs[6])
|
|
emission_found = True
|
|
if parse_opacity:
|
|
out_opacity = parse_value_input(node.inputs[1])
|
|
else:
|
|
return parse_group(node, socket)
|
|
|
|
elif node.type == 'GROUP_INPUT':
|
|
return parse_group_input(node, socket)
|
|
|
|
elif node.type == 'MIX_SHADER':
|
|
prefix = '' if node.inputs[0].is_linked else 'const '
|
|
fac = parse_value_input(node.inputs[0])
|
|
fac_var = node_name(node.name) + '_fac'
|
|
fac_inv_var = node_name(node.name) + '_fac_inv'
|
|
curshader.write('{0}float {1} = {2};'.format(prefix, fac_var, fac))
|
|
curshader.write('{0}float {1} = 1.0 - {2};'.format(prefix, fac_inv_var, fac_var))
|
|
bc1, rough1, met1, occ1, spec1, opac1, emi1 = parse_shader_input(node.inputs[1])
|
|
bc2, rough2, met2, occ2, spec2, opac2, emi2 = parse_shader_input(node.inputs[2])
|
|
if parse_surface:
|
|
out_basecol = '({0} * {3} + {1} * {2})'.format(bc1, bc2, fac_var, fac_inv_var)
|
|
out_roughness = '({0} * {3} + {1} * {2})'.format(rough1, rough2, fac_var, fac_inv_var)
|
|
out_metallic = '({0} * {3} + {1} * {2})'.format(met1, met2, fac_var, fac_inv_var)
|
|
out_occlusion = '({0} * {3} + {1} * {2})'.format(occ1, occ2, fac_var, fac_inv_var)
|
|
out_specular = '({0} * {3} + {1} * {2})'.format(spec1, spec2, fac_var, fac_inv_var)
|
|
out_emission = '({0} * {3} + {1} * {2})'.format(emi1, emi2, fac_var, fac_inv_var)
|
|
if parse_opacity:
|
|
out_opacity = '({0} * {3} + {1} * {2})'.format(opac1, opac2, fac_var, fac_inv_var)
|
|
|
|
elif node.type == 'ADD_SHADER':
|
|
bc1, rough1, met1, occ1, spec1, opac1, emi1 = parse_shader_input(node.inputs[0])
|
|
bc2, rough2, met2, occ2, spec2, opac2, emi2 = parse_shader_input(node.inputs[1])
|
|
if parse_surface:
|
|
out_basecol = '({0} + {1})'.format(bc1, bc2)
|
|
out_roughness = '({0} * 0.5 + {1} * 0.5)'.format(rough1, rough2)
|
|
out_metallic = '({0} * 0.5 + {1} * 0.5)'.format(met1, met2)
|
|
out_occlusion = '({0} * 0.5 + {1} * 0.5)'.format(occ1, occ2)
|
|
out_specular = '({0} * 0.5 + {1} * 0.5)'.format(spec1, spec2)
|
|
out_emission = '({0} * 0.5 + {1} * 0.5)'.format(emi1, emi2)
|
|
if parse_opacity:
|
|
out_opacity = '({0} * 0.5 + {1} * 0.5)'.format(opac1, opac2)
|
|
|
|
elif node.type == 'BSDF_PRINCIPLED':
|
|
if parse_surface:
|
|
write_normal(node.inputs[19])
|
|
out_basecol = parse_vector_input(node.inputs[0])
|
|
# subsurface = parse_vector_input(node.inputs[1])
|
|
# subsurface_radius = parse_vector_input(node.inputs[2])
|
|
# subsurface_color = parse_vector_input(node.inputs[3])
|
|
out_metallic = parse_value_input(node.inputs[4])
|
|
out_specular = parse_value_input(node.inputs[5])
|
|
# specular_tint = parse_vector_input(node.inputs[6])
|
|
out_roughness = parse_value_input(node.inputs[7])
|
|
# aniso = parse_vector_input(node.inputs[8])
|
|
# aniso_rot = parse_vector_input(node.inputs[9])
|
|
# sheen = parse_vector_input(node.inputs[10])
|
|
# sheen_tint = parse_vector_input(node.inputs[11])
|
|
# clearcoat = parse_vector_input(node.inputs[12])
|
|
# clearcoat_rough = parse_vector_input(node.inputs[13])
|
|
# ior = parse_vector_input(node.inputs[14])
|
|
# transmission = parse_vector_input(node.inputs[15])
|
|
# transmission_roughness = parse_vector_input(node.inputs[16])
|
|
if node.inputs[17].is_linked or node.inputs[17].default_value[0] != 0.0:
|
|
out_emission = '({0}.x)'.format(parse_vector_input(node.inputs[17]))
|
|
emission_found = True
|
|
# clearcoar_normal = parse_vector_input(node.inputs[20])
|
|
# tangent = parse_vector_input(node.inputs[21])
|
|
if parse_opacity:
|
|
if len(node.inputs) > 20:
|
|
out_opacity = parse_value_input(node.inputs[18])
|
|
|
|
elif node.type == 'BSDF_DIFFUSE':
|
|
if parse_surface:
|
|
write_normal(node.inputs[2])
|
|
out_basecol = parse_vector_input(node.inputs[0])
|
|
out_roughness = parse_value_input(node.inputs[1])
|
|
out_specular = '0.0'
|
|
|
|
elif node.type == 'BSDF_GLOSSY':
|
|
if parse_surface:
|
|
write_normal(node.inputs[2])
|
|
out_basecol = parse_vector_input(node.inputs[0])
|
|
out_roughness = parse_value_input(node.inputs[1])
|
|
out_metallic = '1.0'
|
|
|
|
elif node.type == 'AMBIENT_OCCLUSION':
|
|
if parse_surface:
|
|
# Single channel
|
|
out_occlusion = parse_vector_input(node.inputs[0]) + '.r'
|
|
|
|
elif node.type == 'BSDF_ANISOTROPIC':
|
|
if parse_surface:
|
|
write_normal(node.inputs[4])
|
|
# Revert to glossy
|
|
out_basecol = parse_vector_input(node.inputs[0])
|
|
out_roughness = parse_value_input(node.inputs[1])
|
|
out_metallic = '1.0'
|
|
|
|
elif node.type == 'EMISSION':
|
|
if parse_surface:
|
|
# Multiply basecol
|
|
out_basecol = parse_vector_input(node.inputs[0])
|
|
out_emission = '1.0'
|
|
emission_found = True
|
|
emission_strength = parse_value_input(node.inputs[1])
|
|
out_basecol = '({0} * {1})'.format(out_basecol, emission_strength)
|
|
|
|
elif node.type == 'BSDF_GLASS':
|
|
if parse_surface:
|
|
write_normal(node.inputs[3])
|
|
out_roughness = parse_value_input(node.inputs[1])
|
|
if parse_opacity:
|
|
out_opacity = '(1.0 - {0}.r)'.format(parse_vector_input(node.inputs[0]))
|
|
|
|
elif node.type == 'BSDF_HAIR':
|
|
pass
|
|
|
|
elif node.type == 'HOLDOUT':
|
|
if parse_surface:
|
|
# Occlude
|
|
out_occlusion = '0.0'
|
|
|
|
elif node.type == 'BSDF_REFRACTION':
|
|
# write_normal(node.inputs[3])
|
|
pass
|
|
|
|
elif node.type == 'SUBSURFACE_SCATTERING':
|
|
if parse_surface:
|
|
write_normal(node.inputs[4])
|
|
out_basecol = parse_vector_input(node.inputs[0])
|
|
|
|
elif node.type == 'BSDF_TOON':
|
|
# write_normal(node.inputs[3])
|
|
pass
|
|
|
|
elif node.type == 'BSDF_TRANSLUCENT':
|
|
if parse_surface:
|
|
write_normal(node.inputs[1])
|
|
if parse_opacity:
|
|
out_opacity = '(1.0 - {0}.r)'.format(parse_vector_input(node.inputs[0]))
|
|
|
|
elif node.type == 'BSDF_TRANSPARENT':
|
|
if parse_opacity:
|
|
out_opacity = '(1.0 - {0}.r)'.format(parse_vector_input(node.inputs[0]))
|
|
|
|
elif node.type == 'BSDF_VELVET':
|
|
if parse_surface:
|
|
write_normal(node.inputs[2])
|
|
out_basecol = parse_vector_input(node.inputs[0])
|
|
out_roughness = '1.0'
|
|
out_metallic = '1.0'
|
|
|
|
elif node.type == 'VOLUME_ABSORPTION':
|
|
pass
|
|
|
|
elif node.type == 'VOLUME_SCATTER':
|
|
pass
|
|
|
|
return out_basecol, out_roughness, out_metallic, out_occlusion, out_specular, out_opacity, out_emission
|
|
|
|
def parse_displacement_input(inp):
|
|
if inp.is_linked:
|
|
l = inp.links[0]
|
|
if l.from_node.type == 'REROUTE':
|
|
return parse_displacement_input(l.from_node.inputs[0])
|
|
return parse_vector_input(inp)
|
|
else:
|
|
return None
|
|
|
|
def parse_vector_input(inp):
|
|
if inp.is_linked:
|
|
l = inp.links[0]
|
|
if l.from_node.type == 'REROUTE':
|
|
return parse_vector_input(l.from_node.inputs[0])
|
|
res_var = write_result(l)
|
|
st = l.from_socket.type
|
|
if st == 'RGB' or st == 'RGBA' or st == 'VECTOR':
|
|
return res_var
|
|
else: # VALUE
|
|
return 'vec3({0})'.format(res_var)
|
|
else:
|
|
if inp.type == 'VALUE': # Unlinked reroute
|
|
return to_vec3([0.0, 0.0, 0.0])
|
|
else:
|
|
if mat_batch() and inp.is_uniform:
|
|
return to_uniform(inp)
|
|
else:
|
|
return to_vec3(inp.default_value)
|
|
|
|
def parse_vector(node, socket):
|
|
global particle_info
|
|
global sample_bump
|
|
global sample_bump_res
|
|
|
|
# RGB
|
|
if node.type == 'GROUP':
|
|
return parse_group(node, socket)
|
|
|
|
elif node.type == 'GROUP_INPUT':
|
|
return parse_group_input(node, socket)
|
|
|
|
elif node.type == 'VERTEX_COLOR':
|
|
con.add_elem('col', 'short4norm') # Vcols only for now
|
|
return 'vcolor'
|
|
|
|
elif node.type == 'ATTRIBUTE':
|
|
if socket == node.outputs[0]: # Color
|
|
con.add_elem('col', 'short4norm') # Vcols only for now
|
|
return 'vcolor'
|
|
else: # Vector
|
|
con.add_elem('tex', 'short2norm') # UVMaps only for now
|
|
mat = mat_get_material()
|
|
mat_users = mat_get_material_users()
|
|
if mat_users != None and mat in mat_users:
|
|
mat_user = mat_users[mat][0]
|
|
if hasattr(mat_user.data, 'uv_layers'): # No uvlayers for Curve
|
|
lays = mat_user.data.uv_layers
|
|
# Second uvmap referenced
|
|
if len(lays) > 1 and node.attribute_name == lays[1].name:
|
|
con.add_elem('tex1', 'short2norm')
|
|
return 'vec3(texCoord1.x, 1.0 - texCoord1.y, 0.0)'
|
|
return 'vec3(texCoord.x, 1.0 - texCoord.y, 0.0)'
|
|
|
|
elif node.type == 'RGB':
|
|
if node.arm_material_param:
|
|
nn = 'param_' + node_name(node.name)
|
|
curshader.add_uniform('vec3 {0}'.format(nn), link='{0}'.format(node.name))
|
|
return nn
|
|
else:
|
|
return to_vec3(socket.default_value)
|
|
|
|
elif node.type == 'TEX_BRICK':
|
|
curshader.add_function(c_functions.str_tex_brick)
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
col1 = parse_vector_input(node.inputs[1])
|
|
col2 = parse_vector_input(node.inputs[2])
|
|
col3 = parse_vector_input(node.inputs[3])
|
|
scale = parse_value_input(node.inputs[4])
|
|
res = 'tex_brick({0} * {4}, {1}, {2}, {3})'.format(co, col1, col2, col3, scale)
|
|
if sample_bump:
|
|
write_bump(node, res)
|
|
return res
|
|
|
|
elif node.type == 'TEX_CHECKER':
|
|
curshader.add_function(c_functions.str_tex_checker)
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
col1 = parse_vector_input(node.inputs[1])
|
|
col2 = parse_vector_input(node.inputs[2])
|
|
scale = parse_value_input(node.inputs[3])
|
|
res = 'tex_checker({0}, {1}, {2}, {3})'.format(co, col1, col2, scale)
|
|
if sample_bump:
|
|
write_bump(node, res)
|
|
return res
|
|
|
|
elif node.type == 'TEX_ENVIRONMENT':
|
|
# Pass through
|
|
return to_vec3([0.0, 0.0, 0.0])
|
|
|
|
elif node.type == 'TEX_GRADIENT':
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
grad = node.gradient_type
|
|
if grad == 'LINEAR':
|
|
f = '{0}.x'.format(co)
|
|
elif grad == 'QUADRATIC':
|
|
f = '0.0'
|
|
elif grad == 'EASING':
|
|
f = '0.0'
|
|
elif grad == 'DIAGONAL':
|
|
f = '({0}.x + {0}.y) * 0.5'.format(co)
|
|
elif grad == 'RADIAL':
|
|
f = 'atan({0}.y, {0}.x) / PI2 + 0.5'.format(co)
|
|
elif grad == 'QUADRATIC_SPHERE':
|
|
f = '0.0'
|
|
elif grad == 'SPHERICAL':
|
|
f = 'max(1.0 - sqrt({0}.x * {0}.x + {0}.y * {0}.y + {0}.z * {0}.z), 0.0)'.format(co)
|
|
res = 'vec3(clamp({0}, 0.0, 1.0))'.format(f)
|
|
if sample_bump:
|
|
write_bump(node, res)
|
|
return res
|
|
|
|
elif node.type == 'TEX_IMAGE':
|
|
# Already fetched
|
|
if is_parsed(store_var_name(node)):
|
|
return '{0}.rgb'.format(store_var_name(node))
|
|
tex_name = node_name(node.name)
|
|
tex = make_texture(node, tex_name)
|
|
tex_link = node.name if node.arm_material_param else None
|
|
if tex != None:
|
|
curshader.write_textures += 1
|
|
to_linear = node.image != None and node.image.colorspace_settings.name == 'sRGB'
|
|
res = '{0}.rgb'.format(texture_store(node, tex, tex_name, to_linear, tex_link=tex_link))
|
|
curshader.write_textures -= 1
|
|
return res
|
|
elif node.image == None: # Empty texture
|
|
tex = {}
|
|
tex['name'] = tex_name
|
|
tex['file'] = ''
|
|
return '{0}.rgb'.format(texture_store(node, tex, tex_name, to_linear=False, tex_link=tex_link))
|
|
else:
|
|
global parsed
|
|
tex_store = store_var_name(node) # Pink color for missing texture
|
|
parsed[tex_store] = True
|
|
curshader.write_textures += 1
|
|
curshader.write('vec4 {0} = vec4(1.0, 0.0, 1.0, 1.0);'.format(tex_store))
|
|
curshader.write_textures -= 1
|
|
return '{0}.rgb'.format(tex_store)
|
|
|
|
elif node.type == 'TEX_MAGIC':
|
|
curshader.add_function(c_functions.str_tex_magic)
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
scale = parse_value_input(node.inputs[1])
|
|
res = 'tex_magic({0} * {1} * 4.0)'.format(co, scale)
|
|
if sample_bump:
|
|
write_bump(node, res, 0.1)
|
|
return res
|
|
|
|
elif node.type == 'TEX_MUSGRAVE':
|
|
curshader.add_function(c_functions.str_tex_musgrave)
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
scale = parse_value_input(node.inputs[1])
|
|
# detail = parse_value_input(node.inputs[2])
|
|
# distortion = parse_value_input(node.inputs[3])
|
|
res = 'vec3(tex_musgrave_f({0} * {1} * 0.5))'.format(co, scale)
|
|
if sample_bump:
|
|
write_bump(node, res)
|
|
return res
|
|
|
|
elif node.type == 'TEX_NOISE':
|
|
curshader.add_function(c_functions.str_tex_noise)
|
|
assets_add(get_sdk_path() + '/armory/Assets/' + 'noise256.png')
|
|
assets_add_embedded_data('noise256.png')
|
|
curshader.add_uniform('sampler2D snoise256', link='$noise256.png')
|
|
curshader.add_function(c_functions.str_tex_noise)
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
scale = parse_value_input(node.inputs[1])
|
|
# detail = parse_value_input(node.inputs[2])
|
|
# distortion = parse_value_input(node.inputs[3])
|
|
# Slow..
|
|
res = 'vec3(tex_noise({0} * {1}), tex_noise({0} * {1} + 0.33), tex_noise({0} * {1} + 0.66))'.format(co, scale)
|
|
if sample_bump:
|
|
write_bump(node, res, 0.1)
|
|
return res
|
|
|
|
elif node.type == 'TEX_POINTDENSITY':
|
|
# Pass through
|
|
return to_vec3([0.0, 0.0, 0.0])
|
|
|
|
elif node.type == 'TEX_SKY':
|
|
# Pass through
|
|
return to_vec3([0.0, 0.0, 0.0])
|
|
|
|
elif node.type == 'TEX_VORONOI':
|
|
curshader.add_function(c_functions.str_tex_voronoi)
|
|
assets_add(get_sdk_path() + '/armory/Assets/' + 'noise256.png')
|
|
assets_add_embedded_data('noise256.png')
|
|
curshader.add_uniform('sampler2D snoise256', link='$noise256.png')
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
scale = parse_value_input(node.inputs[1])
|
|
if node.coloring == 'INTENSITY':
|
|
res = 'vec3(tex_voronoi({0} * {1}).a)'.format(co, scale)
|
|
else: # CELLS
|
|
res = 'tex_voronoi({0} * {1}).rgb'.format(co, scale)
|
|
if sample_bump:
|
|
write_bump(node, res)
|
|
return res
|
|
|
|
elif node.type == 'TEX_WAVE':
|
|
curshader.add_function(c_functions.str_tex_wave)
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
scale = parse_value_input(node.inputs[1])
|
|
res = 'vec3(tex_wave_f({0} * {1}))'.format(co, scale)
|
|
if sample_bump:
|
|
write_bump(node, res)
|
|
return res
|
|
|
|
elif node.type == 'BRIGHTCONTRAST':
|
|
out_col = parse_vector_input(node.inputs[0])
|
|
bright = parse_value_input(node.inputs[1])
|
|
contr = parse_value_input(node.inputs[2])
|
|
curshader.add_function(c_functions.str_brightcontrast)
|
|
return 'brightcontrast({0}, {1}, {2})'.format(out_col, bright, contr)
|
|
|
|
elif node.type == 'GAMMA':
|
|
out_col = parse_vector_input(node.inputs[0])
|
|
gamma = parse_value_input(node.inputs[1])
|
|
return 'pow({0}, vec3({1}))'.format(out_col, gamma)
|
|
|
|
elif node.type == 'HUE_SAT':
|
|
curshader.add_function(c_functions.str_hue_sat)
|
|
hue = parse_value_input(node.inputs[0])
|
|
sat = parse_value_input(node.inputs[1])
|
|
val = parse_value_input(node.inputs[2])
|
|
fac = parse_value_input(node.inputs[3])
|
|
col = parse_vector_input(node.inputs[4])
|
|
return 'hue_sat({0}, vec4({1}-0.5, {2}, {3}, 1.0-{4}))'.format(col, hue, sat, val, fac)
|
|
|
|
elif node.type == 'INVERT':
|
|
fac = parse_value_input(node.inputs[0])
|
|
out_col = parse_vector_input(node.inputs[1])
|
|
return 'mix({0}, vec3(1.0) - ({0}), {1})'.format(out_col, fac)
|
|
|
|
elif node.type == 'MIX_RGB':
|
|
fac = parse_value_input(node.inputs[0])
|
|
fac_var = node_name(node.name) + '_fac'
|
|
curshader.write('float {0} = {1};'.format(fac_var, fac))
|
|
col1 = parse_vector_input(node.inputs[1])
|
|
col2 = parse_vector_input(node.inputs[2])
|
|
blend = node.blend_type
|
|
if blend == 'MIX':
|
|
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac_var)
|
|
elif blend == 'ADD':
|
|
out_col = 'mix({0}, {0} + {1}, {2})'.format(col1, col2, fac_var)
|
|
elif blend == 'MULTIPLY':
|
|
out_col = 'mix({0}, {0} * {1}, {2})'.format(col1, col2, fac_var)
|
|
elif blend == 'SUBTRACT':
|
|
out_col = 'mix({0}, {0} - {1}, {2})'.format(col1, col2, fac_var)
|
|
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_var)
|
|
elif blend == 'DIVIDE':
|
|
out_col = '(vec3((1.0 - {2}) * {0} + {2} * {0} / {1}))'.format(col1, col2, fac_var)
|
|
elif blend == 'DIFFERENCE':
|
|
out_col = 'mix({0}, abs({0} - {1}), {2})'.format(col1, col2, fac_var)
|
|
elif blend == 'DARKEN':
|
|
out_col = 'min({0}, {1} * {2})'.format(col1, col2, fac_var)
|
|
elif blend == 'LIGHTEN':
|
|
out_col = 'max({0}, {1} * {2})'.format(col1, col2, fac_var)
|
|
elif blend == 'OVERLAY':
|
|
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac_var) # Revert to mix
|
|
elif blend == 'DODGE':
|
|
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac_var) # Revert to mix
|
|
elif blend == 'BURN':
|
|
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac_var) # Revert to mix
|
|
elif blend == 'HUE':
|
|
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac_var) # Revert to mix
|
|
elif blend == 'SATURATION':
|
|
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac_var) # Revert to mix
|
|
elif blend == 'VALUE':
|
|
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac_var) # Revert to mix
|
|
elif blend == 'COLOR':
|
|
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac_var) # Revert to mix
|
|
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)
|
|
elif blend == 'LINEAR_LIGHT':
|
|
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac_var) # Revert to mix
|
|
# out_col = '({0} + {2} * (2.0 * ({1} - vec3(0.5))))'.format(col1, col2, fac_var)
|
|
if node.use_clamp:
|
|
return 'clamp({0}, vec3(0.0), vec3(1.0))'.format(out_col)
|
|
else:
|
|
return out_col
|
|
|
|
elif node.type == 'BLACKBODY':
|
|
|
|
t = float(parse_value_input(node.inputs[0]))
|
|
|
|
rgb = [0,0,0]
|
|
|
|
blackbody_table_r = [
|
|
[2.52432244e+03, -1.06185848e-03, 3.11067539e+00],
|
|
[3.37763626e+03, -4.34581697e-04, 1.64843306e+00],
|
|
[4.10671449e+03, -8.61949938e-05, 6.41423749e-01],
|
|
[4.66849800e+03, 2.85655028e-05, 1.29075375e-01],
|
|
[4.60124770e+03, 2.89727618e-05, 1.48001316e-01],
|
|
[3.78765709e+03, 9.36026367e-06, 3.98995841e-01]
|
|
]
|
|
|
|
blackbody_table_g = [
|
|
[-7.50343014e+02, 3.15679613e-04, 4.73464526e-01],
|
|
[-1.00402363e+03, 1.29189794e-04, 9.08181524e-01],
|
|
[-1.22075471e+03, 2.56245413e-05, 1.20753416e+00],
|
|
[-1.42546105e+03, -4.01730887e-05, 1.44002695e+00],
|
|
[-1.18134453e+03, -2.18913373e-05, 1.30656109e+00],
|
|
[-5.00279505e+02, -4.59745390e-06, 1.09090465e+00]
|
|
]
|
|
|
|
blackbody_table_b = [
|
|
[0.0, 0.0, 0.0, 0.0],
|
|
[0.0, 0.0, 0.0, 0.0],
|
|
[0.0, 0.0, 0.0, 0.0],
|
|
[-2.02524603e-11, 1.79435860e-07, -2.60561875e-04, -1.41761141e-02],
|
|
[-2.22463426e-13, -1.55078698e-08, 3.81675160e-04, -7.30646033e-01],
|
|
[6.72595954e-13, -2.73059993e-08, 4.24068546e-04, -7.52204323e-01]
|
|
]
|
|
|
|
if (t >= 12000):
|
|
|
|
rgb[0] = 0.826270103
|
|
rgb[1] = 0.994478524
|
|
rgb[2] = 1.56626022
|
|
|
|
elif (t < 965.0):
|
|
|
|
rgb[0] = 4.70366907
|
|
rgb[1] = 0.0
|
|
rgb[2] = 0.0
|
|
|
|
else:
|
|
|
|
if (t >= 6365.0):
|
|
i = 5
|
|
elif(t >= 3315.0):
|
|
i = 4
|
|
elif(t >= 1902.0):
|
|
i = 3
|
|
elif(t >= 1449.0):
|
|
i = 2
|
|
elif(t >= 1167.0):
|
|
i = 1
|
|
else:
|
|
i = 0
|
|
|
|
r = blackbody_table_r[i]
|
|
g = blackbody_table_g[i]
|
|
b = blackbody_table_b[i]
|
|
|
|
t_inv = 1.0 / t
|
|
|
|
rgb[0] = r[0] * t_inv + r[1] * t + r[2]
|
|
rgb[1] = g[0] * t_inv + g[1] * t + g[2]
|
|
rgb[2] = ((b[0] * t + b[1]) * t + b[2]) * t + b[3]
|
|
|
|
# Pass constant
|
|
return to_vec3([rgb[0], rgb[1], rgb[2]])
|
|
|
|
elif node.type == 'VALTORGB': # ColorRamp
|
|
fac = parse_value_input(node.inputs[0])
|
|
interp = node.color_ramp.interpolation
|
|
elems = node.color_ramp.elements
|
|
if len(elems) == 1:
|
|
return to_vec3(elems[0].color)
|
|
# Write cols array
|
|
cols_var = node_name(node.name) + '_cols'
|
|
curshader.write('vec3 {0}[{1}];'.format(cols_var, len(elems))) # TODO: Make const
|
|
for i in range(0, len(elems)):
|
|
curshader.write('{0}[{1}] = vec3({2}, {3}, {4});'.format(cols_var, i, elems[i].color[0], elems[i].color[1], elems[i].color[2]))
|
|
# Get index
|
|
fac_var = node_name(node.name) + '_fac'
|
|
curshader.write('float {0} = {1};'.format(fac_var, fac))
|
|
index = '0'
|
|
for i in range(1, len(elems)):
|
|
index += ' + ({0} > {1} ? 1 : 0)'.format(fac_var, elems[i].position)
|
|
# Write index
|
|
index_var = node_name(node.name) + '_i'
|
|
curshader.write('int {0} = {1};'.format(index_var, index))
|
|
if interp == 'CONSTANT':
|
|
return '{0}[{1}]'.format(cols_var, index_var)
|
|
else: # Linear
|
|
# Write facs array
|
|
facs_var = node_name(node.name) + '_facs'
|
|
curshader.write('float {0}[{1}];'.format(facs_var, len(elems))) # TODO: Make const
|
|
for i in range(0, len(elems)):
|
|
curshader.write('{0}[{1}] = {2};'.format(facs_var, i, elems[i].position))
|
|
# Mix color
|
|
# float f = (pos - start) * (1.0 / (finish - start))
|
|
return 'mix({0}[{1}], {0}[{1} + 1], ({2} - {3}[{1}]) * (1.0 / ({3}[{1} + 1] - {3}[{1}]) ))'.format(cols_var, index_var, fac_var, facs_var)
|
|
|
|
elif node.type == 'CURVE_VEC': # Vector Curves
|
|
fac = parse_value_input(node.inputs[0])
|
|
vec = parse_vector_input(node.inputs[1])
|
|
curves = node.mapping.curves
|
|
name = node_name(node.name)
|
|
# mapping.curves[0].points[0].handle_type # bezier curve
|
|
return '(vec3({0}, {1}, {2}) * {3})'.format(\
|
|
vector_curve(name + '0', vec + '.x', curves[0].points), vector_curve(name + '1', vec + '.y', curves[1].points), vector_curve(name + '2', vec + '.z', curves[2].points), fac)
|
|
|
|
elif node.type == 'CURVE_RGB': # RGB Curves
|
|
fac = parse_value_input(node.inputs[0])
|
|
vec = parse_vector_input(node.inputs[1])
|
|
curves = node.mapping.curves
|
|
name = node_name(node.name)
|
|
# mapping.curves[0].points[0].handle_type
|
|
return '(sqrt(vec3({0}, {1}, {2}) * vec3({4}, {5}, {6})) * {3})'.format(\
|
|
vector_curve(name + '0', vec + '.x', curves[0].points), vector_curve(name + '1', vec + '.y', curves[1].points), vector_curve(name + '2', vec + '.z', curves[2].points), fac,\
|
|
vector_curve(name + '3a', vec + '.x', curves[3].points), vector_curve(name + '3b', vec + '.y', curves[3].points), vector_curve(name + '3c', vec + '.z', curves[3].points))
|
|
|
|
elif node.type == 'COMBHSV':
|
|
curshader.add_function(c_functions.str_hue_sat)
|
|
h = parse_value_input(node.inputs[0])
|
|
s = parse_value_input(node.inputs[1])
|
|
v = parse_value_input(node.inputs[2])
|
|
return 'hsv_to_rgb(vec3({0}, {1}, {2}))'.format(h,s,v)
|
|
|
|
elif node.type == 'COMBRGB':
|
|
r = parse_value_input(node.inputs[0])
|
|
g = parse_value_input(node.inputs[1])
|
|
b = parse_value_input(node.inputs[2])
|
|
return 'vec3({0}, {1}, {2})'.format(r, g, b)
|
|
|
|
elif node.type == 'WAVELENGTH':
|
|
curshader.add_function(c_functions.str_wavelength_to_rgb)
|
|
wl = parse_value_input(node.inputs[0])
|
|
# Roughly map to cycles - 450 to 600 nanometers
|
|
return 'wavelength_to_rgb(({0} - 450.0) / 150.0)'.format(wl)
|
|
|
|
# Vector
|
|
|
|
elif node.type == 'CAMERA':
|
|
# View Vector in camera space
|
|
return 'vVecCam'
|
|
|
|
elif node.type == 'NEW_GEOMETRY':
|
|
if socket == node.outputs[0]: # Position
|
|
return 'wposition'
|
|
elif socket == node.outputs[1]: # Normal
|
|
return 'n' if curshader.shader_type == 'frag' else 'wnormal'
|
|
elif socket == node.outputs[2]: # Tangent
|
|
return 'wtangent'
|
|
elif socket == node.outputs[3]: # True Normal
|
|
return 'n' if curshader.shader_type == 'frag' else 'wnormal'
|
|
elif socket == node.outputs[4]: # Incoming
|
|
return 'vVec'
|
|
elif socket == node.outputs[5]: # Parametric
|
|
return 'mposition'
|
|
|
|
elif node.type == 'HAIR_INFO':
|
|
return 'vec3(0.0)' # Tangent Normal
|
|
|
|
elif node.type == 'OBJECT_INFO':
|
|
return 'wposition'
|
|
|
|
elif node.type == 'PARTICLE_INFO':
|
|
if socket == node.outputs[3]: # Location
|
|
particle_info['location'] = True
|
|
return 'p_location' if arm.utils.get_rp().arm_particles == 'On' else 'vec3(0.0)'
|
|
elif socket == node.outputs[5]: # Velocity
|
|
particle_info['velocity'] = True
|
|
return 'p_velocity' if arm.utils.get_rp().arm_particles == 'On' else 'vec3(0.0)'
|
|
elif socket == node.outputs[6]: # Angular Velocity
|
|
particle_info['angular_velocity'] = True
|
|
return 'vec3(0.0)'
|
|
|
|
elif node.type == 'TANGENT':
|
|
return 'wtangent'
|
|
|
|
elif node.type == 'TEX_COORD':
|
|
#obj = node.object
|
|
#instance = node.from_instance
|
|
if socket == node.outputs[0]: # Generated - bounds
|
|
return 'bposition'
|
|
elif socket == node.outputs[1]: # Normal
|
|
return 'n'
|
|
elif socket == node.outputs[2]: # UV
|
|
con.add_elem('tex', 'short2norm')
|
|
return 'vec3(texCoord.x, 1.0 - texCoord.y, 0.0)'
|
|
elif socket == node.outputs[3]: # Object
|
|
return 'mposition'
|
|
elif socket == node.outputs[4]: # Camera
|
|
return 'vec3(0.0)' # 'vposition'
|
|
elif socket == node.outputs[5]: # Window
|
|
return 'vec3(0.0)' # 'wvpposition'
|
|
elif socket == node.outputs[6]: # Reflection
|
|
return 'vec3(0.0)'
|
|
|
|
elif node.type == 'UVMAP':
|
|
#instance = node.from_instance
|
|
con.add_elem('tex', 'short2norm')
|
|
mat = mat_get_material()
|
|
mat_users = mat_get_material_users()
|
|
if mat_users != None and mat in mat_users:
|
|
mat_user = mat_users[mat][0]
|
|
if hasattr(mat_user.data, 'uv_layers'):
|
|
lays = mat_user.data.uv_layers
|
|
# Second uvmap referenced
|
|
if len(lays) > 1 and node.uv_map == lays[1].name:
|
|
con.add_elem('tex1', 'short2norm')
|
|
return 'vec3(texCoord1.x, 1.0 - texCoord1.y, 0.0)'
|
|
return 'vec3(texCoord.x, 1.0 - texCoord.y, 0.0)'
|
|
|
|
elif node.type == 'BUMP':
|
|
# Interpolation strength
|
|
strength = parse_value_input(node.inputs[0])
|
|
# Height multiplier
|
|
# distance = parse_value_input(node.inputs[1])
|
|
sample_bump = True
|
|
height = parse_value_input(node.inputs[2])
|
|
sample_bump = False
|
|
nor = parse_vector_input(node.inputs[3])
|
|
if sample_bump_res != '':
|
|
if node.invert:
|
|
ext = ['1', '2', '3', '4']
|
|
else:
|
|
ext = ['2', '1', '4', '3']
|
|
curshader.write('float {0}_fh1 = {0}_{1} - {0}_{2}; float {0}_fh2 = {0}_{3} - {0}_{4};'.format(sample_bump_res, ext[0], ext[1], ext[2], ext[3]))
|
|
curshader.write('{0}_fh1 *= ({1}) * 3.0; {0}_fh2 *= ({1}) * 3.0;'.format(sample_bump_res, strength))
|
|
curshader.write('vec3 {0}_a = normalize(vec3(2.0, 0.0, {0}_fh1));'.format(sample_bump_res))
|
|
curshader.write('vec3 {0}_b = normalize(vec3(0.0, 2.0, {0}_fh2));'.format(sample_bump_res))
|
|
res = 'normalize(mat3({0}_a, {0}_b, normalize(vec3({0}_fh1, {0}_fh2, 2.0))) * n)'.format(sample_bump_res)
|
|
sample_bump_res = ''
|
|
else:
|
|
res = 'n'
|
|
return res
|
|
|
|
elif node.type == 'MAPPING':
|
|
out = parse_vector_input(node.inputs[0])
|
|
scale = node.inputs['Scale'].default_value
|
|
rotation = node.inputs['Rotation'].default_value
|
|
location = node.inputs['Location'].default_value if node.inputs['Location'].enabled else [0.0, 0.0, 0.0]
|
|
if scale[0] != 1.0 or scale[1] != 1.0 or scale[2] != 1.0:
|
|
out = '({0} * vec3({1}, {2}, {3}))'.format(out, scale[0], scale[1], scale[2])
|
|
if rotation[2] != 0.0:
|
|
# ZYX rotation, Z axis for now..
|
|
a = rotation[2]
|
|
# x * cos(theta) - y * sin(theta)
|
|
# x * sin(theta) + y * cos(theta)
|
|
out = 'vec3({0}.x * {1} - ({0}.y) * {2}, {0}.x * {2} + ({0}.y) * {1}, 0.0)'.format(out, math.cos(a), math.sin(a))
|
|
# if node.rotation[1] != 0.0:
|
|
# a = node.rotation[1]
|
|
# out = 'vec3({0}.x * {1} - {0}.z * {2}, {0}.x * {2} + {0}.z * {1}, 0.0)'.format(out, math.cos(a), math.sin(a))
|
|
# if node.rotation[0] != 0.0:
|
|
# a = node.rotation[0]
|
|
# out = 'vec3({0}.y * {1} - {0}.z * {2}, {0}.y * {2} + {0}.z * {1}, 0.0)'.format(out, math.cos(a), math.sin(a))
|
|
|
|
if location[0] != 0.0 or location[1] != 0.0 or location[2] != 0.0:
|
|
out = '({0} + vec3({1}, {2}, {3}))'.format(out, location[0], location[1], location[2])
|
|
# use Extension parameter from the Texture node instead
|
|
# if node.use_min:
|
|
# out = 'max({0}, vec3({1}, {2}, {3}))'.format(out, node.min[0], node.min[1])
|
|
# if node.use_max:
|
|
# out = 'min({0}, vec3({1}, {2}, {3}))'.format(out, node.max[0], node.max[1])
|
|
return out
|
|
|
|
elif node.type == 'NORMAL':
|
|
if socket == node.outputs[0]:
|
|
return to_vec3(node.outputs[0].default_value)
|
|
elif socket == node.outputs[1]: # TODO: is parse_value path preferred?
|
|
nor = parse_vector_input(node.inputs[0])
|
|
return 'vec3(dot({0}, {1}))'.format(to_vec3(node.outputs[0].default_value), nor)
|
|
|
|
elif node.type == 'NORMAL_MAP':
|
|
if curshader == tese:
|
|
return parse_vector_input(node.inputs[1])
|
|
else:
|
|
#space = node.space
|
|
#map = node.uv_map
|
|
# Color
|
|
parse_normal_map_color_input(node.inputs[1], node.inputs[0])
|
|
return None
|
|
|
|
elif node.type == 'VECT_TRANSFORM':
|
|
#type = node.vector_type
|
|
#conv_from = node.convert_from
|
|
#conv_to = node.convert_to
|
|
# Pass throuh
|
|
return parse_vector_input(node.inputs[0])
|
|
|
|
elif node.type == 'COMBXYZ':
|
|
x = parse_value_input(node.inputs[0])
|
|
y = parse_value_input(node.inputs[1])
|
|
z = parse_value_input(node.inputs[2])
|
|
return 'vec3({0}, {1}, {2})'.format(x, y, z)
|
|
|
|
elif node.type == 'VECT_MATH':
|
|
vec1 = parse_vector_input(node.inputs[0])
|
|
vec2 = parse_vector_input(node.inputs[1])
|
|
op = node.operation
|
|
if op == 'ADD':
|
|
return '({0} + {1})'.format(vec1, vec2)
|
|
elif op == 'SUBTRACT':
|
|
return '({0} - {1})'.format(vec1, vec2)
|
|
elif op == 'AVERAGE':
|
|
return '(({0} + {1}) / 2.0)'.format(vec1, vec2)
|
|
elif op == 'DOT_PRODUCT':
|
|
return 'vec3(dot({0}, {1}))'.format(vec1, vec2)
|
|
elif op == 'CROSS_PRODUCT':
|
|
return 'cross({0}, {1})'.format(vec1, vec2)
|
|
elif op == 'NORMALIZE':
|
|
return 'normalize({0})'.format(vec1)
|
|
|
|
elif node.type == 'DISPLACEMENT':
|
|
height = parse_value_input(node.inputs[0])
|
|
midlevel = parse_value_input(node.inputs[1])
|
|
scale = parse_value_input(node.inputs[2])
|
|
nor = parse_vector_input(node.inputs[3])
|
|
return '(vec3({0}) * {1})'.format(height, scale)
|
|
|
|
def parse_normal_map_color_input(inp, strength_input=None):
|
|
global normal_parsed
|
|
global frag
|
|
if basecol_only:
|
|
return
|
|
if inp.is_linked == False:
|
|
return
|
|
if normal_parsed:
|
|
return
|
|
normal_parsed = True
|
|
frag.write_normal += 1
|
|
if not get_arm_export_tangents() or mat_get_material().arm_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('vec3 n = ({0}) * 2.0 - 1.0;'.format(parse_vector_input(inp)))
|
|
if strength_input != None:
|
|
strength = parse_value_input(strength_input)
|
|
if strength != '1.0':
|
|
frag.write('n.xy *= {0};'.format(strength))
|
|
frag.write('n = normalize(TBN * n);')
|
|
con.add_elem('tang', 'short4norm')
|
|
frag.write_normal -= 1
|
|
|
|
def parse_value_input(inp):
|
|
if inp.is_linked:
|
|
l = inp.links[0]
|
|
|
|
if l.from_node.type == 'REROUTE':
|
|
return parse_value_input(l.from_node.inputs[0])
|
|
|
|
res_var = write_result(l)
|
|
st = l.from_socket.type
|
|
if st == 'RGB' or st == 'RGBA' or st == 'VECTOR':
|
|
return '{0}.x'.format(res_var)
|
|
else: # VALUE
|
|
return res_var
|
|
else:
|
|
if mat_batch() and inp.is_uniform:
|
|
return to_uniform(inp)
|
|
else:
|
|
return to_vec1(inp.default_value)
|
|
|
|
def parse_value(node, socket):
|
|
global particle_info
|
|
global sample_bump
|
|
|
|
if node.type == 'GROUP':
|
|
if node.node_tree.name.startswith('Armory PBR'):
|
|
# Displacement
|
|
if socket == node.outputs[1]:
|
|
return parse_value_input(node.inputs[7])
|
|
else:
|
|
return None
|
|
else:
|
|
return parse_group(node, socket)
|
|
|
|
elif node.type == 'GROUP_INPUT':
|
|
return parse_group_input(node, socket)
|
|
|
|
elif node.type == 'ATTRIBUTE':
|
|
# Pass time till drivers are implemented
|
|
if node.attribute_name == 'time':
|
|
curshader.add_uniform('float time', link='_time')
|
|
return 'time'
|
|
else:
|
|
return '0.0'
|
|
|
|
elif node.type == 'CAMERA':
|
|
# View Z Depth
|
|
if socket == node.outputs[1]:
|
|
curshader.add_include('std/math.glsl')
|
|
curshader.add_uniform('vec2 cameraProj', link='_cameraPlaneProj')
|
|
return 'linearize(gl_FragCoord.z, cameraProj)'
|
|
# View Distance
|
|
else:
|
|
curshader.add_uniform('vec3 eye', link='_cameraPosition')
|
|
return 'distance(eye, wposition)'
|
|
|
|
elif node.type == 'FRESNEL':
|
|
curshader.add_function(c_functions.str_fresnel)
|
|
ior = parse_value_input(node.inputs[0])
|
|
if node.inputs[1].is_linked:
|
|
dotnv = 'dot({0}, vVec)'.format(parse_vector_input(node.inputs[1]))
|
|
else:
|
|
dotnv = 'dotNV'
|
|
return 'fresnel({0}, {1})'.format(ior, dotnv)
|
|
|
|
elif node.type == 'NEW_GEOMETRY':
|
|
if socket == node.outputs[6]: # Backfacing
|
|
return '(1.0 - float(gl_FrontFacing))'
|
|
elif socket == node.outputs[7]: # Pointiness
|
|
return '0.0'
|
|
|
|
elif node.type == 'HAIR_INFO':
|
|
# Is Strand
|
|
# Intercept
|
|
# Thickness
|
|
return '0.5'
|
|
|
|
elif node.type == 'LAYER_WEIGHT':
|
|
blend = parse_value_input(node.inputs[0])
|
|
if node.inputs[1].is_linked:
|
|
dotnv = 'dot({0}, vVec)'.format(parse_vector_input(node.inputs[1]))
|
|
else:
|
|
dotnv = 'dotNV'
|
|
if socket == node.outputs[0]: # Fresnel
|
|
curshader.add_function(c_functions.str_fresnel)
|
|
return 'fresnel(1.0 / (1.0 - {0}), {1})'.format(blend, dotnv)
|
|
elif socket == node.outputs[1]: # Facing
|
|
return '(1.0 - pow({0}, ({1} < 0.5) ? 2.0 * {1} : 0.5 / (1.0 - {1})))'.format(dotnv, blend)
|
|
|
|
elif node.type == 'LIGHT_PATH':
|
|
if socket == node.outputs[0]: # Is Camera Ray
|
|
return '1.0'
|
|
elif socket == node.outputs[1]: # Is Shadow Ray
|
|
return '0.0'
|
|
elif socket == node.outputs[2]: # Is Diffuse Ray
|
|
return '1.0'
|
|
elif socket == node.outputs[3]: # Is Glossy Ray
|
|
return '1.0'
|
|
elif socket == node.outputs[4]: # Is Singular Ray
|
|
return '0.0'
|
|
elif socket == node.outputs[5]: # Is Reflection Ray
|
|
return '0.0'
|
|
elif socket == node.outputs[6]: # Is Transmission Ray
|
|
return '0.0'
|
|
elif socket == node.outputs[7]: # Ray Length
|
|
return '0.0'
|
|
elif socket == node.outputs[8]: # Ray Depth
|
|
return '0.0'
|
|
elif socket == node.outputs[9]: # Transparent Depth
|
|
return '0.0'
|
|
elif socket == node.outputs[10]: # Transmission Depth
|
|
return '0.0'
|
|
|
|
elif node.type == 'OBJECT_INFO':
|
|
if socket == node.outputs[2]: # Object Index
|
|
curshader.add_uniform('float objectInfoIndex', link='_objectInfoIndex')
|
|
return 'objectInfoIndex'
|
|
elif socket == node.outputs[3]: # Material Index
|
|
curshader.add_uniform('float objectInfoMaterialIndex', link='_objectInfoMaterialIndex')
|
|
return 'objectInfoMaterialIndex'
|
|
elif socket == node.outputs[4]: # Random
|
|
curshader.add_uniform('float objectInfoRandom', link='_objectInfoRandom')
|
|
return 'objectInfoRandom'
|
|
|
|
elif node.type == 'PARTICLE_INFO':
|
|
if socket == node.outputs[0]: # Index
|
|
particle_info['index'] = True
|
|
return 'p_index' if arm.utils.get_rp().arm_particles == 'On' else '0.0'
|
|
elif socket == node.outputs[1]: # Age
|
|
particle_info['age'] = True
|
|
return 'p_age' if arm.utils.get_rp().arm_particles == 'On' else '0.0'
|
|
elif socket == node.outputs[2]: # Lifetime
|
|
particle_info['lifetime'] = True
|
|
return 'p_lifetime' if arm.utils.get_rp().arm_particles == 'On' else '0.0'
|
|
elif socket == node.outputs[4]: # Size
|
|
particle_info['size'] = True
|
|
return '1.0'
|
|
|
|
elif node.type == 'VALUE':
|
|
if node.arm_material_param:
|
|
nn = 'param_' + node_name(node.name)
|
|
curshader.add_uniform('float {0}'.format(nn), link='{0}'.format(node.name))
|
|
return nn
|
|
else:
|
|
return to_vec1(node.outputs[0].default_value)
|
|
|
|
elif node.type == 'WIREFRAME':
|
|
#node.use_pixel_size
|
|
# size = parse_value_input(node.inputs[0])
|
|
return '0.0'
|
|
|
|
elif node.type == 'TEX_BRICK':
|
|
curshader.add_function(c_functions.str_tex_brick)
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
scale = parse_value_input(node.inputs[4])
|
|
res = 'tex_brick_f({0} * {1})'.format(co, scale)
|
|
if sample_bump:
|
|
write_bump(node, res)
|
|
return res
|
|
|
|
elif node.type == 'TEX_CHECKER':
|
|
curshader.add_function(c_functions.str_tex_checker)
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
scale = parse_value_input(node.inputs[3])
|
|
res = 'tex_checker_f({0}, {1})'.format(co, scale)
|
|
if sample_bump:
|
|
write_bump(node, res)
|
|
return res
|
|
|
|
elif node.type == 'TEX_GRADIENT':
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
grad = node.gradient_type
|
|
if grad == 'LINEAR':
|
|
f = '{0}.x'.format(co)
|
|
elif grad == 'QUADRATIC':
|
|
f = '0.0'
|
|
elif grad == 'EASING':
|
|
f = '0.0'
|
|
elif grad == 'DIAGONAL':
|
|
f = '({0}.x + {0}.y) * 0.5'.format(co)
|
|
elif grad == 'RADIAL':
|
|
f = 'atan({0}.y, {0}.x) / PI2 + 0.5'.format(co)
|
|
elif grad == 'QUADRATIC_SPHERE':
|
|
f = '0.0'
|
|
elif grad == 'SPHERICAL':
|
|
f = 'max(1.0 - sqrt({0}.x * {0}.x + {0}.y * {0}.y + {0}.z * {0}.z), 0.0)'.format(co)
|
|
res = '(clamp({0}, 0.0, 1.0))'.format(f)
|
|
if sample_bump:
|
|
write_bump(node, res)
|
|
return res
|
|
|
|
elif node.type == 'TEX_IMAGE':
|
|
# Already fetched
|
|
if is_parsed(store_var_name(node)):
|
|
return '{0}.a'.format(store_var_name(node))
|
|
tex_name = safesrc(node.name)
|
|
tex = make_texture(node, tex_name)
|
|
tex_link = node.name if node.arm_material_param else None
|
|
if tex != None:
|
|
curshader.write_textures += 1
|
|
res = '{0}.a'.format(texture_store(node, tex, tex_name, tex_link=tex_link))
|
|
curshader.write_textures -= 1
|
|
return res
|
|
elif node.image == None: # Empty texture
|
|
tex = {}
|
|
tex['name'] = tex_name
|
|
tex['file'] = ''
|
|
return '{0}.a'.format(texture_store(node, tex, tex_name, True, tex_link=tex_link))
|
|
else:
|
|
tex_store = store_var_name(node) # Pink color for missing texture
|
|
curshader.write('vec4 {0} = vec4(1.0, 0.0, 1.0, 1.0);'.format(tex_store))
|
|
return '{0}.a'.format(tex_store)
|
|
|
|
elif node.type == 'TEX_MAGIC':
|
|
curshader.add_function(c_functions.str_tex_magic)
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
scale = parse_value_input(node.inputs[1])
|
|
res = 'tex_magic_f({0} * {1} * 4.0)'.format(co, scale)
|
|
if sample_bump:
|
|
write_bump(node, res, 0.1)
|
|
return res
|
|
|
|
elif node.type == 'TEX_MUSGRAVE':
|
|
# Fall back to noise
|
|
curshader.add_function(c_functions.str_tex_musgrave)
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
scale = parse_value_input(node.inputs[1])
|
|
# detail = parse_value_input(node.inputs[2])
|
|
# distortion = parse_value_input(node.inputs[3])
|
|
res = 'tex_musgrave_f({0} * {1} * 0.5)'.format(co, scale)
|
|
if sample_bump:
|
|
write_bump(node, res)
|
|
return res
|
|
|
|
elif node.type == 'TEX_NOISE':
|
|
curshader.add_function(c_functions.str_tex_noise)
|
|
assets_add(get_sdk_path() + '/armory/Assets/' + 'noise256.png')
|
|
assets_add_embedded_data('noise256.png')
|
|
curshader.add_uniform('sampler2D snoise256', link='$noise256.png')
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
scale = parse_value_input(node.inputs[1])
|
|
# detail = parse_value_input(node.inputs[2])
|
|
# distortion = parse_value_input(node.inputs[3])
|
|
res = 'tex_noise({0} * {1})'.format(co, scale)
|
|
if sample_bump:
|
|
write_bump(node, res, 0.1)
|
|
return res
|
|
|
|
elif node.type == 'TEX_POINTDENSITY':
|
|
return '0.0'
|
|
|
|
elif node.type == 'TEX_VORONOI':
|
|
curshader.add_function(c_functions.str_tex_voronoi)
|
|
assets_add(get_sdk_path() + '/armory/Assets/' + 'noise256.png')
|
|
assets_add_embedded_data('noise256.png')
|
|
curshader.add_uniform('sampler2D snoise256', link='$noise256.png')
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
scale = parse_value_input(node.inputs[1])
|
|
if node.coloring == 'INTENSITY':
|
|
res = 'tex_voronoi({0} * {1}).a'.format(co, scale)
|
|
else: # CELLS
|
|
res = 'tex_voronoi({0} * {1}).r'.format(co, scale)
|
|
if sample_bump:
|
|
write_bump(node, res)
|
|
return res
|
|
|
|
elif node.type == 'TEX_WAVE':
|
|
curshader.add_function(c_functions.str_tex_wave)
|
|
if node.inputs[0].is_linked:
|
|
co = parse_vector_input(node.inputs[0])
|
|
else:
|
|
co = 'bposition'
|
|
scale = parse_value_input(node.inputs[1])
|
|
res = 'tex_wave_f({0} * {1})'.format(co, scale)
|
|
if sample_bump:
|
|
write_bump(node, res)
|
|
return res
|
|
|
|
elif node.type == 'LIGHT_FALLOFF':
|
|
# Constant, linear, quadratic
|
|
# Shaders default to quadratic for now
|
|
return '1.0'
|
|
|
|
elif node.type == 'NORMAL':
|
|
nor = parse_vector_input(node.inputs[0])
|
|
return 'dot({0}, {1})'.format(to_vec3(node.outputs[0].default_value), nor)
|
|
|
|
elif node.type == 'VALTORGB': # ColorRamp
|
|
return '1.0'
|
|
|
|
elif node.type == 'MATH':
|
|
val1 = parse_value_input(node.inputs[0])
|
|
val2 = parse_value_input(node.inputs[1])
|
|
op = node.operation
|
|
if op == 'ADD':
|
|
out_val = '({0} + {1})'.format(val1, val2)
|
|
elif op == 'SUBTRACT':
|
|
out_val = '({0} - {1})'.format(val1, val2)
|
|
elif op == 'MULTIPLY':
|
|
out_val = '({0} * {1})'.format(val1, val2)
|
|
elif op == 'DIVIDE':
|
|
out_val = '({0} / {1})'.format(val1, val2)
|
|
elif op == 'POWER':
|
|
out_val = 'pow({0}, {1})'.format(val1, val2)
|
|
elif op == 'LOGARITHM':
|
|
out_val = 'log({0})'.format(val1)
|
|
elif op == 'SQRT':
|
|
out_val = 'sqrt({0})'.format(val1)
|
|
elif op == 'ABSOLUTE':
|
|
out_val = 'abs({0})'.format(val1)
|
|
elif op == 'MINIMUM':
|
|
out_val = 'min({0}, {1})'.format(val1, val2)
|
|
elif op == 'MAXIMUM':
|
|
out_val = 'max({0}, {1})'.format(val1, val2)
|
|
elif op == 'LESS_THAN':
|
|
out_val = 'float({0} < {1})'.format(val1, val2)
|
|
elif op == 'GREATER_THAN':
|
|
out_val = 'float({0} > {1})'.format(val1, val2)
|
|
elif op == 'ROUND':
|
|
# out_val = 'round({0})'.format(val1)
|
|
out_val = 'floor({0} + 0.5)'.format(val1)
|
|
elif op == 'FLOOR':
|
|
out_val = 'floor({0})'.format(val1)
|
|
elif op == 'CEIL':
|
|
out_val = 'ceil({0})'.format(val1)
|
|
elif op == 'FRACT':
|
|
out_val = 'fract({0})'.format(val1)
|
|
elif op == 'MODULO':
|
|
# out_val = 'float({0} % {1})'.format(val1, val2)
|
|
out_val = 'mod({0}, {1})'.format(val1, val2)
|
|
elif op == 'SINE':
|
|
out_val = 'sin({0})'.format(val1)
|
|
elif op == 'COSINE':
|
|
out_val = 'cos({0})'.format(val1)
|
|
elif op == 'TANGENT':
|
|
out_val = 'tan({0})'.format(val1)
|
|
elif op == 'ARCSINE':
|
|
out_val = 'asin({0})'.format(val1)
|
|
elif op == 'ARCCOSINE':
|
|
out_val = 'acos({0})'.format(val1)
|
|
elif op == 'ARCTANGENT':
|
|
out_val = 'atan({0})'.format(val1)
|
|
elif op == 'ARCTAN2':
|
|
out_val = 'atan({0}, {1})'.format(val1, val2)
|
|
if node.use_clamp:
|
|
return 'clamp({0}, 0.0, 1.0)'.format(out_val)
|
|
else:
|
|
return out_val
|
|
|
|
elif node.type == 'RGBTOBW':
|
|
col = parse_vector_input(node.inputs[0])
|
|
return '((({0}.r * 0.3 + {0}.g * 0.59 + {0}.b * 0.11) / 3.0) * 2.5)'.format(col)
|
|
|
|
elif node.type == 'SEPHSV':
|
|
return '0.0'
|
|
|
|
elif node.type == 'SEPRGB':
|
|
col = parse_vector_input(node.inputs[0])
|
|
if socket == node.outputs[0]:
|
|
return '{0}.r'.format(col)
|
|
elif socket == node.outputs[1]:
|
|
return '{0}.g'.format(col)
|
|
elif socket == node.outputs[2]:
|
|
return '{0}.b'.format(col)
|
|
|
|
elif node.type == 'SEPXYZ':
|
|
vec = parse_vector_input(node.inputs[0])
|
|
if socket == node.outputs[0]:
|
|
return '{0}.x'.format(vec)
|
|
elif socket == node.outputs[1]:
|
|
return '{0}.y'.format(vec)
|
|
elif socket == node.outputs[2]:
|
|
return '{0}.z'.format(vec)
|
|
|
|
elif node.type == 'VECT_MATH':
|
|
vec1 = parse_vector_input(node.inputs[0])
|
|
vec2 = parse_vector_input(node.inputs[1])
|
|
op = node.operation
|
|
if op == 'DOT_PRODUCT':
|
|
return 'dot({0}, {1})'.format(vec1, vec2)
|
|
else:
|
|
return '0.0'
|
|
|
|
##
|
|
|
|
def vector_curve(name, fac, points):
|
|
# Write Ys array
|
|
ys_var = name + '_ys'
|
|
curshader.write('float {0}[{1}];'.format(ys_var, len(points))) # TODO: Make const
|
|
for i in range(0, len(points)):
|
|
curshader.write('{0}[{1}] = {2};'.format(ys_var, i, points[i].location[1]))
|
|
# Get index
|
|
fac_var = name + '_fac'
|
|
curshader.write('float {0} = {1};'.format(fac_var, fac))
|
|
index = '0'
|
|
for i in range(1, len(points)):
|
|
index += ' + ({0} > {1} ? 1 : 0)'.format(fac_var, points[i].location[0])
|
|
# Write index
|
|
index_var = name + '_i'
|
|
curshader.write('int {0} = {1};'.format(index_var, index))
|
|
# Linear
|
|
# Write Xs array
|
|
facs_var = name + '_xs'
|
|
curshader.write('float {0}[{1}];'.format(facs_var, len(points))) # TODO: Make const
|
|
for i in range(0, len(points)):
|
|
curshader.write('{0}[{1}] = {2};'.format(facs_var, i, points[i].location[0]))
|
|
# Map vector
|
|
return 'mix({0}[{1}], {0}[{1} + 1], ({2} - {3}[{1}]) * (1.0 / ({3}[{1} + 1] - {3}[{1}]) ))'.format(ys_var, index_var, fac_var, facs_var)
|
|
|
|
def write_normal(inp):
|
|
if inp.is_linked and inp.links[0].from_node.type != 'GROUP_INPUT':
|
|
normal_res = parse_vector_input(inp)
|
|
if normal_res != None:
|
|
curshader.write('n = {0};'.format(normal_res))
|
|
|
|
def is_parsed(s):
|
|
global parsed
|
|
return s in parsed
|
|
|
|
def res_var_name(node, socket):
|
|
return node_name(node.name) + '_' + safesrc(socket.name) + '_res'
|
|
|
|
def write_result(l):
|
|
global parsed
|
|
res_var = res_var_name(l.from_node, l.from_socket)
|
|
# Unparsed node
|
|
if not is_parsed(res_var):
|
|
parsed[res_var] = True
|
|
st = l.from_socket.type
|
|
if st == 'RGB' or st == 'RGBA' or st == 'VECTOR':
|
|
res = parse_vector(l.from_node, l.from_socket)
|
|
if res == None:
|
|
return None
|
|
curshader.write('vec3 {0} = {1};'.format(res_var, res))
|
|
elif st == 'VALUE':
|
|
res = parse_value(l.from_node, l.from_socket)
|
|
if res == None:
|
|
return None
|
|
curshader.write('float {0} = {1};'.format(res_var, res))
|
|
# Normal map already parsed, return
|
|
elif l.from_node.type == 'NORMAL_MAP':
|
|
return None
|
|
return res_var
|
|
|
|
def glsl_type(t):
|
|
if t == 'RGB' or t == 'RGBA' or t == 'VECTOR':
|
|
return 'vec3'
|
|
else:
|
|
return 'float'
|
|
|
|
def to_uniform(inp):
|
|
uname = safesrc(inp.node.name) + safesrc(inp.name)
|
|
curshader.add_uniform(glsl_type(inp.type) + ' ' + uname)
|
|
return uname
|
|
|
|
def store_var_name(node):
|
|
return node_name(node.name) + '_store'
|
|
|
|
def texture_store(node, tex, tex_name, to_linear=False, tex_link=None):
|
|
global sample_bump
|
|
global sample_bump_res
|
|
global parsed
|
|
tex_store = store_var_name(node)
|
|
if is_parsed(tex_store):
|
|
return tex_store
|
|
parsed[tex_store] = True
|
|
mat_bind_texture(tex)
|
|
con.add_elem('tex', 'short2norm')
|
|
curshader.add_uniform('sampler2D {0}'.format(tex_name), link=tex_link)
|
|
if node.inputs[0].is_linked:
|
|
uv_name = parse_vector_input(node.inputs[0])
|
|
uv_name = 'vec2({0}.x, 1.0 - {0}.y)'.format(uv_name)
|
|
else:
|
|
uv_name = 'texCoord'
|
|
triplanar = node.projection == 'BOX'
|
|
if triplanar:
|
|
curshader.write(f'vec3 texCoordBlend = vec3(0.0); vec2 {uv_name}1 = vec2(0.0); vec2 {uv_name}2 = vec2(0.0);') # Temp
|
|
curshader.write(f'vec4 {tex_store} = vec4(0.0, 0.0, 0.0, 0.0);')
|
|
curshader.write(f'if (texCoordBlend.x > 0) {tex_store} += texture({tex_name}, {uv_name}.xy) * texCoordBlend.x;')
|
|
curshader.write(f'if (texCoordBlend.y > 0) {tex_store} += texture({tex_name}, {uv_name}1.xy) * texCoordBlend.y;')
|
|
curshader.write(f'if (texCoordBlend.z > 0) {tex_store} += texture({tex_name}, {uv_name}2.xy) * texCoordBlend.z;')
|
|
else:
|
|
if mat_texture_grad():
|
|
curshader.write('vec4 {0} = textureGrad({1}, {2}.xy, g2.xy, g2.zw);'.format(tex_store, tex_name, uv_name))
|
|
else:
|
|
curshader.write('vec4 {0} = texture({1}, {2}.xy);'.format(tex_store, tex_name, uv_name))
|
|
if sample_bump:
|
|
sample_bump_res = tex_store
|
|
curshader.write('float {0}_1 = textureOffset({1}, {2}.xy, ivec2(-2, 0)).r;'.format(tex_store, tex_name, uv_name))
|
|
curshader.write('float {0}_2 = textureOffset({1}, {2}.xy, ivec2(2, 0)).r;'.format(tex_store, tex_name, uv_name))
|
|
curshader.write('float {0}_3 = textureOffset({1}, {2}.xy, ivec2(0, -2)).r;'.format(tex_store, tex_name, uv_name))
|
|
curshader.write('float {0}_4 = textureOffset({1}, {2}.xy, ivec2(0, 2)).r;'.format(tex_store, tex_name, uv_name))
|
|
sample_bump = False
|
|
if to_linear:
|
|
curshader.write('{0}.rgb = pow({0}.rgb, vec3(2.2));'.format(tex_store))
|
|
return tex_store
|
|
|
|
def write_bump(node, res, scl=0.001):
|
|
global sample_bump
|
|
global sample_bump_res
|
|
sample_bump_res = store_var_name(node) + '_bump'
|
|
# Testing.. get function parts..
|
|
ar = res.split('(', 1)
|
|
pre = ar[0] + '('
|
|
if ',' in ar[1]:
|
|
ar2 = ar[1].split(',', 1)
|
|
co = ar2[0]
|
|
post = ',' + ar2[1]
|
|
else:
|
|
co = ar[1][:-1]
|
|
post = ')'
|
|
curshader.write('float {0}_1 = {1}{2} + vec3(-{4}, 0.0, 0.0){3};'.format(sample_bump_res, pre, co, post, scl))
|
|
curshader.write('float {0}_2 = {1}{2} + vec3({4}, 0.0, {4}){3};'.format(sample_bump_res, pre, co, post, scl))
|
|
curshader.write('float {0}_3 = {1}{2} + vec3(0.0, -{4}, 0.0){3};'.format(sample_bump_res, pre, co, post, scl))
|
|
curshader.write('float {0}_4 = {1}{2} + vec3(0.0, {4}, -{4}){3};'.format(sample_bump_res, pre, co, post, scl))
|
|
sample_bump = False
|
|
|
|
def to_vec1(v):
|
|
return str(v)
|
|
|
|
def to_vec3(v):
|
|
return 'vec3({0}, {1}, {2})'.format(v[0], v[1], v[2])
|
|
|
|
def node_by_type(nodes, ntype):
|
|
for n in nodes:
|
|
if n.type == ntype:
|
|
return n
|
|
|
|
def socket_index(node, socket):
|
|
for i in range(0, len(node.outputs)):
|
|
if node.outputs[i] == socket:
|
|
return i
|
|
|
|
def node_name(s):
|
|
for p in parents:
|
|
s = p.name + '_' + s
|
|
if curshader.write_textures > 0:
|
|
s += '_texread'
|
|
s = safesrc(s)
|
|
if '__' in s: # Consecutive _ are reserved
|
|
s = s.replace('_', '_x')
|
|
return s
|
|
|
|
##
|
|
|
|
def make_texture(image_node, tex_name, matname=None):
|
|
tex = {}
|
|
tex['name'] = tex_name
|
|
image = image_node.image
|
|
if matname is None:
|
|
matname = mat_state.material.name
|
|
|
|
if image is None:
|
|
return None
|
|
|
|
# Get filepath
|
|
filepath = image.filepath
|
|
if filepath == '':
|
|
if image.packed_file is not None:
|
|
filepath = './' + image.name
|
|
has_ext = filepath.endswith(('.jpg', '.png', '.hdr'))
|
|
if not has_ext:
|
|
# Raw bytes, write converted .jpg to /unpacked
|
|
filepath += '.raw'
|
|
|
|
elif image.source == "GENERATED":
|
|
unpack_path = os.path.join(arm.utils.get_fp_build(), 'compiled', 'Assets', 'unpacked')
|
|
if not os.path.exists(unpack_path):
|
|
os.makedirs(unpack_path)
|
|
|
|
filepath = os.path.join(unpack_path, image.name + ".jpg")
|
|
arm.utils.convert_image(image, filepath, "JPEG")
|
|
|
|
else:
|
|
arm.log.warn(matname + '/' + image.name + ' - invalid file path')
|
|
return None
|
|
|
|
# Reference image name
|
|
texpath = arm.utils.asset_path(filepath)
|
|
texfile = arm.utils.extract_filename(filepath)
|
|
tex['file'] = arm.utils.safestr(texfile)
|
|
s = tex['file'].rsplit('.', 1)
|
|
|
|
if len(s) == 1:
|
|
arm.log.warn(matname + '/' + image.name + ' - file extension required for image name')
|
|
return None
|
|
|
|
ext = s[1].lower()
|
|
do_convert = ext not in ('jpg', 'png', 'hdr', 'mp4') # Convert image
|
|
if do_convert:
|
|
new_ext = 'png' if (ext in ('tga', 'dds')) else 'jpg'
|
|
tex['file'] = tex['file'].rsplit('.', 1)[0] + '.' + new_ext
|
|
|
|
if image.packed_file is not None or not is_ascii(texfile):
|
|
# Extract packed data / copy non-ascii texture
|
|
unpack_path = os.path.join(arm.utils.get_fp_build(), 'compiled', 'Assets', 'unpacked')
|
|
if not os.path.exists(unpack_path):
|
|
os.makedirs(unpack_path)
|
|
unpack_filepath = os.path.join(unpack_path, tex['file'])
|
|
|
|
if do_convert:
|
|
if not os.path.isfile(unpack_filepath):
|
|
fmt = 'PNG' if new_ext == 'png' else 'JPEG'
|
|
arm.utils.convert_image(image, unpack_filepath, file_format=fmt)
|
|
else:
|
|
|
|
# Write bytes if size is different or file does not exist yet
|
|
if image.packed_file is not None:
|
|
if not os.path.isfile(unpack_filepath) or os.path.getsize(unpack_filepath) != image.packed_file.size:
|
|
with open(unpack_filepath, 'wb') as f:
|
|
f.write(image.packed_file.data)
|
|
# Copy non-ascii texture
|
|
else:
|
|
if not os.path.isfile(unpack_filepath) or os.path.getsize(unpack_filepath) != os.path.getsize(texpath):
|
|
shutil.copy(texpath, unpack_filepath)
|
|
|
|
arm.assets.add(unpack_filepath)
|
|
|
|
else:
|
|
if not os.path.isfile(arm.utils.asset_path(filepath)):
|
|
arm.log.warn('Material ' + matname + '/' + image.name + ' - file not found(' + filepath + ')')
|
|
return None
|
|
|
|
if do_convert:
|
|
unpack_path = os.path.join(arm.utils.get_fp_build(), 'compiled', 'Assets', 'unpacked')
|
|
if not os.path.exists(unpack_path):
|
|
os.makedirs(unpack_path)
|
|
converted_path = os.path.join(unpack_path, tex['file'])
|
|
# TODO: delete cache when file changes
|
|
if not os.path.isfile(converted_path):
|
|
fmt = 'PNG' if new_ext == 'png' else 'JPEG'
|
|
arm.utils.convert_image(image, converted_path, file_format=fmt)
|
|
arm.assets.add(converted_path)
|
|
else:
|
|
# Link image path to assets
|
|
# TODO: Khamake converts .PNG to .jpg? Convert ext to lowercase on windows
|
|
if arm.utils.get_os() == 'win':
|
|
s = filepath.rsplit('.', 1)
|
|
arm.assets.add(arm.utils.asset_path(s[0] + '.' + s[1].lower()))
|
|
else:
|
|
arm.assets.add(arm.utils.asset_path(filepath))
|
|
|
|
|
|
# if image_format != 'RGBA32':
|
|
# tex['format'] = image_format
|
|
|
|
interpolation = image_node.interpolation
|
|
rpdat = arm.utils.get_rp()
|
|
texfilter = rpdat.arm_texture_filter
|
|
if texfilter == 'Anisotropic':
|
|
interpolation = 'Smart'
|
|
elif texfilter == 'Linear':
|
|
interpolation = 'Linear'
|
|
elif texfilter == 'Point':
|
|
interpolation = 'Closest'
|
|
|
|
# TODO: Blender seems to load full images on size request, cache size instead
|
|
powimage = is_pow(image.size[0]) and is_pow(image.size[1])
|
|
|
|
if interpolation == 'Cubic': # Mipmap linear
|
|
tex['mipmap_filter'] = 'linear'
|
|
tex['generate_mipmaps'] = True
|
|
elif interpolation == 'Smart': # Mipmap anisotropic
|
|
tex['min_filter'] = 'anisotropic'
|
|
tex['mipmap_filter'] = 'linear'
|
|
tex['generate_mipmaps'] = True
|
|
elif interpolation == 'Closest':
|
|
tex['min_filter'] = 'point'
|
|
tex['mag_filter'] = 'point'
|
|
# else defaults to linear
|
|
|
|
if image_node.extension != 'REPEAT': # Extend or clip
|
|
tex['u_addressing'] = 'clamp'
|
|
tex['v_addressing'] = 'clamp'
|
|
|
|
if image.source == 'MOVIE':
|
|
tex['source'] = 'movie'
|
|
tex['min_filter'] = 'linear'
|
|
tex['mag_filter'] = 'linear'
|
|
tex['mipmap_filter'] = 'no'
|
|
tex['generate_mipmaps'] = False
|
|
|
|
return tex
|
|
|
|
def is_pow(num):
|
|
return ((num & (num - 1)) == 0) and num != 0
|
|
|
|
def is_ascii(s):
|
|
return len(s) == len(s.encode())
|
|
|
|
##
|
|
|
|
def get_rp_renderer():
|
|
return arm.utils.get_rp().rp_renderer
|
|
|
|
def get_arm_export_tangents():
|
|
return bpy.data.worlds['Arm'].arm_export_tangents
|
|
|
|
def safesrc(name):
|
|
return arm.utils.safesrc(name)
|
|
|
|
def get_sdk_path():
|
|
return arm.utils.get_sdk_path()
|
|
|
|
def disp_enabled():
|
|
return arm.utils.disp_enabled(arm.make_state.target)
|
|
|
|
def warn(text):
|
|
arm.log.warn(text)
|
|
|
|
def assets_add(path):
|
|
arm.assets.add(path)
|
|
|
|
def assets_add_embedded_data(path):
|
|
arm.assets.add_embedded_data(path)
|
|
|
|
def mat_name():
|
|
return mat_state.material.name
|
|
|
|
def mat_batch():
|
|
return mat_state.batch
|
|
|
|
def mat_bind_texture(tex):
|
|
mat_state.bind_textures.append(tex)
|
|
|
|
def mat_texture_grad():
|
|
return mat_state.texture_grad
|
|
|
|
def mat_get_material():
|
|
return mat_state.material
|
|
|
|
def mat_get_material_users():
|
|
return mat_state.mat_users
|