feat(world): depth-attenuated daylight and cold-water palette for the seabed

The seabed shader applies per-channel Beer-Lambert transmittance relative
to a 24 m reference depth: the scalar part dims the directional light in a
custom light() function and the sky ambient through AO, the chroma part
tints the albedo, and artificial lights are left untouched for the
submarine's projectors. Sky, fog and sun are cooled and the sun softened
so the plateau reads green-blue, the canyon floor cold blue and the
drop-off foot near-indigo.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
OlivierandClaude Fable 5.1 committed 2026-10-06 23:21:14 +02:00
1 parent 26a7dfc4f7
commit 34b39a8dbc
9 files changed
+58 -23

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 KiB

After

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 599 KiB

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 610 KiB

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 507 KiB

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 KiB

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

After

Width:  |  Height:  |  Size: 261 KiB

@@ -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
+11 -11
View File
@@ -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
+39 -12
View File
@@ -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;
}