lighting: fixed daytime rig, real surface materials, crossing fix

Fixed daytime lighting and basic ambience for the DemoMap:

- Sun pitch -50 / yaw 120 (intensity 4, warm), sky light 2.6 with real-time
  capture, sky atmosphere, height fog, and an unbound post-process volume with
  fixed exposure (min = max brightness) so no shot washes out or crushes to
  black depending on how much sky it frames.
- Root cause of the washed-out look was NOT the sun: Blender's flat diffuse
  colours do not survive the FBX->Unreal import. Every site material arrived
  white and the terrain arrived with WorldGridMaterial. The demo builder now
  creates and assigns M_Asphalt / M_Concrete / M_Ground / M_RoadPaint.
- Crossing fix: each zebra band was its own object, so Blender renamed the
  duplicates .001/.002 and site assembly (exact-name append) kept only one.
  All bands are now a single Crossing_Markings mesh (36 verts, 3 bands).
- Terrain gets a Ground_Earth material in the builder so it stops importing
  as a white grid.
- Demo cameras: street-level no longer sits inside the facade body (was
  rendering its back face), and the crossing camera aims at the designated
  s=130.1 crossing (UE X -14956) looking along the crosswalk.

Screenshots re-rendered; the four demo views are marked needs-review for the
owner's approval. MEMORY.md records the material-import, exposure,
light_color, band-naming and camera-bounds traps.
This commit is contained in:
zwitschi committed 2026-11-07 17:56:21 +01:00
1 parent 121c0398b0
commit 830ce596fa
37 files changed
+241 -82

No files matched your search

Binary file not shown.
Binary file not shown.
+23 -19
View File
@@ -261,20 +261,24 @@ def main():
paint = bpy.data.materials.new("Road_Paint_White")
paint.diffuse_color = (0.85, 0.85, 0.85, 1.0)
def crossing_band(s_c):
# All three bands live in ONE object. Creating one object per crossing used
# to produce Crossing_Markings, .001 and .002; site assembly appends by the
# exact name, so only the first crossing ever reached the game.
band_verts, band_faces = [], []
def add_crossing_band(s_c):
# Stripes are emitted as two triangles each: the road crown makes the
# quad non-planar and the FBX SDK refuses to triangulate it on import
# ("unable to triangulate mesh" — the mesh is omitted entirely).
zone = 3.0 # band length along the road, m
w, g = 0.5, 0.5 # stripe width / gap, m
span = (SOUTH_GUTTER + 0.2, NORTH_GUTTER - 0.2)
verts, faces = [], []
n = int(zone // (w + g))
covered = n * w + (n - 1) * g # actual stripe coverage, centred on s_c
for k in range(n):
s0 = s_c - covered / 2 + k * (w + g)
corners = []
base_idx = len(verts) # verts grows only after the stripe is sampled
base_idx = len(band_verts) # sampled positions, then vertices
pts = []
for s_edge in (s0, s0 + w):
base, normal, _ = at(s_edge)
for off in span:
@@ -284,23 +288,23 @@ def main():
# interpolates the DEM along 1 m rows while the markings sample
# only their own corners, so a 1 cm lift left parts of the
# stripes sunk under the road and invisible in game.
corners.append((base_idx + len(corners), (x, y, (dem_z(x, y) or 0.0) + 0.06)))
# corners order: (s0,off_s), (s0,off_n), (s1,off_s), (s1,off_n)
idx = [c[0] for c in corners]
verts.extend(c[1] for c in corners)
faces.append((idx[0], idx[3], idx[1]))
faces.append((idx[0], idx[2], idx[3]))
mesh = bpy.data.meshes.new("Crossing_Markings")
mesh.from_pydata(verts, [], faces)
mesh.update()
mesh.materials.append(paint)
obj = bpy.data.objects.new("Crossing_Markings", mesh)
bpy.context.scene.collection.objects.link(obj)
return obj
pts.append((x, y, (dem_z(x, y) or 0.0) + 0.06))
# pts order: (s0,off_s), (s0,off_n), (s1,off_s), (s1,off_n)
band_verts.extend(pts)
band_faces.append((base_idx + 0, base_idx + 3, base_idx + 1))
band_faces.append((base_idx + 0, base_idx + 2, base_idx + 3))
for s_c in CROSSINGS:
band = crossing_band(s_c)
print(f"crossing band at s={s_c:.1f}: {len(band.data.polygons)} stripes")
add_crossing_band(s_c)
mesh = bpy.data.meshes.new("Crossing_Markings")
mesh.from_pydata(band_verts, [], band_faces)
mesh.update()
mesh.materials.append(paint)
markings = bpy.data.objects.new("Crossing_Markings", mesh)
bpy.context.scene.collection.objects.link(markings)
print(f"crossing markings: {len(CROSSINGS)} bands, "
f"{len(markings.data.vertices)} verts, {len(markings.data.polygons)} faces")
for obj in (road, sw_s, sw_n):
print(f"{obj.name:16} verts {len(obj.data.vertices):5} faces {len(obj.data.polygons):5}")
Binary file not shown.
+6
View File
@@ -224,6 +224,12 @@ def main():
mesh = bpy.data.meshes.new("Terrain_DEM")
mesh.from_pydata(verts, [], faces)
mesh.update()
# Without a material the mesh imports into Unreal with the default white
# material and blows out under the demo sun. A muted earth tone keeps the
# ground reading as ground.
ground = bpy.data.materials.new("Ground_Earth")
ground.diffuse_color = (0.30, 0.28, 0.25, 1.0)
mesh.materials.append(ground)
obj = bpy.data.objects.new("Terrain_DEM", mesh)
bpy.context.scene.collection.objects.link(obj)