Upgrade Ground Zero terrain material

This commit is contained in:
2026-05-21 15:12:30 +00:00
parent 13e931eb04
commit 98ab61a7a4
14 changed files with 148 additions and 23 deletions
+1 -1
View File
@@ -946,7 +946,7 @@ looks intentional, grounded, and investor-readable.
Required order:
- [ ] Replace or upgrade the terrain material first so Ground Zero no longer reads as flat tan placeholder ground.
- [x] Replace or upgrade the terrain material first so Ground Zero no longer reads as flat tan placeholder ground. Rebuilt `M_AGR_GZ_Terrain_CoastalScrub` as a procedural coastal scrub material that blends dry soil, scrub green, and sandy path color families with broad and fine noise, documented the visual baseline, and extended the natural-environment verifier so flat constant-color terrain fails.
- [ ] Replace or upgrade grasses, shrubs, and trees with believable coastal-scrub vegetation assets, density, color variation, scale variation, and LOD/performance limits.
- [ ] Replace or upgrade freshwater visuals with readable water surface, edge treatment, bank dressing, reflection/roughness tuning, and collectability cues.
- [ ] Replace or upgrade character bodies and clothing so selected characters read as realistic near-future post-collapse frontier people rather than template mannequins or proxy stacks.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,7 +6,9 @@ space.
## Scope
- Terrain receives a warm coastal scrub ground material.
- Terrain receives a procedural coastal scrub terrain material that blends
dry soil, scrub green, and sandy path color families with broad and fine
noise so Ground Zero does not read as flat tan placeholder ground.
- Foliage patch instances keep the current prototype meshes but use distinct
tree, shrub, and dry grass materials.
- Wood, fiber, stone, and freshwater actors receive distinct first-pass
@@ -48,7 +50,10 @@ space.
`Scripts/verify_ground_zero_natural_environment_pass.py` checks that the
materials exist, the landscape uses the terrain material, the foliage actor has
the expected investor-facing instance counts and material assignments, and
resource/water actors are visually dressed. It also checks the asset variation
resource/water actors are visually dressed. It also checks that the terrain
material contains procedural color breakup rather than a flat constant color:
noise, blend, and coastal-scrub color-vector expression families must be present
in the saved material package. It also checks the asset variation
layer: twenty-three labeled variation actors, at least four mesh silhouettes,
unique scale profiles, and coverage across tree, bush, grass, rock, and water
visual families. `Scripts/verify_native_placeholder_meshes.py` checks that playable
+95
View File
@@ -48,6 +48,10 @@ ENVIRONMENT_MATERIALS = {
"terrain": {
"path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Terrain_CoastalScrub",
"color": unreal.LinearColor(0.16, 0.23, 0.12, 1.0),
"dry_soil_color": unreal.LinearColor(0.28, 0.24, 0.16, 1.0),
"scrub_green_color": unreal.LinearColor(0.12, 0.22, 0.10, 1.0),
"sandy_path_color": unreal.LinearColor(0.42, 0.36, 0.23, 1.0),
"noise_scale": 42.0,
"roughness": 0.92,
},
"tree": {
@@ -837,11 +841,102 @@ def ensure_environment_materials():
material.set_editor_property("used_with_instanced_static_meshes", True)
unreal.MaterialEditingLibrary.recompile_material(material)
unreal.EditorAssetLibrary.save_asset(spec["path"], only_if_is_dirty=False)
if key == "terrain":
rebuild_ground_zero_terrain_material(material, spec)
created_or_loaded[key] = material
return created_or_loaded
def set_expression_property(expression, property_name, value):
try:
expression.set_editor_property(property_name, value)
return True
except Exception:
return False
def connect_expression(source, source_output, target, target_input):
try:
unreal.MaterialEditingLibrary.connect_material_expressions(source, source_output, target, target_input)
return True
except Exception as exc:
unreal.log_warning(f"Could not connect material expression input {target_input}: {exc}")
return False
def create_constant_color(material, color, x, y):
expression = unreal.MaterialEditingLibrary.create_material_expression(
material, unreal.MaterialExpressionConstant3Vector, x, y
)
expression.set_editor_property("constant", color)
return expression
def create_constant_scalar(material, value, x, y):
expression = unreal.MaterialEditingLibrary.create_material_expression(
material, unreal.MaterialExpressionConstant, x, y
)
expression.set_editor_property("r", value)
return expression
def rebuild_ground_zero_terrain_material(material, spec):
"""Build a simple procedural material so the landscape no longer reads as a flat color."""
if hasattr(unreal.MaterialEditingLibrary, "delete_all_material_expressions"):
unreal.MaterialEditingLibrary.delete_all_material_expressions(material)
dry_soil = create_constant_color(material, spec["dry_soil_color"], -900, -260)
scrub_green = create_constant_color(material, spec["scrub_green_color"], -900, -80)
sandy_path = create_constant_color(material, spec["sandy_path_color"], -900, 100)
broad_noise = unreal.MaterialEditingLibrary.create_material_expression(
material, unreal.MaterialExpressionNoise, -560, -160
)
set_expression_property(broad_noise, "scale", spec.get("noise_scale", 42.0))
set_expression_property(broad_noise, "quality", 3)
set_expression_property(broad_noise, "levels", 5)
fine_noise = unreal.MaterialEditingLibrary.create_material_expression(
material, unreal.MaterialExpressionNoise, -560, 100
)
set_expression_property(fine_noise, "scale", 145.0)
set_expression_property(fine_noise, "quality", 2)
set_expression_property(fine_noise, "levels", 3)
scrub_blend = unreal.MaterialEditingLibrary.create_material_expression(
material, unreal.MaterialExpressionLinearInterpolate, -260, -180
)
connect_expression(dry_soil, "", scrub_blend, "A")
connect_expression(scrub_green, "", scrub_blend, "B")
connect_expression(broad_noise, "", scrub_blend, "Alpha")
final_blend = unreal.MaterialEditingLibrary.create_material_expression(
material, unreal.MaterialExpressionLinearInterpolate, 40, -70
)
connect_expression(scrub_blend, "", final_blend, "A")
connect_expression(sandy_path, "", final_blend, "B")
connect_expression(fine_noise, "", final_blend, "Alpha")
unreal.MaterialEditingLibrary.connect_material_property(
final_blend, "", unreal.MaterialProperty.MP_BASE_COLOR
)
roughness = create_constant_scalar(material, spec["roughness"], -260, 160)
unreal.MaterialEditingLibrary.connect_material_property(
roughness, "", unreal.MaterialProperty.MP_ROUGHNESS
)
specular = create_constant_scalar(material, 0.18, -260, 260)
unreal.MaterialEditingLibrary.connect_material_property(
specular, "", unreal.MaterialProperty.MP_SPECULAR
)
material.set_editor_property("use_material_attributes", False)
unreal.MaterialEditingLibrary.recompile_material(material)
unreal.EditorAssetLibrary.save_asset(spec["path"], only_if_is_dirty=False)
unreal.log("Rebuilt Ground Zero terrain material with coastal scrub color variation and procedural noise.")
def apply_material_to_actor_meshes(actor, material):
applied_count = 0
for component in actor.get_components_by_class(unreal.StaticMeshComponent):
@@ -82,6 +82,28 @@ def assert_asset(path):
return asset
def verify_terrain_material_is_not_flat(material, failures):
project_root = unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_dir())
material_package = project_root + "Content/Agrarian/Materials/M_AGR_GZ_Terrain_CoastalScrub.uasset"
try:
with open(material_package, "rb") as handle:
package_bytes = handle.read()
except Exception as exc:
failures.append(f"could not inspect terrain material package: {exc}")
return
noise_count = package_bytes.count(b"MaterialExpressionNoise")
lerp_count = package_bytes.count(b"MaterialExpressionLinearInterpolate")
color_count = package_bytes.count(b"MaterialExpressionConstant3Vector")
if noise_count < 1:
failures.append("terrain material should include a noise expression for color breakup")
if lerp_count < 1:
failures.append("terrain material should include a blend expression instead of a flat base color")
if color_count < 1:
failures.append("terrain material should include coastal scrub color vector expressions")
def main():
if not unreal.EditorLevelLibrary.load_level(MAP_PATH):
raise RuntimeError(f"Could not load map: {MAP_PATH}")
@@ -98,6 +120,7 @@ def main():
expected = MATERIALS["terrain"]
if assigned != expected:
failures.append(f"landscape material expected {expected}, got {assigned}")
verify_terrain_material_is_not_flat(materials["terrain"], failures)
foliage_actors = [actor for actor in actors if get_actor_label(actor) == FOLIAGE_LABEL]
if len(foliage_actors) != 1:
@@ -187,8 +210,10 @@ def main():
roadmap = unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_dir()) + "AGRARIAN_DEVELOPMENT_ROADMAP.md"
for path, snippet in [
(docs, "asset variation"),
(docs, "procedural coastal scrub terrain material"),
(roadmap, "[x] Replace grey-box environment presentation with an MVP natural environment pass"),
(roadmap, "[x] Add first-pass environment asset variation"),
(roadmap, "[x] Replace or upgrade the terrain material first so Ground Zero no longer reads as flat tan placeholder ground."),
]:
with open(path, "r", encoding="utf-8") as handle:
text = handle.read()