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: 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 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 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. - [ ] 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 ## 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 - Foliage patch instances keep the current prototype meshes but use distinct
tree, shrub, and dry grass materials. tree, shrub, and dry grass materials.
- Wood, fiber, stone, and freshwater actors receive distinct first-pass - 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 `Scripts/verify_ground_zero_natural_environment_pass.py` checks that the
materials exist, the landscape uses the terrain material, the foliage actor has materials exist, the landscape uses the terrain material, the foliage actor has
the expected investor-facing instance counts and material assignments, and 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, layer: twenty-three labeled variation actors, at least four mesh silhouettes,
unique scale profiles, and coverage across tree, bush, grass, rock, and water unique scale profiles, and coverage across tree, bush, grass, rock, and water
visual families. `Scripts/verify_native_placeholder_meshes.py` checks that playable visual families. `Scripts/verify_native_placeholder_meshes.py` checks that playable
+95
View File
@@ -48,6 +48,10 @@ ENVIRONMENT_MATERIALS = {
"terrain": { "terrain": {
"path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Terrain_CoastalScrub", "path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Terrain_CoastalScrub",
"color": unreal.LinearColor(0.16, 0.23, 0.12, 1.0), "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, "roughness": 0.92,
}, },
"tree": { "tree": {
@@ -837,11 +841,102 @@ def ensure_environment_materials():
material.set_editor_property("used_with_instanced_static_meshes", True) material.set_editor_property("used_with_instanced_static_meshes", True)
unreal.MaterialEditingLibrary.recompile_material(material) unreal.MaterialEditingLibrary.recompile_material(material)
unreal.EditorAssetLibrary.save_asset(spec["path"], only_if_is_dirty=False) 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 created_or_loaded[key] = material
return created_or_loaded 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): def apply_material_to_actor_meshes(actor, material):
applied_count = 0 applied_count = 0
for component in actor.get_components_by_class(unreal.StaticMeshComponent): for component in actor.get_components_by_class(unreal.StaticMeshComponent):
@@ -82,6 +82,28 @@ def assert_asset(path):
return asset 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(): def main():
if not unreal.EditorLevelLibrary.load_level(MAP_PATH): if not unreal.EditorLevelLibrary.load_level(MAP_PATH):
raise RuntimeError(f"Could not load map: {MAP_PATH}") raise RuntimeError(f"Could not load map: {MAP_PATH}")
@@ -98,6 +120,7 @@ def main():
expected = MATERIALS["terrain"] expected = MATERIALS["terrain"]
if assigned != expected: if assigned != expected:
failures.append(f"landscape material expected {expected}, got {assigned}") 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] foliage_actors = [actor for actor in actors if get_actor_label(actor) == FOLIAGE_LABEL]
if len(foliage_actors) != 1: 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" roadmap = unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_dir()) + "AGRARIAN_DEVELOPMENT_ROADMAP.md"
for path, snippet in [ for path, snippet in [
(docs, "asset variation"), (docs, "asset variation"),
(docs, "procedural coastal scrub terrain material"),
(roadmap, "[x] Replace grey-box environment presentation with an MVP natural environment pass"), (roadmap, "[x] Replace grey-box environment presentation with an MVP natural environment pass"),
(roadmap, "[x] Add first-pass environment asset variation"), (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: with open(path, "r", encoding="utf-8") as handle:
text = handle.read() text = handle.read()