forked from LeenkxTeam/LNXSDK
		
	
		
			
				
	
	
		
			227 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			227 lines
		
	
	
		
			8.8 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'})
 | 
						|
                    #HACK: checking `mat.arm_particle_flag` to force appending 'iscl' to the particle's vertex shader
 | 
						|
                    if 'Scale' in inst or mat.arm_particle_flag:
 | 
						|
                        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
 |