Browse Source

Merge pull request #283 from Hoikas/moar_material_colors

Fix RT lit material shading animations
pull/282/head
Adam Johnson 3 years ago committed by GitHub
parent
commit
cbaebfdb24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      cmake/Dependencies.cmake
  2. 97
      korman/exporter/material.py
  3. 56
      korman/exporter/mesh.py
  4. 2
      korman/properties/modifiers/render.py

2
cmake/Dependencies.cmake

@ -208,7 +208,7 @@ if(korman_BUILD_HSPLASMA)
korman_add_external_project(HSPlasma korman_add_external_project(HSPlasma
GIT_REPOSITORY "https://github.com/H-uru/libhsplasma.git" GIT_REPOSITORY "https://github.com/H-uru/libhsplasma.git"
# Be sure to increase this as the feature set used by Korman increases # Be sure to increase this as the feature set used by Korman increases
GIT_TAG ba5ca8e3fe9261dc6a0d40abfc6f6ec7c82c1b93 GIT_TAG d248e0111f21305b916f40289cdb993a6545e67a
# We can only do shallow checkouts if the above is a branch or tag. # We can only do shallow checkouts if the above is a branch or tag.
GIT_SHALLOW FALSE GIT_SHALLOW FALSE
CMAKE_CACHE_ARGS CMAKE_CACHE_ARGS

97
korman/exporter/material.py

@ -205,7 +205,8 @@ class MaterialConverter:
mat_prefix = "RTLit_" mat_prefix = "RTLit_"
else: else:
mat_prefix = "" mat_prefix = ""
mat_name = "".join((mat_prefix, bm.name)) mat_prefix2 = "NonVtxP_" if self._exporter().mesh.is_nonpreshaded(bo, bm) else ""
mat_name = "".join((mat_prefix, mat_prefix2, bm.name))
self._report.msg("Exporting Material '{}'", mat_name, indent=1) self._report.msg("Exporting Material '{}'", mat_name, indent=1)
hsgmat = self._mgr.find_key(hsGMaterial, name=mat_name, bl=bo) hsgmat = self._mgr.find_key(hsGMaterial, name=mat_name, bl=bo)
if hsgmat is not None: if hsgmat is not None:
@ -489,8 +490,7 @@ class MaterialConverter:
else: else:
layer_props = texture.plasma_layer layer_props = texture.plasma_layer
layer.opacity = layer_props.opacity / 100 layer.opacity = layer_props.opacity / 100
if layer_props.opacity < 100 and not state.blendFlags & hsGMatState.kBlendMask: self._handle_layer_opacity(layer, layer_props.opacity)
state.blendFlags |= hsGMatState.kBlendAlpha
if layer_props.alpha_halo: if layer_props.alpha_halo:
state.blendFlags |= hsGMatState.kBlendAlphaTestHigh state.blendFlags |= hsGMatState.kBlendAlphaTestHigh
if layer_props.z_bias: if layer_props.z_bias:
@ -501,7 +501,7 @@ class MaterialConverter:
state.ZFlags |= hsGMatState.kZNoZWrite state.ZFlags |= hsGMatState.kZNoZWrite
# Export the specific texture type # Export the specific texture type
self._tex_exporters[texture.type](bo, layer, slot) self._tex_exporters[texture.type](bo, layer, slot, idx)
# Export any layer animations # Export any layer animations
# NOTE: animated stencils and bumpmaps are nonsense. # NOTE: animated stencils and bumpmaps are nonsense.
@ -533,9 +533,10 @@ class MaterialConverter:
fcurves = [] fcurves = []
# Base layers get all of the fcurves for animating things like the diffuse color # Base layers get all of the fcurves for animating things like the diffuse color. Danger,
# however, the user can insert fake base layers on top, so be careful.
texture = tex_slot.texture if tex_slot is not None else None texture = tex_slot.texture if tex_slot is not None else None
if idx == 0: if idx == 0 or base_layer.state.miscFlags & hsGMatState.kMiscRestartPassHere:
harvest_fcurves(bm, fcurves) harvest_fcurves(bm, fcurves)
harvest_fcurves(texture, fcurves) harvest_fcurves(texture, fcurves)
elif tex_slot is not None: elif tex_slot is not None:
@ -602,10 +603,14 @@ class MaterialConverter:
return ctrl return ctrl
def _export_layer_opacity_animation(self, bo, bm, tex_slot, base_layer, fcurves): def _export_layer_opacity_animation(self, bo, bm, tex_slot, base_layer, fcurves):
# Dumb function to intercept the opacity values and properly flag the base layer
def process_opacity(value):
self._handle_layer_opacity(base_layer, value)
return value
for i in fcurves: for i in fcurves:
if i.data_path == "plasma_layer.opacity": if i.data_path == "plasma_layer.opacity":
base_layer.state.blendFlags |= hsGMatState.kBlendAlpha ctrl = self._exporter().animation.make_scalar_leaf_controller(i, process_opacity)
ctrl = self._exporter().animation.make_scalar_leaf_controller(i)
return ctrl return ctrl
return None return None
@ -620,7 +625,7 @@ class MaterialConverter:
return ctrl return ctrl
return None return None
def _export_texture_type_environment_map(self, bo, layer, slot): def _export_texture_type_environment_map(self, bo, layer, slot, idx):
"""Exports a Blender EnvironmentMapTexture to a plLayer""" """Exports a Blender EnvironmentMapTexture to a plLayer"""
texture = slot.texture texture = slot.texture
@ -765,7 +770,7 @@ class MaterialConverter:
return pl_env return pl_env
def _export_texture_type_image(self, bo, layer, slot): def _export_texture_type_image(self, bo, layer, slot, idx):
"""Exports a Blender ImageTexture to a plLayer""" """Exports a Blender ImageTexture to a plLayer"""
texture = slot.texture texture = slot.texture
layer_props = texture.plasma_layer layer_props = texture.plasma_layer
@ -796,6 +801,11 @@ class MaterialConverter:
if texture.invert_alpha and has_alpha: if texture.invert_alpha and has_alpha:
state.blendFlags |= hsGMatState.kBlendInvertAlpha state.blendFlags |= hsGMatState.kBlendInvertAlpha
# Not really mutually exclusive, but if this isn't the first slot and there's no alpha,
# then this is probably a new base layer, meaning that we need to restart the render pass.
if not has_alpha and idx > 0:
state.miscFlags |= hsGMatState.kMiscRestartPassHere
if texture.extension in {"CLIP", "EXTEND"}: if texture.extension in {"CLIP", "EXTEND"}:
state.clampFlags |= hsGMatState.kClampTexture state.clampFlags |= hsGMatState.kClampTexture
@ -838,11 +848,11 @@ class MaterialConverter:
mipmap=mipmap, allowed_formats=allowed_formats, mipmap=mipmap, allowed_formats=allowed_formats,
indent=3) indent=3)
def _export_texture_type_none(self, bo, layer, texture): def _export_texture_type_none(self, bo, layer, slot, idx):
# We'll allow this, just for sanity's sake... # We'll allow this, just for sanity's sake...
pass pass
def _export_texture_type_blend(self, bo, layer, slot): def _export_texture_type_blend(self, bo, layer, slot, idx):
state = layer.state state = layer.state
state.blendFlags |= hsGMatState.kBlendAlpha | hsGMatState.kBlendAlphaMult | hsGMatState.kBlendNoTexColor state.blendFlags |= hsGMatState.kBlendAlpha | hsGMatState.kBlendAlphaMult | hsGMatState.kBlendNoTexColor
state.clampFlags |= hsGMatState.kClampTexture state.clampFlags |= hsGMatState.kClampTexture
@ -1254,7 +1264,8 @@ class MaterialConverter:
def get_bump_layer(self, bo): def get_bump_layer(self, bo):
return self._bump_mats.get(bo, None) return self._bump_mats.get(bo, None)
def get_material_ambient(self, bo, bm, color : Union[None, mathutils.Color]=None) -> hsColorRGBA: def get_material_ambient(self, bo, bm, color: Union[None, mathutils.Color] = None) -> hsColorRGBA:
# Although Plasma calls this the ambient color, it is actually always used as the emissive color.
emit_scale = bm.emit * 0.5 emit_scale = bm.emit * 0.5
if emit_scale > 0.0: if emit_scale > 0.0:
if color is None: if color is None:
@ -1266,16 +1277,33 @@ class MaterialConverter:
else: else:
return utils.color(bpy.context.scene.world.ambient_color) return utils.color(bpy.context.scene.world.ambient_color)
def get_material_preshade(self, bo, bm, color : Union[None, mathutils.Color]=None) -> hsColorRGBA: def get_material_preshade(self, bo, bm, color: Union[None, mathutils.Color] = None) -> hsColorRGBA:
if bo.plasma_modifiers.lighting.rt_lights: # This color is always used for shading. In all lighting equations, it represents the world
return hsColorRGBA.kBlack # ambient color. Anyway, if we have a manual (read: animated color), just dump that out.
if color is None: if color is not None:
color = bm.diffuse_color return utils.color(color)
return utils.color(color)
# Runtime lit objects want light from runtime lights, so they have an ambient world color
# of black - and yes, this is an ambient world color. But it gets more fascinating...
# The color has been folded into the vertex colors for nonpreshaded, so for nonpreshaded,
# we'll want black if it's ONLY runtime lighting (and white for lightmaps). Otherwise,
# just use the material color for now.
if self._exporter().mesh.is_nonpreshaded(bo, bm):
if bo.plasma_modifiers.lightmap.bake_lightmap:
return hsColorRGBA.kWhite
elif not bo.plasma_modifiers.lighting.preshade:
return hsColorRGBA.kBlack
def get_material_runtime(self, bo, bm, color : Union[None, mathutils.Color]=None) -> hsColorRGBA: # Gulp
if not bo.plasma_modifiers.lighting.preshade: return utils.color(bm.diffuse_color)
def get_material_runtime(self, bo, bm, color: Union[None, mathutils.Color] = None) -> hsColorRGBA:
# The layer runstime color has no effect if the lighting equation is kLiteVtxNonPreshaded,
# so return black to prevent animations from being exported.
if self._exporter().mesh.is_nonpreshaded(bo, bm):
return hsColorRGBA.kBlack return hsColorRGBA.kBlack
# Hmm...
if color is None: if color is None:
color = bm.diffuse_color color = bm.diffuse_color
return utils.color(color) return utils.color(color)
@ -1297,6 +1325,13 @@ class MaterialConverter:
pClass = plLayerSDLAnimation if texture is not None and texture.plasma_layer.anim_sdl_var else plLayerAnimation pClass = plLayerSDLAnimation if texture is not None and texture.plasma_layer.anim_sdl_var else plLayerAnimation
return self._mgr.find_create_key(pClass, bl=bo, name=name) return self._mgr.find_create_key(pClass, bl=bo, name=name)
def _handle_layer_opacity(self, layer: plLayerInterface, value: float):
if value < 100:
base_layer = layer.bottomOfStack.object
state = base_layer.state
if not state.blendFlags & hsGMatState.kBlendMask:
state.blendFlags |= hsGMatState.kBlendAlpha
@property @property
def _mgr(self): def _mgr(self):
return self._exporter().mgr return self._exporter().mgr
@ -1337,6 +1372,26 @@ class MaterialConverter:
layer.specularPower = min(100.0, float(bm.specular_hardness)) layer.specularPower = min(100.0, float(bm.specular_hardness))
layer.LODBias = -1.0 layer.LODBias = -1.0
def requires_material_shading(self, bm: bpy.types.Material) -> bool:
"""Determines if this material requires the lighting equation we all know and love
(kLiteMaterial) in order to display opacity and color animations."""
if bm.animation_data is not None and bm.animation_data.action is not None:
if any((i.data_path == "diffuse_color" for i in bm.animation_data.action.fcurves)):
return True
for slot in filter(lambda x: x and x.use and x.texture, bm.texture_slots):
tex = slot.texture
# TODO (someday): I think PlasmaMax will actually bake some opacities into the vertices
# so that kLiteVtxNonPreshaded can be used. Might be a good idea at some point.
if tex.plasma_layer.opacity < 100:
return True
if tex.animation_data is not None and tex.animation_data.action is not None:
if any((i.data_path == "plasma_layer.opacity" for i in tex.animation_data.action.fcurves)):
return True
return False
def _requires_single_user(self, bo, bm): def _requires_single_user(self, bo, bm):
if bo.data.show_double_sided: if bo.data.show_double_sided:
return True return True

