171 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			171 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								from typing import Dict, List
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import bpy
							 | 
						||
| 
								 | 
							
								from bpy.types import Material
							 | 
						||
| 
								 | 
							
								from bpy.types import Object
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import lnx.log as log
							 | 
						||
| 
								 | 
							
								import lnx.material.cycles as cycles
							 | 
						||
| 
								 | 
							
								import lnx.material.make_shader as make_shader
							 | 
						||
| 
								 | 
							
								import lnx.material.mat_batch as mat_batch
							 | 
						||
| 
								 | 
							
								import lnx.material.mat_utils as mat_utils
							 | 
						||
| 
								 | 
							
								import lnx.node_utils
							 | 
						||
| 
								 | 
							
								import lnx.utils
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if lnx.is_reload(__name__):
							 | 
						||
| 
								 | 
							
								    log = lnx.reload_module(log)
							 | 
						||
| 
								 | 
							
								    cycles = lnx.reload_module(cycles)
							 | 
						||
| 
								 | 
							
								    make_shader = lnx.reload_module(make_shader)
							 | 
						||
| 
								 | 
							
								    mat_batch = lnx.reload_module(mat_batch)
							 | 
						||
| 
								 | 
							
								    mat_utils = lnx.reload_module(mat_utils)
							 | 
						||
| 
								 | 
							
								    lnx.node_utils = lnx.reload_module(lnx.node_utils)
							 | 
						||
| 
								 | 
							
								    lnx.utils = lnx.reload_module(lnx.utils)
							 | 
						||
| 
								 | 
							
								else:
							 | 
						||
| 
								 | 
							
								    lnx.enable_reload(__name__)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def glsl_value(val):
							 | 
						||
| 
								 | 
							
								    if str(type(val)) == "<class 'bpy_prop_array'>":
							 | 
						||
| 
								 | 
							
								        res = []
							 | 
						||
| 
								 | 
							
								        for v in val:
							 | 
						||
| 
								 | 
							
								            res.append(v)
							 | 
						||
| 
								 | 
							
								        return res
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        return val
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]], mat_lnxusers) -> tuple:
							 | 
						||
| 
								 | 
							
								    wrd = bpy.data.worlds['Lnx']
							 | 
						||
| 
								 | 
							
								    rpdat = lnx.utils.get_rp()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Texture caching for material batching
							 | 
						||
| 
								 | 
							
								    batch_cached_textures = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    needs_sss = material_needs_sss(material)
							 | 
						||
| 
								 | 
							
								    if needs_sss and rpdat.rp_sss_state != 'Off' and '_SSS' not in wrd.world_defs:
							 | 
						||
| 
								 | 
							
								        # Must be set before calling make_shader.build()
							 | 
						||
| 
								 | 
							
								        wrd.world_defs += '_SSS'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # No batch - shader data per material
							 | 
						||
| 
								 | 
							
								    if material.lnx_custom_material != '':
							 | 
						||
| 
								 | 
							
								        rpasses = ['mesh']
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        con = {'vertex_elements': []}
							 | 
						||
| 
								 | 
							
								        con['vertex_elements'].append({'name': 'pos', 'data': 'short4norm'})
							 | 
						||
| 
								 | 
							
								        con['vertex_elements'].append({'name': 'nor', 'data': 'short2norm'})
							 | 
						||
| 
								 | 
							
								        con['vertex_elements'].append({'name': 'tex', 'data': 'short2norm'})
							 | 
						||
| 
								 | 
							
								        con['vertex_elements'].append({'name': 'tex1', 'data': 'short2norm'})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        sd = {'contexts': [con]}
							 | 
						||
| 
								 | 
							
								        shader_data_name = material.lnx_custom_material
							 | 
						||
| 
								 | 
							
								        bind_constants = {'mesh': []}
							 | 
						||
| 
								 | 
							
								        bind_textures = {'mesh': []}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        make_shader.make_instancing_and_skinning(material, mat_users)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for idx, item in enumerate(material.lnx_bind_textures_list):
							 | 
						||
| 
								 | 
							
								            if item.uniform_name == '':
							 | 
						||
| 
								 | 
							
								                log.warn(f'Material "{material.name}": skipping export of bind texture at slot {idx + 1} with empty uniform name')
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if item.image is not None:
							 | 
						||
| 
								 | 
							
								                tex = cycles.make_texture(item.image, item.uniform_name, material.name, 'Linear', 'REPEAT')
							 | 
						||
| 
								 | 
							
								                if tex is None:
							 | 
						||
| 
								 | 
							
								                    continue
							 | 
						||
| 
								 | 
							
								                bind_textures['mesh'].append(tex)
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                log.warn(f'Material "{material.name}": skipping export of bind texture at slot {idx + 1} ("{item.uniform_name}") with no image selected')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    elif not wrd.lnx_batch_materials or material.name.startswith('lnxdefault'):
							 | 
						||
| 
								 | 
							
								        rpasses, shader_data, shader_data_name, bind_constants, bind_textures = make_shader.build(material, mat_users, mat_lnxusers)
							 | 
						||
| 
								 | 
							
								        sd = shader_data.sd
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        rpasses, shader_data, shader_data_name, bind_constants, bind_textures = mat_batch.get(material)
							 | 
						||
| 
								 | 
							
								        sd = shader_data.sd
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    sss_used = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Material
							 | 
						||
| 
								 | 
							
								    for rp in rpasses:
							 | 
						||
