226 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			226 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								import subprocess
							 | 
						||
| 
								 | 
							
								from typing import Dict, List, Tuple
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import bpy
							 | 
						||
| 
								 | 
							
								from bpy.types import Material
							 | 
						||
| 
								 | 
							
								from bpy.types import Object
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import lnx.api
							 | 
						||
| 
								 | 
							
								import lnx.assets as assets
							 | 
						||
| 
								 | 
							
								import lnx.exporter
							 | 
						||
| 
								 | 
							
								import lnx.log as log
							 | 
						||
| 
								 | 
							
								import lnx.material.cycles as cycles
							 | 
						||
| 
								 | 
							
								import lnx.material.make_decal as make_decal
							 | 
						||
| 
								 | 
							
								import lnx.material.make_depth as make_depth
							 | 
						||
| 
								 | 
							
								import lnx.material.make_mesh as make_mesh
							 | 
						||
| 
								 | 
							
								import lnx.material.make_overlay as make_overlay
							 | 
						||
| 
								 | 
							
								import lnx.material.make_transluc as make_transluc
							 | 
						||
| 
								 | 
							
								import lnx.material.make_refract as make_refract
							 | 
						||
| 
								 | 
							
								import lnx.material.make_voxel as make_voxel
							 | 
						||
| 
								 | 
							
								import lnx.material.mat_state as mat_state
							 | 
						||
| 
								 | 
							
								import lnx.material.mat_utils as mat_utils
							 | 
						||
| 
								 | 
							
								from lnx.material.shader import Shader, ShaderContext, ShaderData
							 | 
						||
| 
								 | 
							
								import lnx.utils
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if lnx.is_reload(__name__):
							 | 
						||
| 
								 | 
							
								    lnx.api = lnx.reload_module(lnx.api)
							 | 
						||
| 
								 | 
							
								    assets = lnx.reload_module(assets)
							 | 
						||
| 
								 | 
							
								    lnx.exporter = lnx.reload_module(lnx.exporter)
							 | 
						||
| 
								 | 
							
								    log = lnx.reload_module(log)
							 | 
						||
| 
								 | 
							
								    cycles = lnx.reload_module(cycles)
							 | 
						||
| 
								 | 
							
								    make_decal = lnx.reload_module(make_decal)
							 | 
						||
| 
								 | 
							
								    make_depth = lnx.reload_module(make_depth)
							 | 
						||
| 
								 | 
							
								    make_mesh = lnx.reload_module(make_mesh)
							 | 
						||
| 
								 | 
							
								    make_overlay = lnx.reload_module(make_overlay)
							 | 
						||
| 
								 | 
							
								    make_transluc = lnx.reload_module(make_transluc)
							 | 
						||
| 
								 | 
							
								    make_voxel = lnx.reload_module(make_voxel)
							 | 
						||
| 
								 | 
							
								    mat_state = lnx.reload_module(mat_state)
							 | 
						||
| 
								 | 
							
								    mat_utils = lnx.reload_module(mat_utils)
							 | 
						||
| 
								 | 
							
								    lnx.material.shader = lnx.reload_module(lnx.material.shader)
							 | 
						||
| 
								 | 
							
								    from lnx.material.shader import Shader, ShaderContext, ShaderData
							 | 
						||
| 
								 | 
							
								    lnx.utils = lnx.reload_module(lnx.utils)
							 | 
						||
| 
								 | 
							
								else:
							 | 
						||
| 
								 | 
							
								    lnx.enable_reload(__name__)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								rpass_hook = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def build(material: Material, mat_users: Dict[Material, List[Object]], mat_lnxusers) -> Tuple:
							 | 
						||
| 
								 | 
							
								    mat_state.mat_users = mat_users
							 | 
						||
| 
								 | 
							
								    mat_state.mat_lnxusers = mat_lnxusers
							 | 
						||
| 
								 | 
							
								    mat_state.material = material
							 | 
						||
