From ee492d90a22e15755a9b2ac1fb6ee93f23e2a18c Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 25 Aug 2021 18:16:00 -0400 Subject: [PATCH 1/4] Bump HSPlasma requirement. Apparently, plLayerAnimationBase.runtimeCtl was returning the wrong thing, significantly impacting our lighting calculations. --- cmake/Dependencies.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index 6710ca2..f33cbd0 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -208,7 +208,7 @@ if(korman_BUILD_HSPLASMA) korman_add_external_project(HSPlasma GIT_REPOSITORY "https://github.com/H-uru/libhsplasma.git" # 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. GIT_SHALLOW FALSE CMAKE_CACHE_ARGS From ee78b36f5edf746b95e1f86bd88964c4f7951661 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 25 Aug 2021 18:16:51 -0400 Subject: [PATCH 2/4] Fix runtime lit materials diffuse color animations. --- korman/exporter/material.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/korman/exporter/material.py b/korman/exporter/material.py index c3b8fc3..bddef78 100644 --- a/korman/exporter/material.py +++ b/korman/exporter/material.py @@ -1274,7 +1274,9 @@ class MaterialConverter: return utils.color(color) def get_material_runtime(self, bo, bm, color : Union[None, mathutils.Color]=None) -> hsColorRGBA: - if not bo.plasma_modifiers.lighting.preshade: + # If we are animated, then we will need to toss out the diffuse color animation here for + # non-preshaded objects. + if color is None and not bo.plasma_modifiers.lighting.preshade: return hsColorRGBA.kBlack if color is None: color = bm.diffuse_color From 33a7055a13da3f62559e9f19b49754a317a385cf Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 27 Aug 2021 14:41:43 -0400 Subject: [PATCH 3/4] Fix RT-Lit opacity animations. This fixes a number of long standing chicken and egg problems that could prevent opacity animations from working properly on RT-Lit materials. Also addresses a number of odd situations where the lighting results might drastically change in unexpected ways. --- korman/exporter/material.py | 79 +++++++++++++++++++++------ korman/exporter/mesh.py | 56 ++++++++++--------- korman/properties/modifiers/render.py | 2 - 3 files changed, 94 insertions(+), 43 deletions(-) diff --git a/korman/exporter/material.py b/korman/exporter/material.py index bddef78..934c556 100644 --- a/korman/exporter/material.py +++ b/korman/exporter/material.py @@ -205,7 +205,8 @@ class MaterialConverter: mat_prefix = "RTLit_" else: 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) hsgmat = self._mgr.find_key(hsGMaterial, name=mat_name, bl=bo) if hsgmat is not None: @@ -489,8 +490,7 @@ class MaterialConverter: else: layer_props = texture.plasma_layer layer.opacity = layer_props.opacity / 100 - if layer_props.opacity < 100 and not state.blendFlags & hsGMatState.kBlendMask: - state.blendFlags |= hsGMatState.kBlendAlpha + self._handle_layer_opacity(layer, layer_props.opacity) if layer_props.alpha_halo: state.blendFlags |= hsGMatState.kBlendAlphaTestHigh if layer_props.z_bias: @@ -602,10 +602,14 @@ class MaterialConverter: return ctrl 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: if i.data_path == "plasma_layer.opacity": - base_layer.state.blendFlags |= hsGMatState.kBlendAlpha - ctrl = self._exporter().animation.make_scalar_leaf_controller(i) + ctrl = self._exporter().animation.make_scalar_leaf_controller(i, process_opacity) return ctrl return None @@ -1254,7 +1258,8 @@ class MaterialConverter: def get_bump_layer(self, bo): 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 if emit_scale > 0.0: if color is None: @@ -1266,18 +1271,33 @@ class MaterialConverter: else: return utils.color(bpy.context.scene.world.ambient_color) - def get_material_preshade(self, bo, bm, color : Union[None, mathutils.Color]=None) -> hsColorRGBA: - if bo.plasma_modifiers.lighting.rt_lights: - return hsColorRGBA.kBlack - if color is None: - color = bm.diffuse_color - return utils.color(color) + def get_material_preshade(self, bo, bm, color: Union[None, mathutils.Color] = None) -> hsColorRGBA: + # This color is always used for shading. In all lighting equations, it represents the world + # ambient color. Anyway, if we have a manual (read: animated color), just dump that out. + if color is not None: + 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: - # If we are animated, then we will need to toss out the diffuse color animation here for - # non-preshaded objects. - if color is None and not bo.plasma_modifiers.lighting.preshade: + # Gulp + 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 + + # Hmm... if color is None: color = bm.diffuse_color return utils.color(color) @@ -1299,6 +1319,13 @@ class MaterialConverter: 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) + 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 def _mgr(self): return self._exporter().mgr @@ -1339,6 +1366,26 @@ class MaterialConverter: layer.specularPower = min(100.0, float(bm.specular_hardness)) 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): if bo.data.show_double_sided: return True diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py index 0019635..8187854 100644 --- a/korman/exporter/mesh.py +++ b/korman/exporter/mesh.py @@ -18,6 +18,7 @@ from contextlib import ExitStack import itertools from PyHSPlasma import * from math import fabs +from typing import Iterable import weakref from ..exporter.logger import ExportProgressLogger @@ -242,6 +243,7 @@ class MeshConverter(_MeshManager): self._dspans = {} self._mesh_geospans = {} + self._non_preshaded = {} # _report is a property on this subclass super().__init__() @@ -278,33 +280,32 @@ class MeshConverter(_MeshManager): has_alpha = not all(opaque) return has_alpha - def _check_vtx_nonpreshaded(self, bo, mesh, material_idx, base_layer): - def check_layer_shading_animation(layer): - if isinstance(layer, plLayerAnimationBase): - return layer.opacityCtl is not None or layer.preshadeCtl is not None or layer.runtimeCtl is not None - if layer.underLay is not None: - return check_layer_shading_animation(layer.underLay.object) - return False + def _check_vtx_nonpreshaded(self, bo, mesh, material_idx, bm): + def check(): + # TODO: if this is an avatar, we can't be non-preshaded. + # kShadeWhite (used for shadeless) is not handled for kLiteVtxNonPreshaded + if bm is not None: + if bm.use_shadeless: + 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 - # kShadeWhite (used for shadeless) is not handled for kLiteVtxNonPreshaded - if material_idx is not None: - bm = mesh.materials[material_idx] - if bm.use_shadeless: - 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 - - return False + # Safe version for inside the mesh converter. + result = self._non_preshaded.get((bo, bm)) + if result is None: + result = check() + self._non_preshaded[(bo, bm)] = result + return result def _create_geospan(self, bo, mesh, material_idx, bm, hsgmatKey): """Initializes a plGeometrySpan from a Blender Object and an hsGMaterial""" @@ -328,7 +329,7 @@ class MeshConverter(_MeshManager): base_layer = hsgmatKey.object.layers[0].object if is_alpha_blended(base_layer) or self._check_vtx_alpha(mesh, material_idx): 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 if (geospan.props & plGeometrySpan.kLiteMask) != plGeometrySpan.kLiteMaterial: geospan.props |= plGeometrySpan.kDiffuseFoldedIn @@ -650,6 +651,7 @@ class MeshConverter(_MeshManager): msg = "'{}' is a WaveSet -- only one material is supported".format(bo.name) self._exporter().report.warn(msg, indent=1) blmat = materials[0][1] + self._check_vtx_nonpreshaded(bo, mesh, 0, blmat) matKey = self.material.export_waveset_material(bo, blmat) geospan = self._create_geospan(bo, mesh, None, blmat, matKey) @@ -662,6 +664,7 @@ class MeshConverter(_MeshManager): geospans = [None] * len(materials) mat2span_LUT = {} for i, (blmat_idx, blmat) in enumerate(materials): + self._check_vtx_nonpreshaded(bo, mesh, blmat_idx, blmat) matKey = self.material.export_material(bo, blmat) geospans[i] = _GeoSpan(bo, blmat, self._create_geospan(bo, mesh, blmat_idx, blmat, matKey), @@ -718,6 +721,9 @@ class MeshConverter(_MeshManager): return baked_layer.data return None + def is_nonpreshaded(self, bo: bpy.types.Object, bm: bpy.types.Material) -> bool: + return self._non_preshaded[(bo, bm)] + @property def _mgr(self): return self._exporter().mgr diff --git a/korman/properties/modifiers/render.py b/korman/properties/modifiers/render.py index 04bb8ae..8916b9d 100644 --- a/korman/properties/modifiers/render.py +++ b/korman/properties/modifiers/render.py @@ -564,8 +564,6 @@ class PlasmaLightingMod(PlasmaModifierProperties): mods = self.id_data.plasma_modifiers if mods.water_basic.enabled: return False - if mods.lightmap.bake_lightmap: - return False return True def export(self, exporter, bo, so): From 9eb4652d05e619e09c32117b3f40b66ba76a5baf Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 27 Aug 2021 19:07:37 -0400 Subject: [PATCH 4/4] Allow multiple "base-layer" diffuse color animations. This is for if you have a layer that has an animated opacity but no alpha, it will be able to receive the material's color animation. For now, we won't apply those to upper layers as that doesn't really seem like what we would expect. --- korman/exporter/material.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/korman/exporter/material.py b/korman/exporter/material.py index 934c556..a32c748 100644 --- a/korman/exporter/material.py +++ b/korman/exporter/material.py @@ -501,7 +501,7 @@ class MaterialConverter: state.ZFlags |= hsGMatState.kZNoZWrite # 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 # NOTE: animated stencils and bumpmaps are nonsense. @@ -533,9 +533,10 @@ class MaterialConverter: 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 - if idx == 0: + if idx == 0 or base_layer.state.miscFlags & hsGMatState.kMiscRestartPassHere: harvest_fcurves(bm, fcurves) harvest_fcurves(texture, fcurves) elif tex_slot is not None: @@ -624,7 +625,7 @@ class MaterialConverter: return ctrl 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""" texture = slot.texture @@ -769,7 +770,7 @@ class MaterialConverter: 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""" texture = slot.texture layer_props = texture.plasma_layer @@ -800,6 +801,11 @@ class MaterialConverter: if texture.invert_alpha and has_alpha: 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"}: state.clampFlags |= hsGMatState.kClampTexture @@ -842,11 +848,11 @@ class MaterialConverter: mipmap=mipmap, allowed_formats=allowed_formats, 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... pass - def _export_texture_type_blend(self, bo, layer, slot): + def _export_texture_type_blend(self, bo, layer, slot, idx): state = layer.state state.blendFlags |= hsGMatState.kBlendAlpha | hsGMatState.kBlendAlphaMult | hsGMatState.kBlendNoTexColor state.clampFlags |= hsGMatState.kClampTexture