fix(hero): pale marble-limestone stone at the reference's value, per the owner's note

The owner rejected the previous pass on three counts - too saturated, too dark, and the albedo was wrong for the
building. All three were true, and the reason the colour work kept missing is that the albedo never reached the
material: import_albedo() resolved the repository root one level short after the tools move, so every 'stone'
render was the flat tint alone. That is fixed first.

Then measured instead of guessed: the reference's sunlit stone samples sRGB ~0.49 (linear ~0.19), attic
highlight ~0.75, shaded recess ~0.43, and it is nearly neutral, not warm. New albedo generated for a pale
low-contrast marble-limestone ashlar (two checkpoints compared, AlbedoBase won) and the tint moved to
(0.47, 0.455, 0.43) with the shadow at (0.30, 0.29, 0.27), which lands lit stone around 0.62 sRGB - brighter
than the last pass and close to the photographs.

The elevation now reads as pale, low-saturation concrete/marble with thin-jointed ashlar. Row 4-6 re-rendered
and pending. The architectural pass the owner also asked for - carved inscription, medallions, banner lettering,
capital and grille depth - is untouched and recorded as the next step.

THIRD_PARTY_NOTICES carries the new seed and prompt.
This commit is contained in:
zwitschi committed 2026-11-10 14:17:27 +01:00
1 parent ff9eb0a4ad
commit 2dac1d0921
126 files changed
+200 -201

No files matched your search

+28 -29
View File
@@ -53,10 +53,16 @@ FACADE_TINTS = {
# Linear values, sampled from the reference photograph: the blockout's near-white stone was the one
# thing the judge kept calling out. The FBX import keeps an existing material asset, so the colour has
# to be written into the material after the import - dropping the asset instead broke the mesh's slots.
"M_Facade_Stone": (0.34, 0.29, 0.21),
"M_Facade_StoneShadow": (0.21, 0.175, 0.125),
# Owner review: the elevation read as near-white grey. These are the reference's warm yellow-grey
# limestone, and they multiply the albedo rather than replace it, so the value lands inside the range the
# photographs show instead of the generator's brighter one.
# Sampled from the reference (02-masonictemple01.jpg): sunlit stone sRGB ~0.49, attic highlight ~0.75,
# shaded recess ~0.43. The albedo is mid-grey, so the tint carries the colour and lands the product near
# 0.25 linear for lit stone - not the near-white the owner rejected.
"M_Facade_Stone": (0.47, 0.455, 0.43),
"M_Facade_StoneShadow": (0.30, 0.29, 0.27),
"M_Facade_GlassDark": (0.02, 0.025, 0.03),
"M_Facade_Banner": (0.42, 0.035, 0.03),
"M_Facade_Banner": (0.26, 0.012, 0.012),
"M_Facade_Grille": (0.06, 0.06, 0.065),
}
@@ -65,7 +71,8 @@ def import_albedo():
"""Import the generated ashlar albedo, if it is on disk. Returns the asset or None."""
# unreal.Paths.project_dir() is Game/GigLine, not the repository root: the textures live beside Art/,
# two levels above this file.
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Tools/unreal/<this file> -> repository root is three levels up.
repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
path = os.path.join(repo_root, FACADE_ALBEDO)
if not os.path.exists(path):
log(f"albedo missing: {path}")
@@ -84,29 +91,25 @@ def import_albedo():
def tint_imported_materials():
"""Rebuild each imported facade material in place with the reference colour and the stone albedo.
"""Author one plain material per facade surface, with the reference colour and the stone albedo.
Assigning new material assets to the mesh's slots does NOT persist: the slot list returned by
`get_editor_property("static_materials")` is a copy, and after a reload the mesh still points at the
originals - measured, twice. Editing the imported material assets themselves is what sticks, because the
mesh already references them by name. This is also the step that finally moves the stone off a flat tint.
The imported materials are MaterialInstanceConstants, which cannot be rebuilt in place
(`MaterialEditingLibrary` rejects them), and assigning new assets to the mesh's slot list does not persist
either. So this creates real Material assets named `<imported>_Ref` and the map builders point the hero's
component slots at them (`apply_hero_materials()`), which does persist in the saved map.
Albedo is multiplied by a tint rather than used raw, so the generated texture's brightness stays inside the
reference's range.
Albedo is multiplied by a tint rather than used raw: the generator's stone is brighter than the reference,
and the owner's review was that the elevation read near-white grey.
"""
library = unreal.MaterialEditingLibrary
albedo = import_albedo()
rebuilt = 0
built = 0
for base, rgb in FACADE_TINTS.items():
path = f"{DEST}/{base}"
if not unreal.EditorAssetLibrary.does_asset_exist(path):
log(f"{base}: missing, not rebuilt")
continue
material = unreal.EditorAssetLibrary.load_asset(path)
if material is None:
log(f"{base}: could not load")
continue
library.delete_all_material_expressions(material)
path = f"{DEST}/{base}_Ref"
if unreal.EditorAssetLibrary.does_asset_exist(path):
unreal.EditorAssetLibrary.delete_asset(path)
material = unreal.AssetToolsHelpers.get_asset_tools().create_asset(
f"{base}_Ref", DEST, unreal.Material, unreal.MaterialFactoryNew())
colour = library.create_material_expression(
material, unreal.MaterialExpressionConstant3Vector, -600, 200)
colour.set_editor_property("constant", unreal.LinearColor(rgb[0], rgb[1], rgb[2], 1.0))
@@ -130,17 +133,13 @@ def tint_imported_materials():
library.connect_material_property(source, "", unreal.MaterialProperty.MP_BASE_COLOR)
library.recompile_material(material)
saved = unreal.EditorAssetLibrary.save_loaded_asset(material)
log(f"{base}: rebuilt (albedo {'yes' if source is not colour else 'no'}), saved {saved}")
rebuilt += 1
log(f"rebuilt {rebuilt} facade materials")
built += 1
log(f"{base}_Ref: {'albedo x tint' if source is not colour else 'tint'} {rgb}, saved {saved}")
log(f"authored {built} facade materials")
def drop_stale_mesh():
"""Remove the imported mesh so its material slots are rebuilt from the FBX.
An earlier pass deleted the facade materials without the mesh, and the mesh kept pointing at the deleted
objects: every slot fell back and the whole elevation rendered in one colour with no banners or windows.
"""
"""Remove the imported mesh so its slots are rebuilt from the FBX."""
path = f"{DEST}/SM_HeroFacade"
if unreal.EditorAssetLibrary.does_asset_exist(path):
unreal.EditorAssetLibrary.delete_asset(path)