| 
								 | 
							
								        c = {
							 | 
						||
| 
								 | 
							
								            'name': rp,
							 | 
						||
| 
								 | 
							
								            'bind_constants': [] + bind_constants[rp],
							 | 
						||
| 
								 | 
							
								            'bind_textures': [] + bind_textures[rp],
							 | 
						||
| 
								 | 
							
								            'depth_read': material.lnx_depth_read,
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        mat_data['contexts'].append(c)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if rp == 'mesh':
							 | 
						||
| 
								 | 
							
								            c['bind_constants'].append({'name': 'receiveShadow', 'boolValue': material.lnx_receive_shadow})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if material.lnx_material_id != 0:
							 | 
						||
| 
								 | 
							
								                c['bind_constants'].append({'name': 'materialID', 'intValue': material.lnx_material_id})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                if material.lnx_material_id == 2:
							 | 
						||
| 
								 | 
							
								                    wrd.world_defs += '_Hair'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            elif rpdat.rp_sss_state != 'Off':
							 | 
						||
| 
								 | 
							
								                const = {'name': 'materialID'}
							 | 
						||
| 
								 | 
							
								                if needs_sss:
							 | 
						||
| 
								 | 
							
								                    const['intValue'] = 2
							 | 
						||
| 
								 | 
							
								                    sss_used = True
							 | 
						||
| 
								 | 
							
								                else:
							 | 
						||
| 
								 | 
							
								                    const['intValue'] = 0
							 | 
						||
| 
								 | 
							
								                c['bind_constants'].append(const)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # TODO: Mesh only material batching
							 | 
						||
| 
								 | 
							
								            if wrd.lnx_batch_materials:
							 | 
						||
| 
								 | 
							
								                # Set textures uniforms
							 | 
						||
| 
								 | 
							
								                if len(c['bind_textures']) > 0:
							 | 
						||
| 
								 | 
							
								                    c['bind_textures'] = []
							 | 
						||
| 
								 | 
							
								                    for node in material.node_tree.nodes:
							 | 
						||
| 
								 | 
							
								                        if node.type == 'TEX_IMAGE':
							 | 
						||
| 
								 | 
							
								                            tex_name = lnx.utils.safesrc(node.name)
							 | 
						||
| 
								 | 
							
								                            tex = cycles.make_texture_from_image_node(node, tex_name)
							 | 
						||
| 
								 | 
							
								                            # Empty texture
							 | 
						||
| 
								 | 
							
								                            if tex is None:
							 | 
						||
| 
								 | 
							
								                                tex = {'name': tex_name, 'file': ''}
							 | 
						||
| 
								 | 
							
								                            c['bind_textures'].append(tex)
							 | 
						||
| 
								 | 
							
								                    batch_cached_textures = c['bind_textures']
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                # Set marked inputs as uniforms
							 | 
						||
| 
								 | 
							
								                for node in material.node_tree.nodes:
							 | 
						||
| 
								 | 
							
								                    for inp in node.inputs:
							 | 
						||
| 
								 | 
							
								                        if inp.is_uniform:
							 | 
						||
| 
								 | 
							
								                            uname = lnx.utils.safesrc(inp.node.name) + lnx.utils.safesrc(inp.name)  # Merge with cycles module
							 | 
						||
| 
								 | 
							
								                            c['bind_constants'].append({'name': uname, cycles.glsl_type(inp.type)+'Value': glsl_value(inp.default_value)})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        elif rp == 'translucent' or rp == 'refraction':
							 | 
						||
| 
								 | 
							
								            c['bind_constants'].append({'name': 'receiveShadow', 'boolValue': material.lnx_receive_shadow})
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        elif rp == 'shadowmap':
							 | 
						||
| 
								 | 
							
								            if wrd.lnx_batch_materials:
							 | 
						||
| 
								 | 
							
								                if len(c['bind_textures']) > 0:
							 | 
						||
| 
								 | 
							
								                    c['bind_textures'] = batch_cached_textures
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    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
							 | 
						||
| 
								 | 
							
								        ext = '' if wrd.lnx_minimize and material.lnx_custom_material == "" else '.json'
							 | 
						||
| 
								 | 
							
								        mat_data['shader'] = shader_data_name + ext + '/' + shader_data_name
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return sd, rpasses, sss_used
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def material_needs_sss(material: Material) -> bool:
							 | 
						||
| 
								 | 
							
								    """Check whether the given material requires SSS."""
							 | 
						||
| 
								 | 
							
								    for sss_node in lnx.node_utils.iter_nodes_by_type(material.node_tree, 'SUBSURFACE_SCATTERING'):
							 | 
						||
| 
								 | 
							
								        if sss_node is not None and sss_node.outputs[0].is_linked:
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for sss_node in lnx.node_utils.iter_nodes_by_type(material.node_tree, 'BSDF_PRINCIPLED'):
							 | 
						||
| 
								 | 
							
								        if sss_node is not None and sss_node.outputs[0].is_linked and (sss_node.inputs[1].is_linked or sss_node.inputs[1].default_value != 0.0):
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for sss_node in mat_utils.iter_nodes_leenkxpbr(material.node_tree):
							 | 
						||
| 
								 | 
							
								        if sss_node is not None and sss_node.outputs[0].is_linked and (sss_node.inputs[8].is_linked or sss_node.inputs[8].default_value != 0.0):
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return False
							 |