Bake library AO and lift environment textures
Full build / Full desktop build + optional release (push) Successful in 3m43s
Full build / Import + headless tests (push) Successful in 29s

Phase 4 of the v0.6.0 graphics plan:

- tools/bake_ao.py bakes 256² AO maps for every Blender character/gear/prop
  (Cycles, existing UVs) next to each GLB; Materials.apply_ao() wires the red
  channel into character slots, gear, and props. Compatibility has no SSAO.
- Regenerated brick/concrete/asphalt at 1024² with OpenRouter, derived tangent
  normal maps via tools/make_normal_maps.py, and wired them through
  SurfaceMaterials.textured() without changing UV scales.
- Evaluated Forward+: boots on desktop Vulkan, but the renderer is project-wide
  and Web must stay GL Compatibility; v0.6.0 keeps Compatibility (decision in
  MEMORY/plan). A desktop-only override is deferred.
- Recaptured screenshots; compare 11 good / 0 bad. Fixed marquee text alignment.
- Tests: baked AO maps exist for all 46 assets; prop/character AO material
  checks; headless 222/222, Python 69/69, all smokes pass.
- Version 0.6.0; ADR-0013 accepted; README/architecture/blender-assets/plan and
  TODO/DONE updated.
This commit is contained in:
zwitschi committed 2026-11-01 15:07:27 +01:00
1 parent d8cfddbf89
commit d872034388
78 files changed
+390 -28

No files matched your search

+15 -2
View File
@@ -125,6 +125,7 @@ func _build_building() -> void:
door_model.position = Vector3(0, 0.0, -3.84)
building.add_child(door_model)
library_nodes.append(door_model)
_apply_library_ao(door_model, "double_door")
for material in _emissive_materials(door_model):
if not _lamp_bulb_mats.has(material):
_lamp_bulb_mats.append(material)
@@ -287,6 +288,7 @@ func _build_marquee() -> void:
library.position = Vector3(0, 5.62, -3.9)
root.add_child(library)
library_nodes.append(library)
_apply_library_ao(library, "marquee")
for material in _emissive_materials(library):
if not _marquee_emissive.has(material):
_marquee_emissive.append(material)
@@ -304,7 +306,7 @@ func _build_marquee() -> void:
marquee_label = Label3D.new()
marquee_label.name = "MarqueeLabel"
marquee_label.text = "GIG LINE"
marquee_label.position = Vector3(0, 6.45, -2.12)
marquee_label.position = Vector3(0, 6.36, -1.9)
marquee_label.modulate = Color(1, 0.6, 0.9)
marquee_label.font_size = 160
marquee_label.pixel_size = 0.004
@@ -326,6 +328,7 @@ func _build_library_dressing() -> void:
rail.rotation_degrees.y = 90.0
root.add_child(rail)
library_nodes.append(rail)
_apply_library_ao(rail, "handrail")
if ResourceLoader.exists(BLENDER_PROP_DIR + "sedan.glb"):
var sedan := _add_prop_model(root, "sedan", Vector3(6.0, 0.1, 17.0), 180.0)
sedan.name = "ParkedSedan"
@@ -343,6 +346,7 @@ func _build_library_dressing() -> void:
lantern.position = Vector3(side, 3.3, -3.78)
root.add_child(lantern)
library_nodes.append(lantern)
_apply_library_ao(lantern, "wall_lantern")
for material in _emissive_materials(lantern):
if not _lamp_bulb_mats.has(material):
_lamp_bulb_mats.append(material)
@@ -407,6 +411,14 @@ func library_triangle_count() -> int:
total += vertices.size() / 3
return total
## Apply the baked AO map that sits next to a library prop GLB.
func _apply_library_ao(model: Node3D, prop: String) -> void:
var texture := Materials.load_ao(BLENDER_PROP_DIR + prop + "_ao.png")
if texture == null:
return
for node in model.find_children("*", "MeshInstance3D", true, false):
Materials.apply_ao(node as MeshInstance3D, texture)
## Register a library prop's emissive surfaces so night mode can switch them on.
func _collect_emissive(holder: Node3D) -> void:
for node in holder.find_children("*", "MeshInstance3D", true, false):
@@ -544,7 +556,8 @@ func _add_prop_model(parent: Node3D, prop: String, pos: Vector3,
loaded_models.append(prop)
if ResourceLoader.exists(BLENDER_PROP_DIR + prop + ".glb"):
library_props.append(prop)
library_nodes.append(holder)
library_nodes.append(holder)
_apply_library_ao(model, prop)
else:
var fallback := _fallback_prop(prop)
holder.add_child(fallback)