| 
								 | 
							
								    mat_state.nodes = material.node_tree.nodes
							 | 
						||
| 
								 | 
							
								    mat_state.data = ShaderData(material)
							 | 
						||
| 
								 | 
							
								    mat_state.output_node = cycles.node_by_type(mat_state.nodes, 'OUTPUT_MATERIAL')
							 | 
						||
| 
								 | 
							
								    if mat_state.output_node is None:
							 | 
						||
| 
								 | 
							
								        # Place empty material output to keep compiler happy..
							 | 
						||
| 
								 | 
							
								        mat_state.output_node = mat_state.nodes.new('ShaderNodeOutputMaterial')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    wrd = bpy.data.worlds['Lnx']
							 | 
						||
| 
								 | 
							
								    rpdat = lnx.utils.get_rp()
							 | 
						||
| 
								 | 
							
								    rpasses = mat_utils.get_rpasses(material)
							 | 
						||
| 
								 | 
							
								    matname = lnx.utils.safesrc(lnx.utils.asset_name(material))
							 | 
						||
| 
								 | 
							
								    rel_path = lnx.utils.build_dir() + '/compiled/Shaders/'
							 | 
						||
| 
								 | 
							
								    full_path = lnx.utils.get_fp() + '/' + rel_path
							 | 
						||
| 
								 | 
							
								    if not os.path.exists(full_path):
							 | 
						||
