Adopt the realism pass and refresh screenshots/docs
- add the v2 runtime picks: per-phase skyline panoramas, brick, sidewalk pavers, dry/wet asphalt, awning canvas, and their normal maps - add the realism tooling and evidence: tools/scene_concepts.py, capture_realism.gd, concept frames, reports/sheets, and tests - retune phase lighting/fog/exposure, add sun yaw, brighten the night glow, add the flashlight beam mesh, and rebuild the planter/tree assets - regenerate all eleven staged screenshots from the current build; compare is 11 good / 0 bad - finish the visual-alignment closeout in DONE/TODO, update architecture, blender-assets, PLAN, and ignore rules (editable Blender sources untracked) - keep the judge retry and document the verified default judge model Verification: headless 227/227; Python tooling 86/86; check_screenshots 11/11.
This commit is contained in:
1 parent
26ecb93fd4
commit
d7e523f63c
70 files changed
+4042
-101
No files matched your search
+35
-11
@@ -46,19 +46,19 @@ var _last_phase: int = -1
|
||||
## structure — phase-based lighting). Night phases switch street lamps on.
|
||||
const PHASE_LIGHTING := {
|
||||
Rules.Phase.EARLY: {
|
||||
"sun_energy": 0.62, "sun_angle": -50.0, "sun_color": Color(1.0, 0.96, 0.88),
|
||||
"sun_energy": 0.62, "sun_angle": -34.0, "sun_yaw": -30.0, "sun_color": Color(1.0, 0.96, 0.88),
|
||||
"sky_top": Color(0.32, 0.55, 0.95), "sky_horizon": Color(0.82, 0.88, 0.95),
|
||||
"sky_ground": Color(0.45, 0.5, 0.55), "ambient": Color(0.75, 0.78, 0.85), "night": false,
|
||||
"exposure": 0.6, "glow": 0.35, "fog_color": Color(0.80, 0.85, 0.92),
|
||||
"fog_density": 0.0026, "fog_energy": 0.75, "contrast": 1.05, "saturation": 1.06,
|
||||
"exposure": 0.6, "glow": 0.45, "fog_color": Color(0.87, 0.83, 0.76),
|
||||
"fog_density": 0.0034, "fog_energy": 0.5, "contrast": 1.05, "saturation": 1.04,
|
||||
"ambient_energy": 0.1,
|
||||
},
|
||||
Rules.Phase.MIDDAY: {
|
||||
"sun_energy": 1.0, "sun_angle": -62.0, "sun_color": Color(1.0, 0.99, 0.96),
|
||||
"sun_energy": 1.0, "sun_angle": -62.0, "sun_yaw": -30.0, "sun_color": Color(1.0, 0.99, 0.96),
|
||||
"sky_top": Color(0.3, 0.52, 0.95), "sky_horizon": Color(0.85, 0.9, 0.96),
|
||||
"sky_ground": Color(0.5, 0.54, 0.58), "ambient": Color(0.85, 0.88, 0.95), "night": false,
|
||||
"exposure": 0.6, "glow": 0.3, "fog_color": Color(0.84, 0.88, 0.94),
|
||||
"fog_density": 0.0021, "fog_energy": 0.8, "contrast": 1.06, "saturation": 1.05,
|
||||
"exposure": 0.6, "glow": 0.4, "fog_color": Color(0.88, 0.87, 0.83),
|
||||
"fog_density": 0.0028, "fog_energy": 0.5, "contrast": 1.06, "saturation": 1.04,
|
||||
"ambient_energy": 0.28,
|
||||
},
|
||||
# Evening is dusk, not a bright sunset: the sun is almost down and the street
|
||||
@@ -75,7 +75,7 @@ const PHASE_LIGHTING := {
|
||||
"sun_energy": 0.02, "sun_angle": -8.0, "sun_color": Color(0.3, 0.35, 0.5),
|
||||
"sky_top": Color(0.015, 0.02, 0.05), "sky_horizon": Color(0.06, 0.07, 0.13),
|
||||
"sky_ground": Color(0.03, 0.03, 0.05), "ambient": Color(0.06, 0.07, 0.12), "night": true,
|
||||
"exposure": 0.95, "glow": 1.0, "fog_color": Color(0.08, 0.1, 0.2),
|
||||
"exposure": 0.95, "glow": 1.15, "fog_color": Color(0.08, 0.1, 0.2),
|
||||
"fog_density": 0.0028, "fog_energy": 0.45, "contrast": 1.12, "saturation": 1.0,
|
||||
"ambient_energy": 0.22,
|
||||
},
|
||||
@@ -190,10 +190,31 @@ func _build_guard() -> GuardController:
|
||||
# Aim straight ahead: tilting it down made the cone hit the ground right in
|
||||
# front of the guard instead of the person or door being checked.
|
||||
flashlight.rotation_degrees = Vector3.ZERO
|
||||
flashlight.spot_range = 22.0
|
||||
flashlight.spot_angle = 30.0
|
||||
flashlight.spot_range = 16.0
|
||||
flashlight.spot_angle = 20.0
|
||||
flashlight.light_energy = 4.0
|
||||
flashlight.spot_attenuation = 0.6
|
||||
flashlight.spot_attenuation = 0.8
|
||||
flashlight.spot_angle_attenuation = 0.9
|
||||
# Visible beam: a fading additive cone parented to the lamp, because the
|
||||
# Compatibility renderer has no volumetric light. It is offset forward so the
|
||||
# near cap never fills the first-person view.
|
||||
var beam := MeshInstance3D.new()
|
||||
beam.name = "Beam"
|
||||
var cone := CylinderMesh.new()
|
||||
cone.top_radius = 1.0
|
||||
cone.bottom_radius = 0.05
|
||||
cone.height = 5.5
|
||||
cone.radial_segments = 16
|
||||
cone.rings = 1
|
||||
cone.cap_top = false
|
||||
cone.cap_bottom = false
|
||||
beam.mesh = cone
|
||||
# Cylinder axis is +Y; -90° about X aims the wide end along -Z (forward).
|
||||
beam.rotation_degrees = Vector3(-90, 0, 0)
|
||||
beam.position = Vector3(0, 0, -3.65)
|
||||
beam.material_override = Materials.light_beam()
|
||||
beam.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
flashlight.add_child(beam)
|
||||
head.add_child(flashlight)
|
||||
var collider := CollisionShape3D.new()
|
||||
var shape := CapsuleShape3D.new()
|
||||
@@ -484,7 +505,10 @@ func _apply_phase_lighting() -> void:
|
||||
if _sun:
|
||||
_sun.light_energy = float(cfg.get("sun_energy", 1.0))
|
||||
_sun.light_color = cfg.get("sun_color", Color.WHITE)
|
||||
_sun.rotation_degrees = Vector3(float(cfg.get("sun_angle", -50.0)), -30, 0)
|
||||
# `sun_yaw` sets which side of the street the light comes from; the
|
||||
# entrance view reads as shade when the sun is off the facade.
|
||||
_sun.rotation_degrees = Vector3(float(cfg.get("sun_angle", -50.0)),
|
||||
float(cfg.get("sun_yaw", -30.0)), 0)
|
||||
if _sky:
|
||||
_sky.sky_top_color = cfg.get("sky_top", Color.BLUE)
|
||||
_sky.sky_horizon_color = cfg.get("sky_horizon", Color.WHITE)
|
||||
|
||||
+43
-11
@@ -9,13 +9,19 @@ extends RefCounted
|
||||
## UV scaling, and texture filtering in one place so the world builder, the NPC
|
||||
## avatars, and the props all look like one scene.
|
||||
|
||||
const T_BRICK := preload("res://assets/textures/ai/game_ready/brick_facade.png")
|
||||
const T_CONCRETE := preload("res://assets/textures/ai/game_ready/concrete.png")
|
||||
const T_ASPHALT := preload("res://assets/textures/ai/game_ready/asphalt.png")
|
||||
const T_AWNING := preload("res://assets/textures/ai/game_ready/awning.png")
|
||||
const T_BRICK_NORMAL := preload("res://assets/textures/ai/game_ready/brick_facade_normal.png")
|
||||
const T_CONCRETE_NORMAL := preload("res://assets/textures/ai/game_ready/concrete_normal.png")
|
||||
const T_ASPHALT_NORMAL := preload("res://assets/textures/ai/game_ready/asphalt_normal.png")
|
||||
## Realism-pass picks (tools/scene_concepts.py promote): brick, sidewalk pavers and
|
||||
## asphalt replaced the flat first-generation tiles. Normal maps carry the mortar,
|
||||
## paver joints and aggregate that Compatibility cannot get from screen space.
|
||||
const PICK_DIR := "res://assets/textures/ai/game_ready/v2/picks/"
|
||||
const T_BRICK := preload(PICK_DIR + "brick_facade.png")
|
||||
const T_CONCRETE := preload(PICK_DIR + "sidewalk_pavers.png")
|
||||
const T_ASPHALT := preload(PICK_DIR + "asphalt.png")
|
||||
const T_ASPHALT_WET := preload(PICK_DIR + "asphalt_wet.png")
|
||||
const T_AWNING := preload(PICK_DIR + "awning_canvas.png")
|
||||
const T_BRICK_NORMAL := preload(PICK_DIR + "brick_facade_normal.png")
|
||||
const T_CONCRETE_NORMAL := preload(PICK_DIR + "sidewalk_pavers_normal.png")
|
||||
const T_ASPHALT_NORMAL := preload(PICK_DIR + "asphalt_normal.png")
|
||||
const T_ASPHALT_WET_NORMAL := preload(PICK_DIR + "asphalt_wet_normal.png")
|
||||
|
||||
## Roughness / metallic pairs per surface role. Exposed so tests and tooling can
|
||||
## assert the profiles instead of hard-coding numbers twice.
|
||||
@@ -90,10 +96,15 @@ static func brick(tint: Color = Color(1, 1, 1), uv_scale: Vector2 = Vector2(10,
|
||||
static func concrete(tint: Color = Color(1, 1, 1), uv_scale: Vector2 = Vector2(9, 2.4)) -> StandardMaterial3D:
|
||||
return textured(T_CONCRETE, tint, uv_scale, "concrete", T_CONCRETE_NORMAL)
|
||||
|
||||
static func asphalt(tint: Color = Color(1, 1, 1), uv_scale: Vector2 = Vector2(30, 7)) -> StandardMaterial3D:
|
||||
var mat := textured(T_ASPHALT, tint, uv_scale, "asphalt", T_ASPHALT_NORMAL)
|
||||
# Damp asphalt at night picks up the street lamps and the marquee.
|
||||
mat.metallic_specular = 0.6
|
||||
static func asphalt(tint: Color = Color(1, 1, 1), uv_scale: Vector2 = Vector2(30, 7),
|
||||
wet: bool = false) -> StandardMaterial3D:
|
||||
var texture: Texture2D = T_ASPHALT_WET if wet else T_ASPHALT
|
||||
var normal: Texture2D = T_ASPHALT_WET_NORMAL if wet else T_ASPHALT_NORMAL
|
||||
var mat := textured(texture, tint, uv_scale, "asphalt", normal)
|
||||
# Damp asphalt at night picks up the street lamps and the marquee: glossier
|
||||
# and slightly metallic so the lamp pools read as reflections.
|
||||
mat.metallic_specular = 0.75 if wet else 0.6
|
||||
mat.roughness = 0.38 if wet else float(profile("asphalt").get("roughness", 0.72))
|
||||
return mat
|
||||
|
||||
static func awning(tint: Color = Color(1, 1, 1), uv_scale: Vector2 = Vector2(4, 1)) -> StandardMaterial3D:
|
||||
@@ -117,6 +128,27 @@ static func emissive(color: Color, energy: float = 1.4) -> StandardMaterial3D:
|
||||
mat.roughness = 0.4
|
||||
return mat
|
||||
|
||||
## Fake light shaft for the flashlight (Compatibility has no volumetrics).
|
||||
## Additive, unshaded, no depth write: it glows over the scene, is still occluded
|
||||
## by walls, and fades out along its length instead of ending in a hard disc.
|
||||
static func light_beam() -> StandardMaterial3D:
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
mat.blend_mode = BaseMaterial3D.BLEND_MODE_ADD
|
||||
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||
mat.depth_draw_mode = BaseMaterial3D.DEPTH_DRAW_DISABLED
|
||||
mat.albedo_color = Color(1.0, 0.95, 0.82, 1.0)
|
||||
var fade := Gradient.new()
|
||||
fade.set_color(0, Color(1.0, 0.95, 0.82, 0.0))
|
||||
fade.add_point(0.18, Color(1.0, 0.95, 0.82, 0.22))
|
||||
fade.set_color(fade.get_point_count() - 1, Color(1.0, 0.95, 0.82, 0.0))
|
||||
var ramp := GradientTexture1D.new()
|
||||
ramp.gradient = fade
|
||||
mat.albedo_texture = ramp
|
||||
mat.texture_filter = BaseMaterial3D.TEXTURE_FILTER_LINEAR
|
||||
return mat
|
||||
|
||||
## Dark glass for windows and the entrance door. The alpha keeps the interior
|
||||
## visible, and the low roughness gives the facade a highlight instead of a hole.
|
||||
static func glass(color: Color = Color(0.42, 0.56, 0.72, 0.75)) -> StandardMaterial3D:
|
||||
|
||||
+101
-24
@@ -2,11 +2,25 @@ class_name WorldBuilder
|
||||
## Procedurally assembles a minimal Hollywood Boulevard entrance prototype:
|
||||
|
||||
const Materials := preload("res://src/game/materials.gd")
|
||||
const T_BRICK := preload("res://assets/textures/ai/game_ready/brick_facade.png")
|
||||
const T_CONCRETE := preload("res://assets/textures/ai/game_ready/concrete.png")
|
||||
const T_ASPHALT := preload("res://assets/textures/ai/game_ready/asphalt.png")
|
||||
const T_AWNING := preload("res://assets/textures/ai/game_ready/awning.png")
|
||||
const T_SKYLINE := preload("res://assets/textures/skyline.png")
|
||||
const Rules := preload("res://src/core/rules.gd")
|
||||
const PICK_DIR := "res://assets/textures/ai/game_ready/v2/picks/"
|
||||
const T_BRICK := preload(PICK_DIR + "brick_facade.png")
|
||||
const T_CONCRETE := preload(PICK_DIR + "sidewalk_pavers.png")
|
||||
const T_ASPHALT := preload(PICK_DIR + "asphalt.png")
|
||||
const T_AWNING := preload(PICK_DIR + "awning_canvas.png")
|
||||
## One skyline panorama per shift phase (realism pass): hazy daylight, warm dusk
|
||||
## with lit windows, and a night city. The old single strip was a black silhouette.
|
||||
const T_SKYLINE_DAY := preload(PICK_DIR + "skyline_day.png")
|
||||
const T_SKYLINE_DUSK := preload(PICK_DIR + "skyline_dusk.png")
|
||||
const T_SKYLINE_NIGHT := preload(PICK_DIR + "skyline_night.png")
|
||||
## Height of each backdrop plane (metres); the bases sit on the ground plane.
|
||||
const SKYLINE_HEIGHT := 90.0
|
||||
const SKYLINE_BY_PHASE := {
|
||||
Rules.Phase.EARLY: {"texture": T_SKYLINE_DAY, "tint": Color(0.98, 1.0, 1.04)},
|
||||
Rules.Phase.MIDDAY: {"texture": T_SKYLINE_DAY, "tint": Color(1.02, 1.04, 1.08)},
|
||||
Rules.Phase.EVENING: {"texture": T_SKYLINE_DUSK, "tint": Color(0.9, 0.92, 1.0)},
|
||||
Rules.Phase.LATE: {"texture": T_SKYLINE_NIGHT, "tint": Color(1.5, 1.55, 1.65)},
|
||||
}
|
||||
## steps, building facade, restricted door, sidewalk, pavement, and street edge.
|
||||
## Surfaces use the shared PBR profiles in `src/game/materials.gd`; street
|
||||
## furniture comes from the generated GLB props (tools/model_pipeline.py) with a
|
||||
@@ -98,9 +112,18 @@ func _build_building() -> void:
|
||||
var building := StaticBody3D.new()
|
||||
building.name = "Building"
|
||||
# Main facade (textured brick)
|
||||
_add_box(building, Vector3(40, 18, 4), Vector3(0, 9, -6), Color(0.80, 0.76, 0.74), false, T_BRICK, Vector2(22, 10), "brick")
|
||||
_add_box(building, Vector3(40, 18, 4), Vector3(0, 9, -6), Color(0.86, 0.85, 0.83), false, T_BRICK, Vector2(22, 10), "brick")
|
||||
# Stone base course so the facade has a footing instead of meeting the steps dead-on.
|
||||
_add_box(building, Vector3(40, 0.8, 0.35), Vector3(0, 0.4, -3.85), Color(0.58, 0.56, 0.53), false, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(40, 0.8, 0.5), Vector3(0, 0.4, -3.78), Color(0.52, 0.5, 0.47), false, null, Vector2.ONE, "stone")
|
||||
# Facade depth, not a flat wall: a projecting plinth, a string course between
|
||||
# the window bands, a cornice under the roofline and pilasters at the bays.
|
||||
_add_box(building, Vector3(40, 0.34, 0.22), Vector3(0, 0.93, -3.9), Color(0.6, 0.58, 0.55), true, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(40, 0.28, 0.28), Vector3(0, 7.6, -3.86), Color(0.6, 0.58, 0.55), true, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(40, 0.5, 0.5), Vector3(0, 17.7, -3.75), Color(0.56, 0.54, 0.51), true, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(40, 0.22, 0.2), Vector3(0, 10.9, -3.9), Color(0.58, 0.56, 0.53), true, null, Vector2.ONE, "stone")
|
||||
for bay in [-15.3, -11.9, -8.5, -5.1, 5.1, 8.5, 11.9, 15.3]:
|
||||
_add_box(building, Vector3(0.5, 8.4, 0.24), Vector3(bay, 12.6, -3.9),
|
||||
Color(0.8, 0.78, 0.76), true, T_BRICK, Vector2(0.3, 4.7), "brick")
|
||||
# Entrance recess + door recess frame
|
||||
_add_box(building, Vector3(5.4, 6.4, 0.2), Vector3(0, 3.2, -3.9), Color(0.08, 0.08, 0.09), false, null, Vector2.ONE, "stone")
|
||||
# Door frame columns and lintel, so the entrance is framed rather than painted on.
|
||||
@@ -151,9 +174,14 @@ func _build_facade_windows(parent: Node3D) -> void:
|
||||
var pane_mat := Materials.window_pane()
|
||||
pane.material_override = pane_mat
|
||||
_window_mats.append(pane_mat)
|
||||
# Sill below the frame.
|
||||
# Sill below the frame and glazing bars, so the pane reads as a window
|
||||
# rather than a dark hole.
|
||||
_add_box(parent, Vector3(2.1, 0.12, 0.3), Vector3(x, y - 1.3, -3.86),
|
||||
Color(0.55, 0.53, 0.50), false, null, Vector2.ONE, "stone")
|
||||
_add_box(parent, Vector3(0.06, 2.2, 0.05), Vector3(x, y, -3.9),
|
||||
Color(0.24, 0.23, 0.22), false, null, Vector2.ONE, "stone")
|
||||
_add_box(parent, Vector3(1.7, 0.06, 0.05), Vector3(x, y + 0.25, -3.9),
|
||||
Color(0.24, 0.23, 0.22), false, null, Vector2.ONE, "stone")
|
||||
|
||||
func _build_steps() -> void:
|
||||
# Steps have collision so NPCs and the guard can walk up the entrance.
|
||||
@@ -187,7 +215,7 @@ func _build_steps() -> void:
|
||||
func _build_sidewalk() -> void:
|
||||
var sidewalk := StaticBody3D.new()
|
||||
sidewalk.name = "Sidewalk"
|
||||
_add_box(sidewalk, Vector3(30, 0.18, 8), Vector3(0, 0.09, 8), Color(0.74, 0.74, 0.72), false, T_CONCRETE, Vector2(9, 2.4), "concrete")
|
||||
_add_box(sidewalk, Vector3(30, 0.18, 8), Vector3(0, 0.09, 8), Color(0.55, 0.55, 0.54), false, T_CONCRETE, Vector2(9, 2.4), "concrete")
|
||||
var col := BoxShape3D.new()
|
||||
col.size = Vector3(30, 0.36, 8)
|
||||
var so := sidewalk.create_shape_owner(sidewalk)
|
||||
@@ -200,7 +228,12 @@ func _build_street() -> void:
|
||||
street.name = "Street"
|
||||
# The roadway reads darker than the sidewalk; the raw asphalt texture tinted
|
||||
# white looked like more pavement.
|
||||
_add_box(street, Vector3(120, 0.1, 26), Vector3(0, 0.05, 22), Color(0.52, 0.53, 0.57), false, T_ASPHALT, Vector2(30, 7), "asphalt")
|
||||
# The street keeps its own material so night mode can switch to the wet
|
||||
# asphalt map (glossier, with lamp reflections) and back.
|
||||
_street_mat = Materials.asphalt(Color(0.62, 0.63, 0.66), Vector2(30, 7))
|
||||
var street_mesh := _add_box(street, Vector3(120, 0.1, 26), Vector3(0, 0.05, 22),
|
||||
Color(0.62, 0.63, 0.66), false, T_ASPHALT, Vector2(30, 7), "asphalt")
|
||||
street_mesh.material_override = _street_mat
|
||||
# Center dashes along the street (x varied so they form a lane line, not a stack).
|
||||
for i in range(-40, 41, 4):
|
||||
_add_box(street, Vector3(0.2, 0.02, 1.2), Vector3(float(i), 0.11, 22), Color(0.85, 0.85, 0.30), false, null, Vector2.ONE, "concrete")
|
||||
@@ -239,28 +272,32 @@ func _build_skyline_backdrop() -> void:
|
||||
## band from the sidewalk.
|
||||
var backdrop := Node3D.new()
|
||||
backdrop.name = "SkylineBackdrop"
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_texture = T_SKYLINE
|
||||
mat.texture_repeat = true
|
||||
mat.uv1_scale = Vector3(3.0, 1.0, 1.0)
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_SCISSOR
|
||||
mat.alpha_scissor_threshold = 0.5
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||
# Unshaded silhouettes read as pure black at 255-scale; lift the albedo so the
|
||||
# distant skyline sits closer to a hazy daytime city.
|
||||
mat.albedo_color = Color(2.6, 2.7, 3.0, 1.0)
|
||||
# A shader, not a StandardMaterial3D: the panorama already has its own
|
||||
# atmospheric haze baked in, and the scene fog at 200 m washed the towers out
|
||||
# to a white band (Godot 4.3 has no `fog_enabled` on StandardMaterial3D, so the
|
||||
# `fog_disabled` render mode does the job). Two UV repeats over the 400 m ring:
|
||||
# each repeat is ~200 x 90 m, close to the 2:1 panorama aspect, so the towers
|
||||
# keep their proportions instead of stretching into a wide band.
|
||||
var mat := ShaderMaterial.new()
|
||||
var shader := Shader.new()
|
||||
shader.code = SKYLINE_SHADER
|
||||
mat.shader = shader
|
||||
mat.set_shader_parameter("sky_texture", SKYLINE_BY_PHASE[_phase]["texture"])
|
||||
mat.set_shader_parameter("tint", SKYLINE_BY_PHASE[_phase]["tint"])
|
||||
mat.set_shader_parameter("repeats", 2.0)
|
||||
_skyline_mat = mat
|
||||
var mesh := PlaneMesh.new()
|
||||
# PlaneMesh defaults to a horizontal (XZ) plane; the skyline must stand up.
|
||||
mesh.orientation = PlaneMesh.FACE_Z
|
||||
mesh.size = Vector2(400, 60)
|
||||
# 90 m tall: at the 200 m ring distance the towers subtend a skyline band
|
||||
# instead of a thin strip on the horizon.
|
||||
mesh.size = Vector2(400, SKYLINE_HEIGHT)
|
||||
# Far enough that the skyline reads as background and its top edge stays out
|
||||
# of the middle of the sky.
|
||||
const DIST := 200.0
|
||||
# The texture keeps its buildings in the lower part of the image, so the plane
|
||||
# center sits at half its height: the building bases meet the ground.
|
||||
const CENTER_Y := 30.0
|
||||
const CENTER_Y := SKYLINE_HEIGHT / 2.0
|
||||
var placements := [
|
||||
[Vector3(0, CENTER_Y, -DIST), 0.0],
|
||||
[Vector3(0, CENTER_Y, DIST), 0.0],
|
||||
@@ -277,7 +314,27 @@ func _build_skyline_backdrop() -> void:
|
||||
backdrop.add_child(plane)
|
||||
root.add_child(backdrop)
|
||||
|
||||
var _skyline_mat: StandardMaterial3D = null
|
||||
## Backdrop shader: unshaded, alpha-scissored, and fog-free so the panorama's own
|
||||
## haze shows instead of the scene fog bleaching it.
|
||||
const SKYLINE_SHADER := """
|
||||
shader_type spatial;
|
||||
render_mode unshaded, cull_disabled, fog_disabled;
|
||||
|
||||
uniform sampler2D sky_texture : source_color, repeat_enable, filter_linear_mipmap;
|
||||
uniform vec4 tint : source_color = vec4(1.0);
|
||||
uniform float repeats = 2.0;
|
||||
|
||||
void fragment() {
|
||||
vec4 panorama = texture(sky_texture, vec2(UV.x * repeats, UV.y));
|
||||
ALBEDO = panorama.rgb * tint.rgb;
|
||||
ALPHA = panorama.a * tint.a;
|
||||
ALPHA_SCISSOR_THRESHOLD = 0.5;
|
||||
}
|
||||
"""
|
||||
|
||||
var _skyline_mat: ShaderMaterial = null
|
||||
var _street_mat: StandardMaterial3D = null
|
||||
var _phase: int = Rules.Phase.EARLY
|
||||
|
||||
func _build_marquee() -> void:
|
||||
## Blender canopy + sign box above the door; the procedural striped awning and
|
||||
@@ -712,6 +769,26 @@ func _build_crosswalk() -> void:
|
||||
_add_box(root, Vector3(0.8, 0.02, 0.5), Vector3(-2.8 + i * 0.85, 0.11, 17.0), Color(0.92, 0.92, 0.92))
|
||||
_add_box(root, Vector3(0.8, 0.02, 0.5), Vector3(-2.8 + i * 0.85, 0.11, 19.0), Color(0.92, 0.92, 0.92))
|
||||
|
||||
## Apply a shift phase: skyline panorama, dry/wet street, and night lighting.
|
||||
## Called by GameManager on every phase change.
|
||||
func set_phase(phase: int) -> void:
|
||||
_phase = phase
|
||||
var entry: Dictionary = SKYLINE_BY_PHASE.get(phase, SKYLINE_BY_PHASE[Rules.Phase.EARLY])
|
||||
if _skyline_mat != null:
|
||||
_skyline_mat.set_shader_parameter("sky_texture", entry["texture"])
|
||||
_skyline_mat.set_shader_parameter("tint", entry["tint"])
|
||||
var night: bool = phase == Rules.Phase.EVENING or phase == Rules.Phase.LATE
|
||||
set_night_mode(night)
|
||||
if _street_mat != null:
|
||||
var wet: bool = phase == Rules.Phase.LATE
|
||||
var dry: StandardMaterial3D = Materials.asphalt(Color(0.62, 0.63, 0.66), Vector2(30, 7))
|
||||
var damp: StandardMaterial3D = Materials.asphalt(Color(0.52, 0.55, 0.6), Vector2(30, 7), true)
|
||||
var target: StandardMaterial3D = damp if wet else dry
|
||||
_street_mat.albedo_texture = target.albedo_texture
|
||||
_street_mat.normal_texture = target.normal_texture
|
||||
_street_mat.roughness = target.roughness
|
||||
_street_mat.metallic_specular = target.metallic_specular
|
||||
|
||||
## Switch street lamps and signage to night mode (emissive + lights on).
|
||||
func set_night_mode(night: bool) -> void:
|
||||
for mat in _lamp_bulb_mats:
|
||||
|
||||
Reference in new issue
Block a user