Terrain des sites de plongée : champ de hauteur authoré, Banc du Vigilant, formes variées et rendu sous-marin #4
No files matched your search
|
Before Width: | Height: | Size: 456 KiB After Width: | Height: | Size: 458 KiB |
|
Before Width: | Height: | Size: 321 KiB After Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 420 KiB After Width: | Height: | Size: 412 KiB |
|
Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 419 KiB |
|
Before Width: | Height: | Size: 410 KiB After Width: | Height: | Size: 416 KiB |
|
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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||