Merge pull request #2076 from MoritzBrueckner/instancing
Show warnings for invalid instancing configurations + cleanup
This commit is contained in:
commit
97582968bd
|
@ -1,16 +1,15 @@
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import arm.utils
|
from bpy.types import Material
|
||||||
import arm.node_utils
|
from bpy.types import Object
|
||||||
|
|
||||||
|
import arm.material.cycles as cycles
|
||||||
import arm.material.make_shader as make_shader
|
import arm.material.make_shader as make_shader
|
||||||
import arm.material.mat_batch as mat_batch
|
import arm.material.mat_batch as mat_batch
|
||||||
import arm.material.mat_state as mat_state
|
import arm.node_utils
|
||||||
import arm.material.cycles as cycles
|
import arm.utils
|
||||||
|
|
||||||
def glsl_type(t): # Merge with cycles
|
|
||||||
if t == 'RGB' or t == 'RGBA' or t == 'VECTOR':
|
|
||||||
return 'vec3'
|
|
||||||
else:
|
|
||||||
return 'float'
|
|
||||||
|
|
||||||
def glsl_value(val):
|
def glsl_value(val):
|
||||||
if str(type(val)) == "<class 'bpy_prop_array'>":
|
if str(type(val)) == "<class 'bpy_prop_array'>":
|
||||||
|
@ -21,39 +20,27 @@ def glsl_value(val):
|
||||||
else:
|
else:
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def parse(material, mat_data, mat_users, mat_armusers):
|
|
||||||
|
def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]], mat_armusers):
|
||||||
wrd = bpy.data.worlds['Arm']
|
wrd = bpy.data.worlds['Arm']
|
||||||
rpdat = arm.utils.get_rp()
|
rpdat = arm.utils.get_rp()
|
||||||
|
|
||||||
# No batch - shader data per material
|
# No batch - shader data per material
|
||||||
if material.arm_custom_material != '':
|
if material.arm_custom_material != '':
|
||||||
rpasses = ['mesh']
|
rpasses = ['mesh']
|
||||||
sd = {}
|
|
||||||
sd['contexts'] = []
|
con = {'vertex_elements': []}
|
||||||
con = {}
|
con['vertex_elements'].append({'name': 'pos', 'data': 'short4norm'})
|
||||||
con['vertex_elements'] = []
|
con['vertex_elements'].append({'name': 'nor', 'data': 'short2norm'})
|
||||||
elem = {}
|
con['vertex_elements'].append({'name': 'tex', 'data': 'short2norm'})
|
||||||
elem['name'] = 'pos'
|
con['vertex_elements'].append({'name': 'tex1', 'data': 'short2norm'})
|
||||||
elem['data'] = 'short4norm'
|
|
||||||
con['vertex_elements'].append(elem)
|
sd = {'contexts': [con]}
|
||||||
elem = {}
|
|
||||||
elem['name'] = 'nor'
|
|
||||||
elem['data'] = 'short2norm'
|
|
||||||
con['vertex_elements'].append(elem)
|
|
||||||
elem = {}
|
|
||||||
elem['name'] = 'tex'
|
|
||||||
elem['data'] = 'short2norm'
|
|
||||||
con['vertex_elements'].append(elem)
|
|
||||||
elem = {}
|
|
||||||
elem['name'] = 'tex1'
|
|
||||||
elem['data'] = 'short2norm'
|
|
||||||
con['vertex_elements'].append(elem)
|
|
||||||
sd['contexts'].append(con)
|
|
||||||
shader_data_name = material.arm_custom_material
|
shader_data_name = material.arm_custom_material
|
||||||
bind_constants = {}
|
bind_constants = {'mesh': []}
|
||||||
bind_constants['mesh'] = []
|
bind_textures = {'mesh': []}
|
||||||
bind_textures = {}
|
|
||||||
bind_textures['mesh'] = []
|
make_shader.make_instancing_and_skinning(material, mat_users)
|
||||||
elif not wrd.arm_batch_materials or material.name.startswith('armdefault'):
|
elif not wrd.arm_batch_materials or material.name.startswith('armdefault'):
|
||||||
rpasses, shader_data, shader_data_name, bind_constants, bind_textures = make_shader.build(material, mat_users, mat_armusers)
|
rpasses, shader_data, shader_data_name, bind_constants, bind_textures = make_shader.build(material, mat_users, mat_armusers)
|
||||||
sd = shader_data.sd
|
sd = shader_data.sd
|
||||||
|
@ -63,39 +50,35 @@ def parse(material, mat_data, mat_users, mat_armusers):
|
||||||
|
|
||||||
# Material
|
# Material
|
||||||
for rp in rpasses:
|
for rp in rpasses:
|
||||||
|
c = {
|
||||||
c = {}
|
'name': rp,
|
||||||
c['name'] = rp
|
'bind_constants': [] + bind_constants[rp],
|
||||||
c['bind_constants'] = [] + bind_constants[rp]
|
'bind_textures': [] + bind_textures[rp],
|
||||||
c['bind_textures'] = [] + bind_textures[rp]
|
}
|
||||||
mat_data['contexts'].append(c)
|
mat_data['contexts'].append(c)
|
||||||
|
|
||||||
if rp == 'mesh':
|
if rp == 'mesh':
|
||||||
const = {}
|
c['bind_constants'].append({'name': 'receiveShadow', 'bool': material.arm_receive_shadow})
|
||||||
const['name'] = 'receiveShadow'
|
|
||||||
const['bool'] = material.arm_receive_shadow
|
|
||||||
c['bind_constants'].append(const)
|
|
||||||
|
|
||||||
if material.arm_material_id != 0:
|
if material.arm_material_id != 0:
|
||||||
const = {}
|
c['bind_constants'].append({'name': 'materialID', 'int': material.arm_material_id})
|
||||||
const['name'] = 'materialID'
|
|
||||||
const['int'] = material.arm_material_id
|
|
||||||
c['bind_constants'].append(const)
|
|
||||||
if material.arm_material_id == 2:
|
if material.arm_material_id == 2:
|
||||||
wrd.world_defs += '_Hair'
|
wrd.world_defs += '_Hair'
|
||||||
|
|
||||||
elif rpdat.rp_sss_state == 'On':
|
elif rpdat.rp_sss_state == 'On':
|
||||||
sss = False
|
sss = False
|
||||||
sss_node = arm.node_utils.get_node_by_type(material.node_tree, 'SUBSURFACE_SCATTERING')
|
sss_node = arm.node_utils.get_node_by_type(material.node_tree, 'SUBSURFACE_SCATTERING')
|
||||||
if sss_node != None and sss_node.outputs[0].is_linked: # Check linked node
|
if sss_node is not None and sss_node.outputs[0].is_linked: # Check linked node
|
||||||
sss = True
|
sss = True
|
||||||
sss_node = arm.node_utils.get_node_by_type(material.node_tree, 'BSDF_PRINCIPLED')
|
sss_node = arm.node_utils.get_node_by_type(material.node_tree, 'BSDF_PRINCIPLED')
|
||||||
if sss_node != None and sss_node.outputs[0].is_linked and (sss_node.inputs[1].is_linked or sss_node.inputs[1].default_value != 0.0):
|
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):
|
||||||
sss = True
|
sss = True
|
||||||
sss_node = arm.node_utils.get_node_armorypbr(material.node_tree)
|
sss_node = arm.node_utils.get_node_armorypbr(material.node_tree)
|
||||||
if sss_node != None and sss_node.outputs[0].is_linked and (sss_node.inputs[8].is_linked or sss_node.inputs[8].default_value != 0.0):
|
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):
|
||||||
sss = True
|
sss = True
|
||||||
const = {}
|
|
||||||
const['name'] = 'materialID'
|
const = {'name': 'materialID'}
|
||||||
if sss:
|
if sss:
|
||||||
const['int'] = 2
|
const['int'] = 2
|
||||||
else:
|
else:
|
||||||
|
@ -111,27 +94,20 @@ def parse(material, mat_data, mat_users, mat_armusers):
|
||||||
if node.type == 'TEX_IMAGE':
|
if node.type == 'TEX_IMAGE':
|
||||||
tex_name = arm.utils.safesrc(node.name)
|
tex_name = arm.utils.safesrc(node.name)
|
||||||
tex = cycles.make_texture(node, tex_name)
|
tex = cycles.make_texture(node, tex_name)
|
||||||
if tex == None: # Empty texture
|
# Empty texture
|
||||||
tex = {}
|
if tex is None:
|
||||||
tex['name'] = tex_name
|
tex = {'name': tex_name, 'file': ''}
|
||||||
tex['file'] = ''
|
|
||||||
c['bind_textures'].append(tex)
|
c['bind_textures'].append(tex)
|
||||||
|
|
||||||
# Set marked inputs as uniforms
|
# Set marked inputs as uniforms
|
||||||
for node in material.node_tree.nodes:
|
for node in material.node_tree.nodes:
|
||||||
for inp in node.inputs:
|
for inp in node.inputs:
|
||||||
if inp.is_uniform:
|
if inp.is_uniform:
|
||||||
uname = arm.utils.safesrc(inp.node.name) + arm.utils.safesrc(inp.name) # Merge with cycles
|
uname = arm.utils.safesrc(inp.node.name) + arm.utils.safesrc(inp.name) # Merge with cycles module
|
||||||
const = {}
|
c['bind_constants'].append({'name': uname, cycles.glsl_type(inp.type): glsl_value(inp.default_value)})
|
||||||
const['name'] = uname
|
|
||||||
const[glsl_type(inp.type)] = glsl_value(inp.default_value)
|
|
||||||
c['bind_constants'].append(const)
|
|
||||||
|
|
||||||
elif rp == 'translucent':
|
elif rp == 'translucent':
|
||||||
const = {}
|
c['bind_constants'].append({'name': 'receiveShadow', 'bool': material.arm_receive_shadow})
|
||||||
const['name'] = 'receiveShadow'
|
|
||||||
const['bool'] = material.arm_receive_shadow
|
|
||||||
c['bind_constants'].append(const)
|
|
||||||
|
|
||||||
if wrd.arm_single_data_file:
|
if wrd.arm_single_data_file:
|
||||||
mat_data['shader'] = shader_data_name
|
mat_data['shader'] = shader_data_name
|
||||||
|
|
|
@ -1,31 +1,38 @@
|
||||||
import os
|
import os
|
||||||
import bpy
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import arm.utils
|
from typing import Dict, List, Tuple
|
||||||
import arm.assets as assets
|
|
||||||
import arm.material.mat_utils as mat_utils
|
import bpy
|
||||||
import arm.material.mat_state as mat_state
|
from bpy.types import Material
|
||||||
from arm.material.shader import ShaderData
|
from bpy.types import Object
|
||||||
import arm.material.cycles as cycles
|
|
||||||
import arm.material.make_mesh as make_mesh
|
|
||||||
import arm.material.make_transluc as make_transluc
|
|
||||||
import arm.material.make_overlay as make_overlay
|
|
||||||
import arm.material.make_depth as make_depth
|
|
||||||
import arm.material.make_decal as make_decal
|
|
||||||
import arm.material.make_voxel as make_voxel
|
|
||||||
import arm.api
|
import arm.api
|
||||||
|
import arm.assets as assets
|
||||||
import arm.exporter
|
import arm.exporter
|
||||||
|
import arm.log as log
|
||||||
|
import arm.material.cycles as cycles
|
||||||
|
import arm.material.make_decal as make_decal
|
||||||
|
import arm.material.make_depth as make_depth
|
||||||
|
import arm.material.make_mesh as make_mesh
|
||||||
|
import arm.material.make_overlay as make_overlay
|
||||||
|
import arm.material.make_transluc as make_transluc
|
||||||
|
import arm.material.make_voxel as make_voxel
|
||||||
|
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
|
||||||
|
|
||||||
rpass_hook = None
|
rpass_hook = None
|
||||||
|
|
||||||
def build(material, mat_users, mat_armusers):
|
|
||||||
|
def build(material: Material, mat_users: Dict[Material, List[Object]], mat_armusers) -> Tuple:
|
||||||
mat_state.mat_users = mat_users
|
mat_state.mat_users = mat_users
|
||||||
mat_state.mat_armusers = mat_armusers
|
mat_state.mat_armusers = mat_armusers
|
||||||
mat_state.material = material
|
mat_state.material = material
|
||||||
mat_state.nodes = material.node_tree.nodes
|
mat_state.nodes = material.node_tree.nodes
|
||||||
mat_state.data = ShaderData(material)
|
mat_state.data = ShaderData(material)
|
||||||
mat_state.output_node = cycles.node_by_type(mat_state.nodes, 'OUTPUT_MATERIAL')
|
mat_state.output_node = cycles.node_by_type(mat_state.nodes, 'OUTPUT_MATERIAL')
|
||||||
if mat_state.output_node == None:
|
if mat_state.output_node is None:
|
||||||
# Place empty material output to keep compiler happy..
|
# Place empty material output to keep compiler happy..
|
||||||
mat_state.output_node = mat_state.nodes.new('ShaderNodeOutputMaterial')
|
mat_state.output_node = mat_state.nodes.new('ShaderNodeOutputMaterial')
|
||||||
|
|
||||||
|
@ -41,22 +48,7 @@ def build(material, mat_users, mat_armusers):
|
||||||
if not os.path.exists(full_path):
|
if not os.path.exists(full_path):
|
||||||
os.makedirs(full_path)
|
os.makedirs(full_path)
|
||||||
|
|
||||||
global_elems = []
|
make_instancing_and_skinning(material, mat_users)
|
||||||
if mat_users != None and material in mat_users:
|
|
||||||
for bo in mat_users[material]:
|
|
||||||
# GPU Skinning
|
|
||||||
if arm.utils.export_bone_data(bo):
|
|
||||||
global_elems.append({'name': 'bone', 'data': 'short4norm'})
|
|
||||||
global_elems.append({'name': 'weight', 'data': 'short4norm'})
|
|
||||||
# Instancing
|
|
||||||
if bo.arm_instanced != 'Off' or material.arm_particle_flag:
|
|
||||||
global_elems.append({'name': 'ipos', 'data': 'float3'})
|
|
||||||
if bo.arm_instanced == 'Loc + Rot' or bo.arm_instanced == 'Loc + Rot + Scale':
|
|
||||||
global_elems.append({'name': 'irot', 'data': 'float3'})
|
|
||||||
if bo.arm_instanced == 'Loc + Scale' or bo.arm_instanced == 'Loc + Rot + Scale':
|
|
||||||
global_elems.append({'name': 'iscl', 'data': 'float3'})
|
|
||||||
|
|
||||||
mat_state.data.global_elems = global_elems
|
|
||||||
|
|
||||||
bind_constants = dict()
|
bind_constants = dict()
|
||||||
bind_textures = dict()
|
bind_textures = dict()
|
||||||
|
@ -72,10 +64,10 @@ def build(material, mat_users, mat_armusers):
|
||||||
|
|
||||||
con = None
|
con = None
|
||||||
|
|
||||||
if rpdat.rp_driver != 'Armory' and arm.api.drivers[rpdat.rp_driver]['make_rpass'] != None:
|
if rpdat.rp_driver != 'Armory' and arm.api.drivers[rpdat.rp_driver]['make_rpass'] is not None:
|
||||||
con = arm.api.drivers[rpdat.rp_driver]['make_rpass'](rp)
|
con = arm.api.drivers[rpdat.rp_driver]['make_rpass'](rp)
|
||||||
|
|
||||||
if con != None:
|
if con is not None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
elif rp == 'mesh':
|
elif rp == 'mesh':
|
||||||
|
@ -99,7 +91,7 @@ def build(material, mat_users, mat_armusers):
|
||||||
elif rp == 'voxel':
|
elif rp == 'voxel':
|
||||||
con = make_voxel.make(rp)
|
con = make_voxel.make(rp)
|
||||||
|
|
||||||
elif rpass_hook != None:
|
elif rpass_hook is not None:
|
||||||
con = rpass_hook(rp)
|
con = rpass_hook(rp)
|
||||||
|
|
||||||
write_shaders(rel_path, con, rp, matname)
|
write_shaders(rel_path, con, rp, matname)
|
||||||
|
@ -107,7 +99,7 @@ def build(material, mat_users, mat_armusers):
|
||||||
shader_data_name = matname + '_data'
|
shader_data_name = matname + '_data'
|
||||||
|
|
||||||
if wrd.arm_single_data_file:
|
if wrd.arm_single_data_file:
|
||||||
if not 'shader_datas' in arm.exporter.current_output:
|
if 'shader_datas' not in arm.exporter.current_output:
|
||||||
arm.exporter.current_output['shader_datas'] = []
|
arm.exporter.current_output['shader_datas'] = []
|
||||||
arm.exporter.current_output['shader_datas'].append(mat_state.data.get()['shader_datas'][0])
|
arm.exporter.current_output['shader_datas'].append(mat_state.data.get()['shader_datas'][0])
|
||||||
else:
|
else:
|
||||||
|
@ -117,7 +109,8 @@ def build(material, mat_users, mat_armusers):
|
||||||
|
|
||||||
return rpasses, mat_state.data, shader_data_name, bind_constants, bind_textures
|
return rpasses, mat_state.data, shader_data_name, bind_constants, bind_textures
|
||||||
|
|
||||||
def write_shaders(rel_path, con, rpass, matname):
|
|
||||||
|
def write_shaders(rel_path: str, con: ShaderContext, rpass: str, matname: str) -> None:
|
||||||
keep_cache = mat_state.material.arm_cached
|
keep_cache = mat_state.material.arm_cached
|
||||||
write_shader(rel_path, con.vert, 'vert', rpass, matname, keep_cache=keep_cache)
|
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.frag, 'frag', rpass, matname, keep_cache=keep_cache)
|
||||||
|
@ -125,8 +118,9 @@ def write_shaders(rel_path, con, rpass, matname):
|
||||||
write_shader(rel_path, con.tesc, 'tesc', 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)
|
write_shader(rel_path, con.tese, 'tese', rpass, matname, keep_cache=keep_cache)
|
||||||
|
|
||||||
def write_shader(rel_path, shader, ext, rpass, matname, keep_cache=True):
|
|
||||||
if shader == None or shader.is_linked:
|
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
|
return
|
||||||
|
|
||||||
# TODO: blend context
|
# TODO: blend context
|
||||||
|
@ -161,3 +155,43 @@ def write_shader(rel_path, shader, ext, rpass, matname, keep_cache=True):
|
||||||
args.append('pos')
|
args.append('pos')
|
||||||
proc = subprocess.call(args)
|
proc = subprocess.call(args)
|
||||||
os.chdir(cwd)
|
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]
|
||||||
|
|
||||||
|
for bo in mat_users[mat]:
|
||||||
|
if mat.arm_custom_material == '':
|
||||||
|
# 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
|
||||||
|
|
Loading…
Reference in a new issue