| 
								 | 
							
								        os.makedirs(full_path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    make_instancing_and_skinning(material, mat_users)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    bind_constants = dict()
							 | 
						||
| 
								 | 
							
								    bind_textures = dict()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for rp in rpasses:
							 | 
						||
| 
								 | 
							
								        car = []
							 | 
						||
| 
								 | 
							
								        bind_constants[rp] = car
							 | 
						||
| 
								 | 
							
								        mat_state.bind_constants = car
							 | 
						||
| 
								 | 
							
								        tar = []
							 | 
						||
| 
								 | 
							
								        bind_textures[rp] = tar
							 | 
						||
| 
								 | 
							
								        mat_state.bind_textures = tar
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        con = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if rpdat.rp_driver != 'Leenkx' and lnx.api.drivers[rpdat.rp_driver]['make_rpass'] is not None:
							 | 
						||
| 
								 | 
							
								            con = lnx.api.drivers[rpdat.rp_driver]['make_rpass'](rp)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if con is not None:
							 | 
						||
| 
								 | 
							
								            pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        elif rp == 'mesh':
							 | 
						||
| 
								 | 
							
								            con = make_mesh.make(rp, rpasses)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        elif rp == 'shadowmap':
							 | 
						||
| 
								 | 
							
								            con = make_depth.make(rp, rpasses, shadowmap=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        elif rp == 'shadowmap_transparent':
							 | 
						||
| 
								 | 
							
								            con = make_depth.make(rp, rpasses, shadowmap=True, shadowmap_transparent=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        elif rp == 'translucent':
							 | 
						||
| 
								 | 
							
								            con = make_transluc.make(rp)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        elif rp == 'refraction':
							 | 
						||
| 
								 | 
							
								            con = make_refract.make(rp)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        elif rp == 'overlay':
							 | 
						||
| 
								 | 
							
								            con = make_overlay.make(rp)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        elif rp == 'decal':
							 | 
						||
| 
								 | 
							
								            con = make_decal.make(rp)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        elif rp == 'depth':
							 | 
						||
| 
								 | 
							
								            con = make_depth.make(rp, rpasses)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        elif rp == 'voxel':
							 | 
						||
| 
								 | 
							
								            con = make_voxel.make(rp)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        elif rpass_hook is not None:
							 | 
						||
| 
								 | 
							
								            con = rpass_hook(rp)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        write_shaders(rel_path, con, rp, matname)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    shader_data_name = matname + '_data'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if wrd.lnx_single_data_file:
							 | 
						||
| 
								 | 
							
								        if 'shader_datas' not in lnx.exporter.current_output:
							 | 
						||
| 
								 | 
							
								            lnx.exporter.current_output['shader_datas'] = []
							 | 
						||
| 
								 | 
							
								        lnx.exporter.current_output['shader_datas'].append(mat_state.data.get()['shader_datas'][0])
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        lnx.utils.write_lnx(full_path + '/' + matname + '_data.lnx', mat_state.data.get())
							 | 
						||
| 
								 | 
							
								        shader_data_path = lnx.utils.get_fp_build() + '/compiled/Shaders/' + shader_data_name + '.lnx'
							 | 
						||
| 
								 | 
							
								        assets.add_shader_data(shader_data_path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return rpasses, mat_state.data, shader_data_name, bind_constants, bind_textures
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def write_shaders(rel_path: str, con: ShaderContext, rpass: str, matname: str) -> None:
							 | 
						||
| 
								 | 
							
								    keep_cache = mat_state.material.lnx_cached
							 | 
						||
| 
								 | 
							
								    write_shader(rel_path, con.vert, 'vert', rpass, matname, keep_cache=keep_cache)
							 | 
						||
| 
								 | 
							
								    write_shader(rel_path, con.frag, 'frag', rpass, matname, keep_cache=keep_cache)
							 | 
						||
| 
								 | 
							
								    write_shader(rel_path, con.geom, 'geom', rpass, matname, keep_cache=keep_cache)
							 | 
						||
| 
								 | 
							
								    write_shader(rel_path, con.tesc, 'tesc', rpass, matname, keep_cache=keep_cache)
							 | 
						||
| 
								 | 
							
								    write_shader(rel_path, con.tese, 'tese', rpass, matname, keep_cache=keep_cache)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def write_shader(rel_path: str, shader: Shader, ext: str, rpass: str, matname: str, keep_cache=True) -> None:
							 | 
						||
| 
								 | 
							
								    if shader is None or shader.is_linked:
							 | 
						||
| 
								 | 
							
								        return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # TODO: blend context
							 | 
						||
| 
								 | 
							
								    if rpass == 'mesh' and mat_state.material.lnx_blending:
							 | 
						||
| 
								 | 
							
								        rpass = 'blend'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    file_ext = '.glsl'
							 | 
						||
| 
								 | 
							
								    if shader.noprocessing:
							 | 
						||
| 
								 | 
							
								        # Use hlsl directly
							 | 
						||
| 
								 | 
							
								        hlsl_dir = lnx.utils.build_dir() + '/compiled/Hlsl/'
							 | 
						||
| 
								 | 
							
								        if not os.path.exists(hlsl_dir):
							 | 
						||
| 
								 | 
							
								            os.makedirs(hlsl_dir)
							 | 
						||
| 
								 | 
							
								        file_ext = '.hlsl'
							 | 
						||
| 
								 | 
							
								        rel_path = rel_path.replace('/compiled/Shaders/', '/compiled/Hlsl/')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    shader_file = matname + '_' + rpass + '.' + ext + file_ext
							 | 
						||
| 
								 | 
							
								    shader_path = lnx.utils.get_fp() + '/' + rel_path + '/' + shader_file
							 | 
						||
| 
								 | 
							
								    assets.add_shader(shader_path)
							 | 
						||
| 
								 | 
							
								    if not os.path.isfile(shader_path) or not keep_cache:
							 | 
						||
| 
								 | 
							
								        with open(shader_path, 'w') as f:
							 | 
						||
| 
								 | 
							
								            f.write(shader.get())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if shader.noprocessing:
							 | 
						||
| 
								 | 
							
								            cwd = os.getcwd()
							 | 
						||
| 
								 | 
							
								            os.chdir(lnx.utils.get_fp() + '/' + rel_path)
							 | 
						||
| 
								 | 
							
								            hlslbin_path = lnx.utils.get_sdk_path() + '/lib/leenkx_tools/hlslbin/hlslbin.exe'
							 | 
						||
| 
								 | 
							
								            prof = 'vs_5_0' if ext == 'vert' else 'ps_5_0' if ext == 'frag' else 'gs_5_0'
							 | 
						||
| 
								 | 
							
								            # noprocessing flag - gets renamed to .d3d11
							 | 
						||
| 
								 | 
							
								            args = [hlslbin_path.replace('/', '\\').replace('\\\\', '\\'), shader_file, shader_file[:-4] + 'glsl', prof]
							 | 
						||
| 
								 | 
							
								            if ext == 'vert':
							 | 
						||
| 
								 | 
							
								                args.append('-i')
							 | 
						||
| 
								 | 
							
								                args.append('pos')
							 | 
						||
| 
								 | 
							
								            proc = subprocess.call(args)
							 | 
						||
| 
								 | 
							
								            os.chdir(cwd)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def make_instancing_and_skinning(mat: Material, mat_users: Dict[Material, List[Object]]) -> None:
							 | 
						||
| 
								 | 
							
								    """Build material with instancing or skinning if enabled.
							 | 
						||
| 
								 | 
							
								    If the material is a custom material, only validation checks for instancing are performed."""
							 | 
						||
| 
								 | 
							
								    global_elems = []
							 | 
						||
| 
								 | 
							
								    if mat_users is not None and mat in mat_users:
							 | 
						||
| 
								 | 
							
								        # Whether there are both an instanced object and a not instanced object with this material
							 | 
						||
| 
								 | 
							
								        instancing_usage = [False, False]
							 | 
						||
| 
								 | 
							
								        mat_state.uses_instancing = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for bo in mat_users[mat]:
							 | 
						||
| 
								 | 
							
								            if mat.lnx_custom_material == '':
							 | 
						||
| 
								 | 
							
								                # Morph Targets
							 | 
						||
| 
								 | 
							
								                if lnx.utils.export_morph_targets(bo):
							 | 
						||
| 
								 | 
							
								                    global_elems.append({'name': 'morph', 'data': 'short2norm'})
							 | 
						||
| 
								 | 
							
								                # GPU Skinning
							 | 
						||
| 
								 | 
							
								                if lnx.utils.export_bone_data(bo):
							 | 
						||
| 
								 | 
							
								                    global_elems.append({'name': 'bone', 'data': 'short4norm'})
							 | 
						||
| 
								 | 
							
								                    global_elems.append({'name': 'weight', 'data': 'short4norm'})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # Instancing
							 | 
						||
| 
								 | 
							
								            inst = bo.lnx_instanced
							 | 
						||
| 
								 | 
							
								            if inst != 'Off' or mat.lnx_particle_flag:
							 | 
						||
| 
								 | 
							
								                instancing_usage[0] = True
							 | 
						||
| 
								 | 
							
								                mat_state.uses_instancing = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                if mat.lnx_custom_material == '':
							 | 
						||
| 
								 | 
							
								                    global_elems.append({'name': 'ipos', 'data': 'float3'})
							 | 
						||
| 
								 | 
							
								                    if 'Rot' in inst:
							 | 
						||
| 
								 | 
							
								                        global_elems.append({'name': 'irot', 'data': 'float3'})
							 | 
						||
| 
								 | 
							
								                    if 'Scale' in inst:
							 | 
						||
| 
								 | 
							
								                        global_elems.append({'name': 'iscl', 'data': 'float3'})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            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'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if instancing_usage[0] and instancing_usage[1]:
							 | 
						||
| 
								 | 
							
								            # Display a warning for invalid instancing configurations
							 | 
						||
| 
								 | 
							
								            # See https://github.com/leenkx3d/leenkx/issues/2072
							 | 
						||
| 
								 | 
							
								            log.warn(f'Material "{mat.name}" has both instanced and not instanced objects, objects might flicker!')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if mat.lnx_custom_material == '':
							 | 
						||
| 
								 | 
							
								        mat_state.data.global_elems = global_elems
							 |