Merge pull request #2076 from MoritzBrueckner/instancing

Show warnings for invalid instancing configurations + cleanup
This commit is contained in:
Lubos Lenco 2021-01-01 12:56:52 +01:00 committed by GitHub
commit 97582968bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 114 additions and 104 deletions

View file

@ -1,16 +1,15 @@
from typing import Dict, List
import bpy
import arm.utils
import arm.node_utils
from bpy.types import Material
from bpy.types import Object
import arm.material.cycles as cycles
import arm.material.make_shader as make_shader
import arm.material.mat_batch as mat_batch
import arm.material.mat_state as mat_state
import arm.material.cycles as cycles
import arm.node_utils
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):
if str(type(val)) == "<class 'bpy_prop_array'>":
@ -21,39 +20,27 @@ def glsl_value(val):
else:
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']
rpdat = arm.utils.get_rp()
# No batch - shader data per material
if material.arm_custom_material != '':
rpasses = ['mesh']
sd = {}
sd['contexts'] = []
con = {}
con['vertex_elements'] = []
elem = {}
elem['name'] = 'pos'
elem['data'] = 'short4norm'
con['vertex_elements'].append(elem)
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)
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.arm_custom_material
bind_constants = {}
bind_constants['mesh'] = []
bind_textures = {}
bind_textures['mesh'] = []
bind_constants = {'mesh': []}
bind_textures = {'mesh': []}
make_shader.make_instancing_and_skinning(material, mat_users)
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)
sd = shader_data.sd
@ -63,39 +50,35 @@ def parse(material, mat_data, mat_users, mat_armusers):
# Material
for rp in rpasses:
c = {}
c['name'] = rp
c['bind_constants'] = [] + bind_constants[rp]
c['bind_textures'] = [] + bind_textures[rp]
c = {
'name': rp,
'bind_constants': [] + bind_constants[rp],
'bind_textures': [] + bind_textures[rp],
}
mat_data['contexts'].append(c)
if rp == 'mesh':
const = {}
const['name'] = 'receiveShadow'
const['bool'] = material.arm_receive_shadow
c['bind_constants'].append(const)
c['bind_constants'].append({'name': 'receiveShadow', 'bool': material.arm_receive_shadow})
if material.arm_material_id != 0:
const = {}
const['name'] = 'materialID'
const['int'] = material.arm_material_id
c['bind_constants'].append(const)
c['bind_constants'].append({'name': 'materialID', 'int': material.arm_material_id})
if material.arm_material_id == 2:
wrd.world_defs += '_Hair'
elif rpdat.rp_sss_state == 'On':
sss = False
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_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_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
const = {}
const['name'] = 'materialID'
const = {'name': 'materialID'}
if sss:
const['int'] = 2
else:
@ -111,27 +94,20 @@ def parse(material, mat_data, mat_users, mat_armusers):
if node.type == 'TEX_IMAGE':
tex_name = arm.utils.safesrc(node.name)
tex = cycles.make_texture(node, tex_name)
if tex == None: # Empty texture
tex = {}
tex['name'] = tex_name
tex['file'] = ''
# Empty texture
if tex is None:
tex = {'name': tex_name, 'file': ''}
c['bind_textures'].append(tex)
# Set marked inputs as uniforms
for node in material.node_tree.nodes:
for inp in node.inputs:
if inp.is_uniform:
uname = arm.utils.safesrc(inp.node.name) + arm.utils.safesrc(inp.name) # Merge with cycles
const = {}
const['name'] = uname
const[glsl_type(inp.type)] = glsl_value(inp.default_value)
c['bind_constants'].append(const)
uname = arm.utils.safesrc(inp.node.name) + arm.utils.safesrc(inp.name) # Merge with cycles module
c['bind_constants'].append({'name': uname, cycles.glsl_type(inp.type): glsl_value(inp.default_value)})
elif rp == 'translucent':
const = {}
const['name'] = 'receiveShadow'
const['bool'] = material.arm_receive_shadow
c['bind_constants'].append(const)
c['bind_constants'].append({'name': 'receiveShadow', 'bool': material.arm_receive_shadow})
if wrd.arm_single_data_file:
mat_data['shader'] = shader_data_name

View file

@ -1,31 +1,38 @@
import os
import bpy
import subprocess
import arm.utils
import arm.assets as assets
import arm.material.mat_utils as mat_utils
import arm.material.mat_state as mat_state
from arm.material.shader import ShaderData
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
from typing import Dict, List, Tuple
import bpy
from bpy.types import Material
from bpy.types import Object
import arm.api
import arm.assets as assets
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
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_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 == None:
if mat_state.output_node is None:
# Place empty material output to keep compiler happy..
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):
os.makedirs(full_path)
global_elems = []
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
make_instancing_and_skinning(material, mat_users)
bind_constants = dict()
bind_textures = dict()
@ -72,10 +64,10 @@ def build(material, mat_users, mat_armusers):
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)
if con != None:
if con is not None:
pass
elif rp == 'mesh':
@ -99,7 +91,7 @@ def build(material, mat_users, mat_armusers):
elif rp == 'voxel':
con = make_voxel.make(rp)
elif rpass_hook != None:
elif rpass_hook is not None:
con = rpass_hook(rp)
write_shaders(rel_path, con, rp, matname)
@ -107,7 +99,7 @@ def build(material, mat_users, mat_armusers):
shader_data_name = matname + '_data'
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'].append(mat_state.data.get()['shader_datas'][0])
else:
@ -117,7 +109,8 @@ def build(material, mat_users, mat_armusers):
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
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)
@ -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.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
# TODO: blend context
@ -161,3 +155,43 @@ def write_shader(rel_path, shader, ext, rpass, matname, keep_cache=True):
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]
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