armory/blender/arm/material/make_shader.py

222 lines
8.6 KiB
Python
Raw Normal View History

2017-03-14 20:43:54 +01:00
import os
2018-12-04 19:06:01 +01:00
import subprocess
2020-12-30 01:06:54 +01:00
from typing import Dict, List, Tuple
import bpy
from bpy.types import Material
from bpy.types import Object
import arm.api
2017-03-15 12:30:14 +01:00
import arm.assets as assets
2020-12-30 01:06:54 +01:00
import arm.exporter
import arm.log as log
2017-03-15 12:30:14 +01:00
import arm.material.cycles as cycles
2020-12-30 01:06:54 +01:00
import arm.material.make_decal as make_decal
import arm.material.make_depth as make_depth
2017-03-15 12:30:14 +01:00
import arm.material.make_mesh as make_mesh
import arm.material.make_overlay as make_overlay
2020-12-30 01:06:54 +01:00
import arm.material.make_transluc as make_transluc
2017-03-15 12:30:14 +01:00
import arm.material.make_voxel as make_voxel
2020-12-30 01:06:54 +01:00
import arm.material.mat_state as mat_state
import arm.material.mat_utils as mat_utils
from arm.material.shader import Shader, ShaderContext, ShaderData
import arm.utils
2017-03-14 20:43:54 +01:00
if arm.is_reload(__name__):
2021-08-04 22:49:38 +02:00
arm.api = arm.reload_module(arm.api)
assets = arm.reload_module(assets)
arm.exporter = arm.reload_module(arm.exporter)
log = arm.reload_module(log)
cycles = arm.reload_module(cycles)
make_decal = arm.reload_module(make_decal)
make_depth = arm.reload_module(make_depth)
make_mesh = arm.reload_module(make_mesh)
make_overlay = arm.reload_module(make_overlay)
make_transluc = arm.reload_module(make_transluc)
make_voxel = arm.reload_module(make_voxel)
mat_state = arm.reload_module(mat_state)
mat_utils = arm.reload_module(mat_utils)
arm.material.shader = arm.reload_module(arm.material.shader)
from arm.material.shader import Shader, ShaderContext, ShaderData
arm.utils = arm.reload_module(arm.utils)
else:
arm.enable_reload(__name__)
2021-08-04 22:49:38 +02:00
2017-03-14 20:43:54 +01:00
rpass_hook = None
2020-12-30 01:06:54 +01:00
def build(material: Material, mat_users: Dict[Material, List[Object]], mat_armusers) -> Tuple:
2017-03-14 20:43:54 +01:00
mat_state.mat_users = mat_users
mat_state.mat_armusers = mat_armusers
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:
2017-04-21 11:10:49 +02:00
# Place empty material output to keep compiler happy..
mat_state.output_node = mat_state.nodes.new('ShaderNodeOutputMaterial')
2017-03-14 20:43:54 +01:00
wrd = bpy.data.worlds['Arm']
2018-01-04 16:22:16 +01:00
rpdat = arm.utils.get_rp()
2017-03-14 20:43:54 +01:00
rpasses = mat_utils.get_rpasses(material)
2019-01-23 18:09:53 +01:00
is_emissive = mat_utils.is_emmisive(material)
if is_emissive and '_Emission' not in wrd.world_defs:
wrd.world_defs += '_Emission'
2017-10-06 11:16:29 +02:00
matname = arm.utils.safesrc(arm.utils.asset_name(material))
2017-12-20 10:19:44 +01:00
rel_path = arm.utils.build_dir() + '/compiled/Shaders/'
2017-03-15 12:30:14 +01:00
full_path = arm.utils.get_fp() + '/' + rel_path
2017-03-14 20:43:54 +01:00
if not os.path.exists(full_path):
os.makedirs(full_path)
make_instancing_and_skinning(material, mat_users)
2017-03-14 20:43:54 +01:00
2017-03-21 03:06:38 +01:00
bind_constants = dict()
2017-03-14 20:43:54 +01:00
bind_textures = dict()
for rp in rpasses:
car = []
2017-03-21 03:06:38 +01:00
bind_constants[rp] = car
mat_state.bind_constants = car
2017-03-14 20:43:54 +01:00
tar = []
bind_textures[rp] = tar
mat_state.bind_textures = tar
2018-01-03 15:09:55 +01:00
con = None
2020-12-30 01:06:54 +01:00
if rpdat.rp_driver != 'Armory' and arm.api.drivers[rpdat.rp_driver]['make_rpass'] is not None:
2018-01-04 16:22:16 +01:00
con = arm.api.drivers[rpdat.rp_driver]['make_rpass'](rp)
2018-01-03 15:09:55 +01:00
2020-12-30 01:06:54 +01:00
if con is not None:
2018-01-03 15:09:55 +01:00
pass
2020-06-24 00:18:32 +02:00
2018-01-03 15:09:55 +01:00
elif rp == 'mesh':
2019-01-24 12:47:51 +01:00
con = make_mesh.make(rp, rpasses)
2017-03-14 20:43:54 +01:00
elif rp == 'shadowmap':
2017-10-24 13:13:26 +02:00
con = make_depth.make(rp, rpasses, shadowmap=True)
2017-03-14 20:43:54 +01:00
elif rp == 'translucent':
con = make_transluc.make(rp)
elif rp == 'overlay':
con = make_overlay.make(rp)
elif rp == 'decal':
con = make_decal.make(rp)
elif rp == 'depth':
2017-10-24 13:13:26 +02:00
con = make_depth.make(rp, rpasses)
2017-03-14 20:43:54 +01:00
elif rp == 'voxel':
con = make_voxel.make(rp)
2020-12-30 01:06:54 +01:00
elif rpass_hook is not None:
2017-03-14 20:43:54 +01:00
con = rpass_hook(rp)
2017-05-25 16:48:41 +02:00
2017-10-06 11:16:29 +02:00
write_shaders(rel_path, con, rp, matname)
2017-03-14 20:43:54 +01:00
shader_data_name = matname + '_data'
2019-05-14 11:43:41 +02:00
if wrd.arm_single_data_file:
2020-12-30 01:06:54 +01:00
if 'shader_datas' not in arm.exporter.current_output:
2019-05-14 11:43:41 +02:00
arm.exporter.current_output['shader_datas'] = []
arm.exporter.current_output['shader_datas'].append(mat_state.data.get()['shader_datas'][0])
else:
arm.utils.write_arm(full_path + '/' + matname + '_data.arm', mat_state.data.get())
shader_data_path = arm.utils.get_fp_build() + '/compiled/Shaders/' + shader_data_name + '.arm'
assets.add_shader_data(shader_data_path)
2017-03-14 20:43:54 +01:00
2017-03-21 03:06:38 +01:00
return rpasses, mat_state.data, shader_data_name, bind_constants, bind_textures
2017-03-14 20:43:54 +01:00
2020-12-30 01:06:54 +01:00
def write_shaders(rel_path: str, con: ShaderContext, rpass: str, matname: str) -> None:
2018-12-19 20:10:34 +01:00
keep_cache = mat_state.material.arm_cached
2017-10-06 11:16:29 +02:00
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)
2017-03-14 20:43:54 +01:00
2020-12-30 01:06:54 +01:00
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:
2017-03-14 20:43:54 +01:00
return
2017-09-29 01:18:57 +02:00
# TODO: blend context
if rpass == 'mesh' and mat_state.material.arm_blending:
2017-09-29 01:18:57 +02:00
rpass = 'blend'
2018-12-04 19:06:01 +01:00
file_ext = '.glsl'
if shader.noprocessing:
# Use hlsl directly
hlsl_dir = arm.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 = arm.utils.get_fp() + '/' + rel_path + '/' + shader_file
2018-08-16 20:48:00 +02:00
assets.add_shader(shader_path)
2017-03-14 20:43:54 +01:00
if not os.path.isfile(shader_path) or not keep_cache:
with open(shader_path, 'w') as f:
f.write(shader.get())
2018-12-04 19:06:01 +01:00
if shader.noprocessing:
cwd = os.getcwd()
os.chdir(arm.utils.get_fp() + '/' + rel_path)
hlslbin_path = arm.utils.get_sdk_path() + '/lib/armory_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)
2020-12-30 01:06:54 +01:00
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]
for bo in mat_users[mat]:
if mat.arm_custom_material == '':
2021-10-18 19:52:16 +02:00
# Morph Targets
if arm.utils.export_morph_targets(bo) and not arm.utils.export_vcols(bo):
2021-10-27 16:25:11 +02:00
print("sk and no vcol")
2021-10-18 19:52:16 +02:00
global_elems.append({'name': 'morph', 'data': 'short2norm'})
# GPU Skinning
if arm.utils.export_bone_data(bo):
global_elems.append({'name': 'bone', 'data': 'short4norm'})
global_elems.append({'name': 'weight', 'data': 'short4norm'})
# Instancing
inst = bo.arm_instanced
if inst != 'Off' or mat.arm_particle_flag:
instancing_usage[0] = True
if mat.arm_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.arm_instanced == 'Off'
if instancing_usage[0] and instancing_usage[1]:
# Display a warning for invalid instancing configurations
# See https://github.com/armory3d/armory/issues/2072
log.warn(f'Material "{mat.name}" has both instanced and not instanced objects, objects might flicker!')
if mat.arm_custom_material == '':
mat_state.data.global_elems = global_elems