56
korman/exporter/mesh.py

@ -18,6 +18,7 @@ from contextlib import ExitStack
import itertools import itertools
from PyHSPlasma import * from PyHSPlasma import *
from math import fabs from math import fabs
from typing import Iterable
import weakref import weakref
from ..exporter.logger import ExportProgressLogger from ..exporter.logger import ExportProgressLogger
@ -242,6 +243,7 @@ class MeshConverter(_MeshManager):
self._dspans = {} self._dspans = {}
self._mesh_geospans = {} self._mesh_geospans = {}
self._non_preshaded = {}
# _report is a property on this subclass # _report is a property on this subclass
super().__init__() super().__init__()
@ -278,33 +280,32 @@ class MeshConverter(_MeshManager):
has_alpha = not all(opaque) has_alpha = not all(opaque)
return has_alpha return has_alpha
def _check_vtx_nonpreshaded(self, bo, mesh, material_idx, base_layer): def _check_vtx_nonpreshaded(self, bo, mesh, material_idx, bm):
def check_layer_shading_animation(layer): def check():
if isinstance(layer, plLayerAnimationBase): # TODO: if this is an avatar, we can't be non-preshaded.
return layer.opacityCtl is not None or layer.preshadeCtl is not None or layer.runtimeCtl is not None # kShadeWhite (used for shadeless) is not handled for kLiteVtxNonPreshaded
if layer.underLay is not None: if bm is not None:
return check_layer_shading_animation(layer.underLay.object) if bm.use_shadeless:
return False return False
if self.material.requires_material_shading(bm):
return False
mods = bo.plasma_modifiers
if mods.lighting.rt_lights:
return True
if mods.lightmap.bake_lightmap:
return True
if self._check_vtx_alpha(mesh, material_idx):
return True
# TODO: if this is an avatar, we can't be non-preshaded.
if check_layer_shading_animation(base_layer):
return False return False
# kShadeWhite (used for shadeless) is not handled for kLiteVtxNonPreshaded # Safe version for inside the mesh converter.
if material_idx is not None: result = self._non_preshaded.get((bo, bm))
bm = mesh.materials[material_idx] if result is None:
if bm.use_shadeless: result = check()
return False self._non_preshaded[(bo, bm)] = result
return result
mods = bo.plasma_modifiers
if mods.lighting.rt_lights:
return True
if mods.lightmap.bake_lightmap:
return True
if self._check_vtx_alpha(mesh, material_idx):
return True
return False
def _create_geospan(self, bo, mesh, material_idx, bm, hsgmatKey): def _create_geospan(self, bo, mesh, material_idx, bm, hsgmatKey):
"""Initializes a plGeometrySpan from a Blender Object and an hsGMaterial""" """Initializes a plGeometrySpan from a Blender Object and an hsGMaterial"""
@ -328,7 +329,7 @@ class MeshConverter(_MeshManager):
base_layer = hsgmatKey.object.layers[0].object base_layer = hsgmatKey.object.layers[0].object
if is_alpha_blended(base_layer) or self._check_vtx_alpha(mesh, material_idx): if is_alpha_blended(base_layer) or self._check_vtx_alpha(mesh, material_idx):
geospan.props |= plGeometrySpan.kRequiresBlending geospan.props |= plGeometrySpan.kRequiresBlending
if self._check_vtx_nonpreshaded(bo, mesh, material_idx, base_layer): if self._check_vtx_nonpreshaded(bo, mesh, material_idx, bm):
geospan.props |= plGeometrySpan.kLiteVtxNonPreshaded geospan.props |= plGeometrySpan.kLiteVtxNonPreshaded
if (geospan.props & plGeometrySpan.kLiteMask) != plGeometrySpan.kLiteMaterial: if (geospan.props & plGeometrySpan.kLiteMask) != plGeometrySpan.kLiteMaterial:
geospan.props |= plGeometrySpan.kDiffuseFoldedIn geospan.props |= plGeometrySpan.kDiffuseFoldedIn
@ -650,6 +651,7 @@ class MeshConverter(_MeshManager):
msg = "'{}' is a WaveSet -- only one material is supported".format(bo.name) msg = "'{}' is a WaveSet -- only one material is supported".format(bo.name)
self._exporter().report.warn(msg, indent=1) self._exporter().report.warn(msg, indent=1)
blmat = materials[0][1] blmat = materials[0][1]
self._check_vtx_nonpreshaded(bo, mesh, 0, blmat)
matKey = self.material.export_waveset_material(bo, blmat) matKey = self.material.export_waveset_material(bo, blmat)
geospan = self._create_geospan(bo, mesh, None, blmat, matKey) geospan = self._create_geospan(bo, mesh, None, blmat, matKey)
@ -662,6 +664,7 @@ class MeshConverter(_MeshManager):
geospans = [None] * len(materials) geospans = [None] * len(materials)
mat2span_LUT = {} mat2span_LUT = {}
for i, (blmat_idx, blmat) in enumerate(materials): for i, (blmat_idx, blmat) in enumerate(materials):
self._check_vtx_nonpreshaded(bo, mesh, blmat_idx, blmat)
matKey = self.material.export_material(bo, blmat) matKey = self.material.export_material(bo, blmat)
geospans[i] = _GeoSpan(bo, blmat, geospans[i] = _GeoSpan(bo, blmat,
self._create_geospan(bo, mesh, blmat_idx, blmat, matKey), self._create_geospan(bo, mesh, blmat_idx, blmat, matKey),
@ -718,6 +721,9 @@ class MeshConverter(_MeshManager):
return baked_layer.data return baked_layer.data
return None return None
def is_nonpreshaded(self, bo: bpy.types.Object, bm: bpy.types.Material) -> bool:
return self._non_preshaded[(bo, bm)]
@property @property
def _mgr(self): def _mgr(self):
return self._exporter().mgr return self._exporter().mgr

2
korman/properties/modifiers/render.py

@ -564,8 +564,6 @@ class PlasmaLightingMod(PlasmaModifierProperties):
mods = self.id_data.plasma_modifiers mods = self.id_data.plasma_modifiers
if mods.water_basic.enabled: if mods.water_basic.enabled:
return False return False
if mods.lightmap.bake_lightmap:
return False
return True return True
def export(self, exporter, bo, so): def export(self, exporter, bo, so):

Loading…
Cancel
Save