materials: look-developed surface materials for every exported asset
Replaces the flat Constant3Vector fills with a procedural material set built in ensure_material(): base colour blended between two shades by world-space noise for large-scale variation, plus explicit roughness and specular so each surface type reads. - M_Asphalt 0.92, M_Concrete 0.88, M_KerbConcrete 0.85, M_Ground 0.98, M_RoadPaint 0.62, M_FacadeStone 0.72, M_Stucco 0.86 - Site meshes take their material by mesh name; the hero facade and the two north-side buildings take theirs by actor label, so they are covered even though other scripts stage them. - Materials are deleted and rebuilt on every run, so a look change cannot be masked by a stale asset. Screenshots re-rendered: the facade reads as warm stone with surface variation, asphalt as dark asphalt with grain, concrete and kerbs lighter, and the north buildings in stucco instead of flat grey. Docs: Screenshots/README documents the material set; TODO marks the item done and leaves the richer texture-based pass to the 0.1.4 facade patch.
This commit is contained in:
1 parent
75694d2577
commit
45a206e45f
26 files changed
+172
-69
No files matched your search
+115
-25
@@ -50,26 +50,78 @@ def spawn(cls, loc, label, rot=None):
|
||||
return actor
|
||||
|
||||
|
||||
def ensure_material(asset_name, rgb):
|
||||
"""Create/reuse a flat-colour material under /Game/Hollywood/Site.
|
||||
def ensure_material(mat_name, spec):
|
||||
"""Create/reuse a look-developed material under /Game/Hollywood/Site.
|
||||
|
||||
Used where the FBX material round-trip does not survive import: the terrain
|
||||
arrives with WorldGridMaterial (UE's bright default grid), which reads as a
|
||||
blown-out white field under the demo sun.
|
||||
Blender's flat diffuse colours do not survive the FBX -> Unreal import (the
|
||||
terrain even lands with WorldGridMaterial), so the demo has always assigned
|
||||
its own material per surface. Those were flat colour only, which read as
|
||||
plastic. Each material here gets:
|
||||
|
||||
* base colour with a subtle world-space noise blend between two shades,
|
||||
so asphalt and concrete have large-scale variation instead of a fill,
|
||||
* an explicit roughness (and specular) so the surface type is readable,
|
||||
wet-looking stone vs matte concrete vs chalky road paint.
|
||||
|
||||
spec keys: dark, bright (RGB tuples), roughness, specular, noise_scale
|
||||
(world units per noise cycle; smaller = larger features).
|
||||
"""
|
||||
path = f"/Game/Hollywood/Site/{asset_name}"
|
||||
path = f"/Game/Hollywood/Site/{mat_name}"
|
||||
if unreal.EditorAssetLibrary.does_asset_exist(path):
|
||||
return unreal.EditorAssetLibrary.load_asset(path)
|
||||
# Rebuilt every run so a look change cannot be masked by a stale asset.
|
||||
unreal.EditorAssetLibrary.delete_asset(path)
|
||||
|
||||
mat = unreal.AssetToolsHelpers.get_asset_tools().create_asset(
|
||||
asset_name, "/Game/Hollywood/Site", unreal.Material, unreal.MaterialFactoryNew())
|
||||
node = unreal.MaterialEditingLibrary.create_material_expression(
|
||||
mat, unreal.MaterialExpressionConstant3Vector)
|
||||
node.set_editor_property("constant", unreal.LinearColor(rgb[0], rgb[1], rgb[2], 1.0))
|
||||
unreal.MaterialEditingLibrary.connect_material_property(
|
||||
node, "", unreal.MaterialProperty.MP_BASE_COLOR)
|
||||
unreal.MaterialEditingLibrary.recompile_material(mat)
|
||||
mat_name, "/Game/Hollywood/Site", unreal.Material, unreal.MaterialFactoryNew())
|
||||
lib = unreal.MaterialEditingLibrary
|
||||
|
||||
def node(cls):
|
||||
return lib.create_material_expression(mat, cls)
|
||||
|
||||
def c3(rgb):
|
||||
n = node(unreal.MaterialExpressionConstant3Vector)
|
||||
n.set_editor_property("constant", unreal.LinearColor(rgb[0], rgb[1], rgb[2], 1.0))
|
||||
return n
|
||||
|
||||
def scalar(value):
|
||||
n = node(unreal.MaterialExpressionConstant)
|
||||
n.set_editor_property("r", float(value))
|
||||
return n
|
||||
|
||||
# world position -> noise -> lerp(dark, bright) -> base colour
|
||||
wp = node(unreal.MaterialExpressionWorldPosition)
|
||||
scale = scalar(spec.get("noise_scale", 0.004))
|
||||
mul = node(unreal.MaterialExpressionMultiply)
|
||||
lib.connect_material_expressions(wp, "", mul, "A")
|
||||
lib.connect_material_expressions(scale, "", mul, "B")
|
||||
|
||||
noise = node(unreal.MaterialExpressionNoise)
|
||||
for prop, value in (("scale", 1.0), ("quality", 1), ("levels", 2),
|
||||
("output_min", 0.0), ("output_max", 1.0)):
|
||||
try:
|
||||
noise.set_editor_property(prop, value)
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
lib.connect_material_expressions(mul, "", noise, "Position")
|
||||
|
||||
lerp = node(unreal.MaterialExpressionLinearInterpolate)
|
||||
lib.connect_material_expressions(c3(spec["dark"]), "", lerp, "A")
|
||||
lib.connect_material_expressions(c3(spec["bright"]), "", lerp, "B")
|
||||
lib.connect_material_expressions(noise, "", lerp, "Alpha")
|
||||
lib.connect_material_property(lerp, "", unreal.MaterialProperty.MP_BASE_COLOR)
|
||||
|
||||
lib.connect_material_property(
|
||||
scalar(spec.get("roughness", 0.85)), "", unreal.MaterialProperty.MP_ROUGHNESS)
|
||||
try:
|
||||
lib.connect_material_property(
|
||||
scalar(spec.get("specular", 0.3)), "", unreal.MaterialProperty.MP_SPECULAR)
|
||||
except Exception: # noqa: BLE001 - specular input is optional in UE5
|
||||
pass
|
||||
|
||||
lib.recompile_material(mat)
|
||||
unreal.EditorAssetLibrary.save_loaded_asset(mat)
|
||||
log(f"created material {asset_name} rgb={rgb}")
|
||||
log(f"material {mat_name}: rough={spec.get('roughness')} "
|
||||
f"dark={spec['dark']} bright={spec['bright']}")
|
||||
return mat
|
||||
|
||||
|
||||
@@ -209,17 +261,38 @@ def build():
|
||||
# builds a material with a default white base colour, and the terrain even
|
||||
# lands with WorldGridMaterial. Every site surface therefore rendered white,
|
||||
# which is what washed out the demo. Assign explicit colours per mesh here.
|
||||
SURFACE_MATERIALS = {
|
||||
"Terrain_DEM": ("M_Ground", (0.30, 0.28, 0.25)),
|
||||
"Road_Hollywood": ("M_Asphalt", (0.055, 0.055, 0.06)),
|
||||
"Sidewalk_South": ("M_Concrete", (0.52, 0.50, 0.47)),
|
||||
"Sidewalk_North": ("M_Concrete", (0.52, 0.50, 0.47)),
|
||||
"Kerb_South": ("M_Concrete", (0.52, 0.50, 0.47)),
|
||||
"Kerb_North": ("M_Concrete", (0.52, 0.50, 0.47)),
|
||||
"Crossing_Markings": ("M_RoadPaint", (0.80, 0.80, 0.78)),
|
||||
# Look-developed surface materials (see ensure_material for the graph).
|
||||
MATERIAL_SPECS = {
|
||||
"M_Asphalt": dict(dark=(0.040, 0.040, 0.044), bright=(0.072, 0.072, 0.078),
|
||||
roughness=0.92, specular=0.22, noise_scale=0.006),
|
||||
"M_Concrete": dict(dark=(0.44, 0.42, 0.39), bright=(0.56, 0.54, 0.50),
|
||||
roughness=0.88, specular=0.28, noise_scale=0.004),
|
||||
"M_KerbConcrete": dict(dark=(0.47, 0.45, 0.42), bright=(0.60, 0.58, 0.54),
|
||||
roughness=0.85, specular=0.30, noise_scale=0.008),
|
||||
"M_Ground": dict(dark=(0.22, 0.20, 0.17), bright=(0.33, 0.30, 0.26),
|
||||
roughness=0.98, specular=0.15, noise_scale=0.003),
|
||||
"M_RoadPaint": dict(dark=(0.74, 0.74, 0.72), bright=(0.86, 0.86, 0.84),
|
||||
roughness=0.62, specular=0.35, noise_scale=0.02),
|
||||
"M_FacadeStone": dict(dark=(0.62, 0.58, 0.50), bright=(0.74, 0.70, 0.62),
|
||||
roughness=0.72, specular=0.32, noise_scale=0.005),
|
||||
"M_Stucco": dict(dark=(0.52, 0.48, 0.42), bright=(0.66, 0.62, 0.55),
|
||||
roughness=0.86, specular=0.25, noise_scale=0.004),
|
||||
}
|
||||
SURFACE_MATERIALS = {
|
||||
"Terrain_DEM": "M_Ground",
|
||||
"Road_Hollywood": "M_Asphalt",
|
||||
"Sidewalk_South": "M_Concrete",
|
||||
"Sidewalk_North": "M_Concrete",
|
||||
"Kerb_South": "M_KerbConcrete",
|
||||
"Kerb_North": "M_KerbConcrete",
|
||||
"Crossing_Markings": "M_RoadPaint",
|
||||
}
|
||||
built = {}
|
||||
for mat_name, spec in MATERIAL_SPECS.items():
|
||||
built[mat_name] = ensure_material(mat_name, spec)
|
||||
|
||||
imported = 0
|
||||
for name, (mat_name, rgb) in SURFACE_MATERIALS.items():
|
||||
for name, mat_name in SURFACE_MATERIALS.items():
|
||||
mesh = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Site/{name}")
|
||||
if mesh is None:
|
||||
continue
|
||||
@@ -227,12 +300,29 @@ def build():
|
||||
if actor:
|
||||
actor.static_mesh_component.set_static_mesh(mesh)
|
||||
actor.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE)
|
||||
mat = ensure_material(mat_name, rgb)
|
||||
mat = built.get(mat_name)
|
||||
if mat:
|
||||
actor.static_mesh_component.set_material(0, mat)
|
||||
imported += 1
|
||||
log(f"staged {imported} site meshes with explicit surface materials")
|
||||
|
||||
# The hero facade and the two north-side buildings are staged by other
|
||||
# scripts (or reused between rebuilds), so assign their look materials by
|
||||
# actor label after everything is in the level.
|
||||
LABEL_MATERIALS = {"Hero_Facade": "M_FacadeStone",
|
||||
"North_TCL_Chinese": "M_Stucco",
|
||||
"North_Madame_Tussauds": "M_Stucco"}
|
||||
assigned = 0
|
||||
for actor in unreal.EditorLevelLibrary.get_all_level_actors():
|
||||
mat_name = LABEL_MATERIALS.get(actor.get_actor_label())
|
||||
if not mat_name or mat_name not in built:
|
||||
continue
|
||||
comp = actor.get_component_by_class(unreal.StaticMeshComponent)
|
||||
if comp and comp.static_mesh:
|
||||
comp.set_material(0, built[mat_name])
|
||||
assigned += 1
|
||||
log(f"assigned look materials to {assigned} facade/background actors")
|
||||
|
||||
# --- player start: north sidewalk, facing the hero frontage -------------
|
||||
# Signs were measured from the imported meshes rather than assumed: the FBX
|
||||
# preset flips north, so in Unreal the road spans Y -1382..+1047, the NORTH
|
||||
|
||||
Reference in new issue
Block a user