diff --git a/docs/briefs/captures/terrain_showcase_boulders.png b/docs/briefs/captures/terrain_showcase_boulders.png index fbce7e1..f1e0c8f 100644 Binary files a/docs/briefs/captures/terrain_showcase_boulders.png and b/docs/briefs/captures/terrain_showcase_boulders.png differ diff --git a/docs/briefs/captures/terrain_showcase_canyon.png b/docs/briefs/captures/terrain_showcase_canyon.png index 7084aa1..bd44890 100644 Binary files a/docs/briefs/captures/terrain_showcase_canyon.png and b/docs/briefs/captures/terrain_showcase_canyon.png differ diff --git a/docs/briefs/captures/terrain_showcase_crevasse.png b/docs/briefs/captures/terrain_showcase_crevasse.png index 1e11703..837d41e 100644 Binary files a/docs/briefs/captures/terrain_showcase_crevasse.png and b/docs/briefs/captures/terrain_showcase_crevasse.png differ diff --git a/docs/briefs/captures/terrain_showcase_spurs.png b/docs/briefs/captures/terrain_showcase_spurs.png index b5c91cc..6046c89 100644 Binary files a/docs/briefs/captures/terrain_showcase_spurs.png and b/docs/briefs/captures/terrain_showcase_spurs.png differ diff --git a/docs/briefs/captures/zone1_vigilant_plateau.png b/docs/briefs/captures/zone1_vigilant_plateau.png index f44e15c..c5203af 100644 Binary files a/docs/briefs/captures/zone1_vigilant_plateau.png and b/docs/briefs/captures/zone1_vigilant_plateau.png differ diff --git a/docs/briefs/captures/zone1_vigilant_tombant.png b/docs/briefs/captures/zone1_vigilant_tombant.png index e3e9847..17a7e82 100644 Binary files a/docs/briefs/captures/zone1_vigilant_tombant.png and b/docs/briefs/captures/zone1_vigilant_tombant.png differ diff --git a/docs/briefs/terrain-site-zone1-gameplay.md b/docs/briefs/terrain-site-zone1-gameplay.md index 038e1be..6f8d9e2 100644 --- a/docs/briefs/terrain-site-zone1-gameplay.md +++ b/docs/briefs/terrain-site-zone1-gameplay.md @@ -257,6 +257,14 @@ de contacts existant les sépare du fond. plus haute et plus forte (62°, 1,35) avec ombres douces à 70 % d'opacité, SSAO pour l'occlusion à l'échelle du maillage, tonemap filmique, perspective aérienne et albédo du brouillard volumétrique teintés. +- Lumière par profondeur (même jour) : le shader atténue la lumière du jour + par Beer-Lambert spectral (absorption par canal 0,11 / 0,04 / 0,032 par + mètre, normalisée à 24 m) ; la partie scalaire réduit le soleil (fonction + `light()`, directionnelle seulement) et l'ambiance ciel (`AO`), la partie + chromatique teinte l'albédo. Les projecteurs du sous-marin ne sont pas + atténués. Plancher à 5 % pour que le pied du tombant reste lisible. Palette + GDP §18.2 obtenue de fait : vert-bleu à 24 m, bleu froid à 45 m, indigo au + pied du tombant ; ciel, brouillard et soleil (0,9, ombres 45 %) refroidis. **État du 2026-10-06 (demande d'Olivier)** : le shader mélange sable et roche par la pente à partir de 20° (sable sous 20°, roche au-dessus de 40°, fondu diff --git a/game/world/sites/dive_site.tscn b/game/world/sites/dive_site.tscn index c62f654..6c1f647 100644 --- a/game/world/sites/dive_site.tscn +++ b/game/world/sites/dive_site.tscn @@ -4,12 +4,12 @@ [ext_resource type="Script" path="res://game/world/sites/bathymetric_terrain_actor.gd" id="2_terrain"] [sub_resource type="ProceduralSkyMaterial" id="SkyUnderwater"] -sky_top_color = Color(0.3, 0.62, 0.64, 1) -sky_horizon_color = Color(0.13, 0.36, 0.41, 1) +sky_top_color = Color(0.22, 0.52, 0.66, 1) +sky_horizon_color = Color(0.09, 0.28, 0.4, 1) sky_curve = 0.2 sky_energy_multiplier = 1.0 -ground_horizon_color = Color(0.11, 0.3, 0.35, 1) -ground_bottom_color = Color(0.015, 0.05, 0.07, 1) +ground_horizon_color = Color(0.05, 0.17, 0.27, 1) +ground_bottom_color = Color(0.01, 0.03, 0.07, 1) ground_curve = 0.1 ground_energy_multiplier = 0.6 sun_angle_max = 40.0 @@ -26,7 +26,7 @@ background_mode = 2 sky = SubResource("SkyResource") ambient_light_source = 3 ambient_light_sky_contribution = 1.0 -ambient_light_energy = 1.0 +ambient_light_energy = 1.3 reflected_light_source = 1 tonemap_mode = 2 tonemap_exposure = 1.15 @@ -48,8 +48,8 @@ glow_intensity = 0.8 glow_blend_mode = 0 glow_hdr_threshold = 1.2 fog_enabled = true -fog_light_color = Color(0.1, 0.3, 0.35, 1) -fog_light_energy = 0.52 +fog_light_color = Color(0.05, 0.18, 0.29, 1) +fog_light_energy = 0.38 fog_sun_scatter = 0.15 fog_density = 0.006 fog_aerial_perspective = 0.5 @@ -57,7 +57,7 @@ fog_height = -4.0 fog_height_density = 0.025 volumetric_fog_enabled = true volumetric_fog_density = 0.01 -volumetric_fog_albedo = Color(0.6, 0.85, 0.9, 1) +volumetric_fog_albedo = Color(0.5, 0.78, 0.92, 1) volumetric_fog_anisotropy = 0.45 volumetric_fog_length = 72.0 volumetric_fog_ambient_inject = 0.5 @@ -73,11 +73,11 @@ environment = SubResource("EnvironmentUnderwater") [node name="AmbientDirection" type="DirectionalLight3D" parent="."] rotation_degrees = Vector3(-62, -28, 0) -light_color = Color(0.56, 0.8, 0.82, 1) -light_energy = 1.35 +light_color = Color(0.5, 0.78, 0.88, 1) +light_energy = 0.9 light_angular_distance = 3.0 shadow_enabled = true -shadow_opacity = 0.7 +shadow_opacity = 0.45 shadow_blur = 2.0 directional_shadow_max_distance = 160.0 directional_shadow_fade_start = 0.75 diff --git a/game/world/sites/seabed_terrain.gdshader b/game/world/sites/seabed_terrain.gdshader index 24de846..ff2206b 100644 --- a/game/world/sites/seabed_terrain.gdshader +++ b/game/world/sites/seabed_terrain.gdshader @@ -7,14 +7,23 @@ render_mode depth_draw_opaque; // The mesh has no tangents: perturbed normals are built in world space and written // to NORMAL in view space. Detail shading comes from the normal maps, a grain map // used as ambient occlusion in the pits, and albedo mottling. +// +// Daylight is attenuated spectrally with depth (Beer-Lambert, per channel): red is +// gone first, blue last, and the whole scene darkens by exp(-k * depth). The scalar +// part scales the sun (light() function, directional only) and the sky ambient (AO); +// the chroma part tints the albedo. Artificial lights such as the submarine's +// projectors are not attenuated, so they stay useful on the drop-off. Everything is +// normalised at reference_depth_m, where the look is the authored palette. -uniform vec3 sand_color : source_color = vec3(0.50, 0.46, 0.34); -uniform vec3 rock_color : source_color = vec3(0.21, 0.20, 0.18); -uniform vec3 deep_tint : source_color = vec3(0.04, 0.09, 0.15); +uniform vec3 sand_color : source_color = vec3(0.56, 0.52, 0.42); +uniform vec3 rock_color : source_color = vec3(0.22, 0.21, 0.19); uniform float sand_slope_cosine = 0.9397; // cos(20 deg) uniform float rock_slope_cosine = 0.7660; // cos(40 deg) -uniform float depth_tint_start_m = 20.0; -uniform float depth_tint_end_m = 90.0; +// Per-channel absorption of clear tropical water, per metre, scaled for readability. +uniform vec3 water_absorption_per_m = vec3(0.11, 0.04, 0.032); +uniform float reference_depth_m = 24.0; +// Floor on the daylight factor so the drop-off foot stays readable rather than black. +uniform float minimum_daylight = 0.05; uniform sampler2D sand_normal_map : hint_normal, repeat_enable, filter_linear_mipmap; uniform sampler2D rock_normal_map : hint_normal, repeat_enable, filter_linear_mipmap; @@ -31,6 +40,7 @@ uniform vec2 ripple_direction = vec2(0.8, 0.6); varying vec3 world_position; varying vec3 world_normal; +varying float daylight; void vertex() { world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; @@ -92,20 +102,37 @@ void fragment() { vec3 shading_normal = normalize(mix(sand_normal, rock_normal, rockness)); NORMAL = normalize((VIEW_MATRIX * vec4(shading_normal, 0.0)).xyz); - // Albedo: mottled rock, lightly grained sand, both tinted with depth. + // Albedo: mottled rock, lightly grained sand. vec3 rock_albedo = rock_color * (1.0 + (grain - 0.5) * 2.0 * grain_albedo_contrast); vec3 sand_albedo = sand_color * (1.0 + (grain - 0.5) * 0.3); vec3 albedo = mix(sand_albedo, rock_albedo, rockness); - float depth_m = -world_position.y; - float tint = smoothstep(depth_tint_start_m, depth_tint_end_m, depth_m); - albedo = mix(albedo, deep_tint, tint * 0.6); - ALBEDO = albedo; - // Detail shading: pits of the grain map and ripple troughs read as occlusion. + // Water column: spectral transmittance relative to the reference depth. + float depth_m = -world_position.y; + vec3 transmittance = exp(-water_absorption_per_m * (depth_m - reference_depth_m)); + float brightness = dot(transmittance, vec3(0.2126, 0.7152, 0.0722)); + vec3 chroma = transmittance / max(brightness, 0.001); + daylight = max(brightness, minimum_daylight); + // Normalise so the authored palette holds at the reference depth once the + // reference transmittance (red already filtered by 24 m of water) is applied. + vec3 reference_chroma = exp(-water_absorption_per_m * reference_depth_m); + reference_chroma /= dot(reference_chroma, vec3(0.2126, 0.7152, 0.0722)); + ALBEDO = albedo * chroma * reference_chroma; + + // Detail shading: pits of the grain map and ripple troughs read as occlusion; + // the daylight factor dims the sky ambient with depth. float rock_ao = mix(1.0, 0.45 + 0.55 * grain, grain_ao_strength); float sand_ao = mix(1.0, 0.8 + 0.2 * clamp(sand_tangent.z, 0.0, 1.0), grain_ao_strength * 0.6); - AO = mix(sand_ao, rock_ao, rockness); + AO = mix(sand_ao, rock_ao, rockness) * daylight; AO_LIGHT_AFFECT = 0.35; ROUGHNESS = mix(0.95, 0.78 + 0.12 * grain, rockness); METALLIC = 0.0; } + +// Lambert diffuse; the sun (directional) is dimmed by the water column, other lights +// (submarine projectors) are not. Sand and rock are rough enough to skip specular. +void light() { + float sun_factor = LIGHT_IS_DIRECTIONAL ? daylight : 1.0; + float lambert = clamp(dot(NORMAL, LIGHT), 0.0, 1.0); + DIFFUSE_LIGHT += lambert * ATTENUATION * LIGHT_COLOR * ALBEDO * sun_factor / PI; +}