Terrain des sites de plongée : champ de hauteur authoré, Banc du Vigilant, formes variées et rendu sous-marin #4

Open
olix21 wants to merge 47 commits from olix21/worktree-terrain-site-zone1 AGit into main
pull from: olix21/worktree-terrain-site-zone1
11 changed files with 82 additions and 15 deletions
Showing only changes of commit d7e50f8879 - Show all commits

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

After

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 KiB

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 KiB

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 KiB

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 KiB

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

After

Width:  |  Height:  |  Size: 250 KiB

@@ -265,6 +265,14 @@ de contacts existant les sépare du fond.
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.
- Brouillard par profondeur : un `FogVolume` (`WaterColumn`, 1,8 km × 260 m)
porte un shader `fog` (`water_column_fog.gdshader`) dont l'albédo suit la même
transmittance spectrale que le fond et dont la densité croît lentement sous
24 m ; la densité volumétrique globale passe à 0, la portée volumétrique à
128 m, le brouillard de distance (couleur fixe, 0,0045) ne couvre que
l'au-delà. Tuilage du sable cassé par une déformation de domaine lente
(37 m), une seconde échelle de rides (11,3 m) et une extinction des rides
entre 40 et 60 m de fond.
**É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
+14 -4
View File
@@ -1,7 +1,11 @@
[gd_scene load_steps=6 format=3]
[gd_scene load_steps=9 format=3]
[ext_resource type="Script" path="res://game/world/sites/dive_site.gd" id="1_site"]
[ext_resource type="Script" path="res://game/world/sites/bathymetric_terrain_actor.gd" id="2_terrain"]
[ext_resource type="Shader" path="res://game/world/sites/water_column_fog.gdshader" id="3_water_column"]
[sub_resource type="ShaderMaterial" id="WaterColumnMaterial"]
shader = ExtResource("3_water_column")
[sub_resource type="ProceduralSkyMaterial" id="SkyUnderwater"]
sky_top_color = Color(0.22, 0.52, 0.66, 1)
@@ -51,15 +55,16 @@ fog_enabled = true
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_density = 0.0045
fog_aerial_perspective = 0.5
fog_height = -4.0
fog_height_density = 0.025
volumetric_fog_enabled = true
volumetric_fog_density = 0.01
volumetric_fog_density = 0.0
volumetric_fog_albedo = Color(0.5, 0.78, 0.92, 1)
volumetric_fog_anisotropy = 0.45
volumetric_fog_length = 72.0
volumetric_fog_length = 128.0
volumetric_fog_detail_spread = 1.6
volumetric_fog_ambient_inject = 0.5
adjustment_enabled = true
adjustment_contrast = 1.06
@@ -82,6 +87,11 @@ shadow_blur = 2.0
directional_shadow_max_distance = 160.0
directional_shadow_fade_start = 0.75
[node name="WaterColumn" type="FogVolume" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -110, 0)
size = Vector3(1800, 260, 1800)
material = SubResource("WaterColumnMaterial")
[node name="Terrain" type="StaticBody3D" parent="."]
unique_name_in_owner = true
script = ExtResource("2_terrain")
+28 -10
View File
@@ -29,6 +29,14 @@ uniform sampler2D sand_normal_map : hint_normal, repeat_enable, filter_linear_mi
uniform sampler2D rock_normal_map : hint_normal, repeat_enable, filter_linear_mipmap;
uniform sampler2D rock_grain_map : hint_default_white, repeat_enable, filter_linear_mipmap;
uniform float sand_tile_m = 3.2;
// Second ripple scale (megaripples) and the slow domain warp that hide the tiling.
uniform float sand_macro_tile_m = 11.3;
uniform float sand_macro_strength = 0.45;
uniform float sand_warp_tile_m = 37.0;
uniform float sand_warp_m = 1.4;
// Swell-built ripples fade out with depth as the wave energy at the bed dies.
uniform float ripple_fade_start_m = 40.0;
uniform float ripple_fade_end_m = 60.0;
uniform float rock_tile_m = 7.0;
uniform float rock_grain_tile_m = 1.7;
uniform float sand_normal_strength = 1.0;
@@ -81,17 +89,27 @@ void fragment() {
float up = clamp(n.y, 0.0, 1.0);
float rockness = 1.0 - smoothstep(rock_slope_cosine, sand_slope_cosine, up);
// Sand: ripple normal map rotated to the swell direction, on the xz plane.
// Sand: ripple normal map rotated to the swell direction, on the xz plane. The
// plane is bent by a slow warp (two decorrelated reads of the seamless grain map)
// and a second, larger ripple scale is blended in, so no period repeats across
// the field. Ripples fade with depth.
vec2 ripple_axis = normalize(ripple_direction);
vec2 sand_uv = vec2(
dot(world_position.xz, ripple_axis),
dot(world_position.xz, vec2(-ripple_axis.y, ripple_axis.x))
) / sand_tile_m;
vec3 sand_tangent = unpack_normal(texture(sand_normal_map, sand_uv));
vec3 sand_perturbation = (
vec3(ripple_axis.x, 0.0, ripple_axis.y) * sand_tangent.x
+ vec3(-ripple_axis.y, 0.0, ripple_axis.x) * sand_tangent.y
) * sand_normal_strength;
vec2 warp = vec2(
texture(rock_grain_map, world_position.xz / sand_warp_tile_m).r,
texture(rock_grain_map, world_position.xz / sand_warp_tile_m + vec2(0.37, 0.61)).r
) - 0.5;
vec2 bent_xz = world_position.xz + warp * sand_warp_m;
vec2 ripple_frame = vec2(
dot(bent_xz, ripple_axis),
dot(bent_xz, vec2(-ripple_axis.y, ripple_axis.x))
);
vec3 sand_tangent = unpack_normal(texture(sand_normal_map, ripple_frame / sand_tile_m));
vec3 macro_tangent = unpack_normal(texture(sand_normal_map, ripple_frame / sand_macro_tile_m + vec2(0.5, 0.25)));
float depth_for_ripples = -world_position.y;
float ripple_strength = sand_normal_strength * (1.0 - smoothstep(ripple_fade_start_m, ripple_fade_end_m, depth_for_ripples));
vec2 tangent_xy = sand_tangent.xy * ripple_strength + macro_tangent.xy * sand_macro_strength * ripple_strength;
vec3 sand_perturbation = vec3(ripple_axis.x, 0.0, ripple_axis.y) * tangent_xy.x
+ vec3(-ripple_axis.y, 0.0, ripple_axis.x) * tangent_xy.y;
vec3 sand_normal = normalize(n + sand_perturbation);
// Rock: coarse triplanar normal map plus fine grain, both in world space.
@@ -0,0 +1,25 @@
shader_type fog;
// Water column for the volumetric fog: the scattering albedo follows the same
// per-channel Beer-Lambert transmittance as the seabed shader, so the haze turns
// from green-blue near the plateau to indigo at the drop-off foot, and the density
// grows slowly with depth (more suspended sediment, less light). It covers every
// object in the volume, not only the terrain.
uniform vec3 shallow_albedo : source_color = vec3(0.5, 0.78, 0.92);
uniform vec3 water_absorption_per_m = vec3(0.11, 0.04, 0.032);
uniform float reference_depth_m = 24.0;
uniform float minimum_albedo = 0.03;
uniform float base_density = 0.012;
uniform float density_per_m = 0.00012;
uniform float emission_strength = 0.0;
void fog() {
// Above the reference depth the haze keeps its shallow colour: brightening it
// further would wash the upper water column towards white.
float excess_depth_m = max(-WORLD_POSITION.y - reference_depth_m, 0.0);
vec3 transmittance = exp(-water_absorption_per_m * excess_depth_m);
ALBEDO = clamp(shallow_albedo * transmittance, vec3(minimum_albedo), vec3(1.0));
DENSITY = base_density + density_per_m * excess_depth_m;
EMISSION = vec3(emission_strength);
}
@@ -19,7 +19,7 @@ const DIVE_SITE_SCENE = preload("res://game/world/sites/dive_site.tscn")
const ZONE1_SITE_PATH := "res://content/sites/zone1_vigilant.tres"
const SHOWCASE_SITE_PATH := "res://content/sites/terrain_showcase.tres"
const GROOVE_PROBE_OFFSET_M := 25.0
const TEST_COUNT := 40
const TEST_COUNT := 41
var _failures: Array[String] = []
@@ -179,6 +179,12 @@ func _test_dive_site_scene() -> void:
await physics_frame
await physics_frame
_expect(site.terrain().tile_count() > 0, "site builds its terrain tiles")
var water_column := site.get_node_or_null("WaterColumn") as FogVolume
_expect(
water_column != null and water_column.material is ShaderMaterial
and (water_column.material as ShaderMaterial).shader != null,
"site carries a water-column fog volume with its depth shader"
)
_expect(site.placeholder_count() == 2, "calibration target and beacon are placed")
var target := site.get_node_or_null("%Placements/calibration_target") as StaticBody3D
_expect(target != null and int(target.get_meta("sonar_material")) == SonarMaterial.Value.METAL, "wreck placeholder is a metal sonar body")