Full BSDF
This commit is contained in:
@ -64,8 +64,6 @@ def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]],
|
||||
shader_data_name = material.lnx_custom_material
|
||||
bind_constants = {'mesh': []}
|
||||
bind_textures = {'mesh': []}
|
||||
mat_uses_sss = False
|
||||
|
||||
make_shader.make_instancing_and_skinning(material, mat_users)
|
||||
|
||||
for idx, item in enumerate(material.lnx_bind_textures_list):
|
||||
@ -82,11 +80,11 @@ def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]],
|
||||
log.warn(f'Material "{material.name}": skipping export of bind texture at slot {idx + 1} ("{item.uniform_name}") with no image selected')
|
||||
|
||||
elif not wrd.lnx_batch_materials or material.name.startswith('lnxdefault'):
|
||||
rpasses, shader_data, shader_data_name, bind_constants, bind_textures, mat_uses_sss = make_shader.build(material, mat_users, mat_lnxusers)
|
||||
rpasses, shader_data, shader_data_name, bind_constants, bind_textures = make_shader.build(material, mat_users, mat_lnxusers)
|
||||
sd = shader_data.sd
|
||||
else:
|
||||
result = mat_batch.get(material)
|
||||
rpasses, shader_data, shader_data_name, bind_constants, bind_textures, mat_uses_sss = result
|
||||
rpasses, shader_data, shader_data_name, bind_constants, bind_textures = result
|
||||
sd = shader_data.sd
|
||||
|
||||
sss_used = False
|
||||
@ -107,74 +105,144 @@ def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]],
|
||||
if material.lnx_material_id != 0:
|
||||
c['bind_constants'].append({'name': 'materialID', 'intValue': material.lnx_material_id})
|
||||
|
||||
if material.lnx_material_id == 2:
|
||||
wrd.world_defs += '_Hair'
|
||||
|
||||
elif rpdat.rp_sss_state != 'Off':
|
||||
const = {'name': 'materialID'}
|
||||
# Use per-material SSS flag from shader build
|
||||
if mat_uses_sss:
|
||||
const['intValue'] = 2
|
||||
sss_used = True
|
||||
if '_SSS' not in wrd.world_defs:
|
||||
wrd.world_defs += '_SSS'
|
||||
else:
|
||||
const['intValue'] = 0
|
||||
c['bind_constants'].append(const)
|
||||
|
||||
# extended BRDF parameters as bind_constants
|
||||
if rpdat.rp_renderer == 'Deferred':
|
||||
feats = mat_state.features
|
||||
|
||||
def try_float(val_str, default=1.0):
|
||||
import re as _re_mix
|
||||
_mix_re = _re_mix.compile(r'^\s*([+-]?\d*\.?\d+(?:[eE][+-]?\d+)?)\s*\*\s*\w+\s*([+-])\s*([+-]?\d*\.?\d+(?:[eE][+-]?\d+)?)\s*\*\s*\w+\s*$')
|
||||
|
||||
_term_re = _re_mix.compile(r'([+-]?\d*\.?\d+(?:[eE][+-]?\d+)?)\s*\*\s*[^\s*]+(?:\s*\*\s*[^\s*]+)*')
|
||||
_term_pair_re = _re_mix.compile(r'([+-]?\d*\.?\d+(?:[eE][+-]?\d+)?)\s*\*\s*([^\s*]+(?:\s*\*\s*[^\s*]+)*)')
|
||||
|
||||
def _parse_terms(val_str):
|
||||
val_str = str(val_str).strip()
|
||||
pairs = _term_pair_re.findall(val_str)
|
||||
return [(float(c), v.replace(' ', '')) for c, v in pairs]
|
||||
|
||||
def eval_filtered_expr(val_str, filter_str):
|
||||
if val_str is None or filter_str is None:
|
||||
return None
|
||||
filter_terms = _parse_terms(filter_str)
|
||||
if len(filter_terms) < 2:
|
||||
return None
|
||||
filter_map = {}
|
||||
for coeff, var_prod in filter_terms:
|
||||
filter_map[var_prod] = coeff
|
||||
val_terms = _parse_terms(val_str)
|
||||
if len(val_terms) < 2:
|
||||
return None
|
||||
filtered_coeffs = []
|
||||
for coeff, var_prod in val_terms:
|
||||
fc = filter_map.get(var_prod)
|
||||
if fc is not None and fc != 0.0:
|
||||
filtered_coeffs.append(coeff)
|
||||
if not filtered_coeffs:
|
||||
return None
|
||||
if all(c == filtered_coeffs[0] for c in filtered_coeffs):
|
||||
return filtered_coeffs[0]
|
||||
return None
|
||||
|
||||
def eval_mix_expr(val_str):
|
||||
if val_str is None:
|
||||
return None
|
||||
val_str = str(val_str).strip()
|
||||
try:
|
||||
return float(val_str)
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
pass
|
||||
m = _mix_re.match(val_str)
|
||||
if m:
|
||||
a = float(m.group(1))
|
||||
op = m.group(2)
|
||||
b = float(m.group(3))
|
||||
if op == '+' and a == b:
|
||||
return a
|
||||
if op == '-' and a == b:
|
||||
return 0.0
|
||||
terms = _term_re.findall(val_str)
|
||||
if terms and len(terms) >= 2:
|
||||
coeffs = [float(t) for t in terms]
|
||||
nonzero = [c for c in coeffs if c != 0.0]
|
||||
if not nonzero:
|
||||
return 0.0
|
||||
if all(c == nonzero[0] for c in nonzero):
|
||||
return nonzero[0]
|
||||
return None
|
||||
|
||||
def feat_is_nonzero(val_str):
|
||||
v = eval_mix_expr(val_str)
|
||||
if v is None:
|
||||
return True
|
||||
return v != 0.0
|
||||
|
||||
def try_float(val_str, default=1.0):
|
||||
v = eval_mix_expr(val_str)
|
||||
return v if v is not None else default
|
||||
|
||||
def is_constant(val_str):
|
||||
return eval_mix_expr(val_str) is not None
|
||||
|
||||
warned_nonconst = set()
|
||||
|
||||
def ext_bc(name, val_str, default):
|
||||
const = is_constant(val_str)
|
||||
if not const and name not in warned_nonconst:
|
||||
warned_nonconst.add(name)
|
||||
log.warn(f'material "{material.name}": deferred ext BRDF param "{name}" has non-constant expression "{val_str}", using default {default} - per-pixel texture-driven params require deferred texturing (Phase 4)')
|
||||
c['bind_constants'].append({'name': name, 'floatValue': try_float(val_str, default), 'is_constant': const})
|
||||
|
||||
has_ext_brdf = False
|
||||
if feats.get('clearcoat', '0.0') not in ('0.0', '0', ''):
|
||||
c['bind_constants'].append({'name': 'clearcoat', 'floatValue': try_float(feats.get('clearcoat', '1.0'))})
|
||||
c['bind_constants'].append({'name': 'clearcoatRough', 'floatValue': try_float(feats.get('clearcoatRough', '0.03'), 0.03)})
|
||||
c['bind_constants'].append({'name': 'coatIOR', 'floatValue': try_float(feats.get('coatIOR', '1.5'), 1.5)})
|
||||
c['bind_constants'].append({'name': 'coatTintR', 'floatValue': try_float(feats.get('coatTintR', '1.0'), 1.0)})
|
||||
c['bind_constants'].append({'name': 'coatTintG', 'floatValue': try_float(feats.get('coatTintG', '1.0'), 1.0)})
|
||||
c['bind_constants'].append({'name': 'coatTintB', 'floatValue': try_float(feats.get('coatTintB', '1.0'), 1.0)})
|
||||
if feat_is_nonzero(feats.get('clearcoat', '0.0')):
|
||||
ext_bc('clearcoat', feats.get('clearcoat', '1.0'), 1.0)
|
||||
ext_bc('clearcoatRough', feats.get('clearcoatRough', '0.03'), 0.03)
|
||||
ext_bc('coatIOR', feats.get('coatIOR', '1.5'), 1.5)
|
||||
ext_bc('coatTintR', feats.get('coatTintR', '1.0'), 1.0)
|
||||
ext_bc('coatTintG', feats.get('coatTintG', '1.0'), 1.0)
|
||||
ext_bc('coatTintB', feats.get('coatTintB', '1.0'), 1.0)
|
||||
if '_ClearCoat' not in wrd.world_defs:
|
||||
wrd.world_defs += '_ClearCoat'
|
||||
has_ext_brdf = True
|
||||
if feats.get('sheen', '0.0') not in ('0.0', '0', ''):
|
||||
c['bind_constants'].append({'name': 'sheen', 'floatValue': try_float(feats.get('sheen', '1.0'))})
|
||||
c['bind_constants'].append({'name': 'sheenRough', 'floatValue': try_float(feats.get('sheenRough', '0.5'), 0.5)})
|
||||
c['bind_constants'].append({'name': 'sheenTintR', 'floatValue': try_float(feats.get('sheenTintR', '1.0'), 1.0)})
|
||||
c['bind_constants'].append({'name': 'sheenTintG', 'floatValue': try_float(feats.get('sheenTintG', '1.0'), 1.0)})
|
||||
c['bind_constants'].append({'name': 'sheenTintB', 'floatValue': try_float(feats.get('sheenTintB', '1.0'), 1.0)})
|
||||
if feat_is_nonzero(feats.get('sheen', '0.0')):
|
||||
ext_bc('sheen', feats.get('sheen', '1.0'), 1.0)
|
||||
ext_bc('sheenRough', feats.get('sheenRough', '0.5'), 0.5)
|
||||
ext_bc('sheenTintR', feats.get('sheenTintR', '1.0'), 1.0)
|
||||
ext_bc('sheenTintG', feats.get('sheenTintG', '1.0'), 1.0)
|
||||
ext_bc('sheenTintB', feats.get('sheenTintB', '1.0'), 1.0)
|
||||
if '_Sheen' not in wrd.world_defs:
|
||||
wrd.world_defs += '_Sheen'
|
||||
has_ext_brdf = True
|
||||
if feats.get('subsurface', '0.0') not in ('0.0', '0', '') and rpdat.rp_sss_state != 'Off' and not mat_uses_sss:
|
||||
c['bind_constants'].append({'name': 'subsurface', 'floatValue': try_float(feats.get('subsurface', '1.0'))})
|
||||
c['bind_constants'].append({'name': 'subsurfaceAnisotropy', 'floatValue': try_float(feats.get('subsurfaceAnisotropy', '0.0'), 0.0)})
|
||||
c['bind_constants'].append({'name': 'subsurfaceRadiusR', 'floatValue': try_float(feats.get('subsurfaceRadiusR', '1.0'), 1.0)})
|
||||
c['bind_constants'].append({'name': 'subsurfaceRadiusG', 'floatValue': try_float(feats.get('subsurfaceRadiusG', '0.2'), 0.2)})
|
||||
c['bind_constants'].append({'name': 'subsurfaceRadiusB', 'floatValue': try_float(feats.get('subsurfaceRadiusB', '0.1'), 0.1)})
|
||||
c['bind_constants'].append({'name': 'subsurfaceColorR', 'floatValue': try_float(feats.get('subsurfaceColorR', '0.8'), 0.8)})
|
||||
c['bind_constants'].append({'name': 'subsurfaceColorG', 'floatValue': try_float(feats.get('subsurfaceColorG', '0.8'), 0.8)})
|
||||
c['bind_constants'].append({'name': 'subsurfaceColorB', 'floatValue': try_float(feats.get('subsurfaceColorB', '0.8'), 0.8)})
|
||||
if '_Subsurface' not in wrd.world_defs:
|
||||
wrd.world_defs += '_Subsurface'
|
||||
if feat_is_nonzero(feats.get('subsurface', '0.0')) and rpdat.rp_sss_state != 'Off':
|
||||
ext_bc('subsurface', feats.get('subsurface', '1.0'), 1.0)
|
||||
ext_bc('subsurfaceAnisotropy', feats.get('subsurfaceAnisotropy', '0.0'), 0.0)
|
||||
ext_bc('subsurfaceScale', feats.get('subsurfaceScale', '0.05'), 0.05)
|
||||
ext_bc('subsurfaceRadiusR', feats.get('subsurfaceRadiusR', '1.0'), 1.0)
|
||||
ext_bc('subsurfaceRadiusG', feats.get('subsurfaceRadiusG', '0.2'), 0.2)
|
||||
ext_bc('subsurfaceRadiusB', feats.get('subsurfaceRadiusB', '0.1'), 0.1)
|
||||
ext_bc('subsurfaceColorR', feats.get('subsurfaceColorR', '0.8'), 0.8)
|
||||
ext_bc('subsurfaceColorG', feats.get('subsurfaceColorG', '0.8'), 0.8)
|
||||
ext_bc('subsurfaceColorB', feats.get('subsurfaceColorB', '0.8'), 0.8)
|
||||
if '_SSS' not in wrd.world_defs:
|
||||
wrd.world_defs += '_SSS'
|
||||
has_ext_brdf = True
|
||||
if feats.get('anisotropy', '0.0') not in ('0.0', '0', ''):
|
||||
c['bind_constants'].append({'name': 'anisotropy', 'floatValue': try_float(feats.get('anisotropy', '1.0'))})
|
||||
c['bind_constants'].append({'name': 'anisoRot', 'floatValue': try_float(feats.get('anisoRot', '0.0'), 0.0)})
|
||||
sss_used = True
|
||||
if feat_is_nonzero(feats.get('anisotropy', '0.0')):
|
||||
ext_bc('anisotropy', feats.get('anisotropy', '1.0'), 1.0)
|
||||
ext_bc('anisoRot', feats.get('anisoRot', '0.0'), 0.0)
|
||||
if '_Anisotropy' not in wrd.world_defs:
|
||||
wrd.world_defs += '_Anisotropy'
|
||||
has_ext_brdf = True
|
||||
if feats.get('transmission', '0.0') not in ('0.0', '0', ''):
|
||||
c['bind_constants'].append({'name': 'transmission', 'floatValue': try_float(feats.get('transmission', '1.0'))})
|
||||
c['bind_constants'].append({'name': 'transmissionRough', 'floatValue': try_float(feats.get('transmissionRough', '0.0'), 0.0)})
|
||||
c['bind_constants'].append({'name': 'ior', 'floatValue': try_float(feats.get('ior', '1.45'), 1.45)})
|
||||
c['bind_constants'].append({'name': 'thinWall', 'floatValue': try_float(feats.get('thinWall', '0.0'), 0.0)})
|
||||
if feat_is_nonzero(feats.get('transmission', '0.0')):
|
||||
transm_str = feats.get('transmission', '1.0')
|
||||
ext_bc('transmission', transm_str, 1.0)
|
||||
ext_bc('transmissionRough', feats.get('transmissionRough', '0.0'), 0.0)
|
||||
ior_str = feats.get('ior', '1.45')
|
||||
if not is_constant(ior_str):
|
||||
filtered = eval_filtered_expr(ior_str, transm_str)
|
||||
if filtered is not None:
|
||||
ior_str = str(filtered)
|
||||
ext_bc('ior', ior_str, 1.45)
|
||||
ext_bc('thinWall', feats.get('thinWall', '0.0'), 0.0)
|
||||
if '_Transmission' not in wrd.world_defs:
|
||||
wrd.world_defs += '_Transmission'
|
||||
has_ext_brdf = True
|
||||
@ -182,6 +250,11 @@ def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]],
|
||||
if has_ext_brdf and '_ExtBRDF' not in wrd.world_defs:
|
||||
wrd.world_defs += '_ExtBRDF'
|
||||
|
||||
if has_ext_brdf:
|
||||
ext_bc('specularTintR', feats.get('specularTintR', '1.0'), 1.0)
|
||||
ext_bc('specularTintG', feats.get('specularTintG', '1.0'), 1.0)
|
||||
ext_bc('specularTintB', feats.get('specularTintB', '1.0'), 1.0)
|
||||
|
||||
# assign materialID for extended BRDF if not already set
|
||||
# coexist with Scene.hx for last materialID in bind_constants
|
||||
if has_ext_brdf and material.lnx_material_id == 0:
|
||||
@ -191,6 +264,11 @@ def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]],
|
||||
c['bind_constants'].append({'name': 'materialID', 'intValue': ext_id})
|
||||
else:
|
||||
log.warn(f'material "{material.name}": extended BRDF material limit (15) exceeded, extended BRDF will be disabled for this material')
|
||||
has_ext_brdf = False
|
||||
|
||||
has_matid = any(bc.get('name') == 'materialID' for bc in c['bind_constants'] if isinstance(bc, dict))
|
||||
if not has_matid:
|
||||
c['bind_constants'].append({'name': 'materialID', 'intValue': 0})
|
||||
|
||||
# TODO: Mesh only material batching
|
||||
if wrd.lnx_batch_materials:
|
||||
|
||||
Reference in New Issue
Block a user