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
Binary file not shown.
|
Before Width: | Height: | Size: 405 KiB After Width: | Height: | Size: 406 KiB |
@@ -270,6 +270,12 @@ de contacts existant les sépare du fond.
|
||||
semés sur la roche douce (< 30°) de 12 à 40 m en patch reefs par bruit, une
|
||||
`MultiMesh` par tuile et par variante, shader opaque `seabed_coral.gdshader`
|
||||
(couleur de sommet × teinte d'instance, marbrure, même colonne d'eau).
|
||||
- Particules en suspension (même jour) : `MarineSnow`, émetteur
|
||||
`GPUParticles3D` en espace monde recentré sur la caméra active, boîte de
|
||||
44 × 28 × 44 m, 1 800 flocons de 3 à 9 cm qui coulent à 3,5 cm/s avec
|
||||
turbulence, disques doux additifs en billboard, atténués par la colonne
|
||||
d'eau et fondus en début et fin de vie. Remplace le volume de sphères
|
||||
attaché au sous-marin (qui tournait avec lui).
|
||||
- Brouillard, lumière directionnelle et particules de la scène de test
|
||||
actuelle sont conservés et réglés par site.
|
||||
- Passe lumière du 2026-10-06 (`dive_site.tscn`) : l'ambiance vient d'un ciel
|
||||
|
||||
@@ -1,25 +1,10 @@
|
||||
[gd_scene load_steps=7 format=3]
|
||||
[gd_scene load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://game/world/sites/dive_site.tscn" id="1_site"]
|
||||
[ext_resource type="PackedScene" path="res://game/world/submarine/submarine_actor.tscn" id="2_submarine"]
|
||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/cockpit_camera_rig.tscn" id="3_camera"]
|
||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/cockpit_interior.tscn" id="4_cockpit"]
|
||||
|
||||
[sub_resource type="ParticleProcessMaterial" id="SuspendedMotion"]
|
||||
emission_shape = 3
|
||||
emission_box_extents = Vector3(45, 28, 70)
|
||||
direction = Vector3(0.1, 0.04, 0)
|
||||
spread = 180.0
|
||||
initial_velocity_min = 0.02
|
||||
initial_velocity_max = 0.09
|
||||
gravity = Vector3(0, 0, 0)
|
||||
scale_min = 0.025
|
||||
scale_max = 0.08
|
||||
|
||||
[sub_resource type="SphereMesh" id="SuspendedParticleMesh"]
|
||||
radius = 0.035
|
||||
height = 0.07
|
||||
|
||||
[node name="DiveWorld" type="Node3D"]
|
||||
|
||||
[node name="DiveSite" parent="." instance=ExtResource("1_site")]
|
||||
@@ -28,14 +13,6 @@ unique_name_in_owner = true
|
||||
[node name="SubmarineActor" parent="." instance=ExtResource("2_submarine")]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="SuspendedParticles" type="GPUParticles3D" parent="SubmarineActor"]
|
||||
amount = 360
|
||||
lifetime = 14.0
|
||||
preprocess = 14.0
|
||||
visibility_aabb = AABB(-50, -35, -78, 100, 70, 156)
|
||||
process_material = SubResource("SuspendedMotion")
|
||||
draw_pass_1 = SubResource("SuspendedParticleMesh")
|
||||
|
||||
[node name="CockpitInterior" parent="SubmarineActor" instance=ExtResource("4_cockpit")]
|
||||
unique_name_in_owner = true
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
[gd_scene load_steps=10 format=3]
|
||||
[gd_scene load_steps=11 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"]
|
||||
[ext_resource type="Script" path="res://game/world/sites/seabed_flora_actor.gd" id="4_flora"]
|
||||
[ext_resource type="Script" path="res://game/world/sites/marine_snow.gd" id="5_snow"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="WaterColumnMaterial"]
|
||||
shader = ExtResource("3_water_column")
|
||||
@@ -101,5 +102,8 @@ script = ExtResource("2_terrain")
|
||||
unique_name_in_owner = true
|
||||
script = ExtResource("4_flora")
|
||||
|
||||
[node name="MarineSnow" type="GPUParticles3D" parent="."]
|
||||
script = ExtResource("5_snow")
|
||||
|
||||
[node name="Placements" type="Node3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
@@ -0,0 +1,76 @@
|
||||
class_name MarineSnow
|
||||
extends GPUParticles3D
|
||||
|
||||
## Suspended particles (marine snow, sediment) around the viewer. The emitter is a
|
||||
## world-space box re-centred on the active camera every frame, so flakes keep their
|
||||
## own slow drift while the submarine moves through them instead of travelling with
|
||||
## it. Sinks a few centimetres per second with a little turbulence, as detritus does.
|
||||
|
||||
const BOX_EXTENTS_M := Vector3(16.0, 10.0, 16.0)
|
||||
const PARTICLE_COUNT := 2400
|
||||
const LIFETIME_S := 16.0
|
||||
const SINK_SPEED_M_S := 0.035
|
||||
const DRIFT_SPEED_M_S := 0.06
|
||||
const FLAKE_SIZE_M := Vector2(0.05, 0.16)
|
||||
|
||||
const SNOW_SHADER = preload("res://game/world/sites/marine_snow.gdshader")
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
local_coords = false
|
||||
amount = PARTICLE_COUNT
|
||||
lifetime = LIFETIME_S
|
||||
preprocess = LIFETIME_S
|
||||
fixed_fps = 30
|
||||
visibility_aabb = AABB(-BOX_EXTENTS_M * 1.5, BOX_EXTENTS_M * 3.0)
|
||||
process_material = _process_material()
|
||||
var quad := QuadMesh.new()
|
||||
quad.size = Vector2.ONE
|
||||
var material := ShaderMaterial.new()
|
||||
material.shader = SNOW_SHADER
|
||||
quad.material = material
|
||||
draw_pass_1 = quad
|
||||
emitting = true
|
||||
|
||||
|
||||
## A jump larger than the box (spawn, teleport, first camera) would leave the viewer in
|
||||
## empty water for a whole lifetime: the emitter restarts and preprocesses in place.
|
||||
func _process(_delta: float) -> void:
|
||||
var camera := get_viewport().get_camera_3d() if is_inside_tree() else null
|
||||
if camera == null:
|
||||
return
|
||||
var jumped := global_position.distance_to(camera.global_position) > BOX_EXTENTS_M.y
|
||||
global_position = camera.global_position
|
||||
if jumped:
|
||||
restart()
|
||||
|
||||
|
||||
func _process_material() -> ParticleProcessMaterial:
|
||||
var material := ParticleProcessMaterial.new()
|
||||
material.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX
|
||||
material.emission_box_extents = BOX_EXTENTS_M
|
||||
material.direction = Vector3(1.0, 0.0, 0.3)
|
||||
material.spread = 180.0
|
||||
material.initial_velocity_min = DRIFT_SPEED_M_S * 0.3
|
||||
material.initial_velocity_max = DRIFT_SPEED_M_S
|
||||
material.gravity = Vector3(0.0, -SINK_SPEED_M_S, 0.0)
|
||||
material.turbulence_enabled = true
|
||||
material.turbulence_noise_strength = 0.4
|
||||
material.turbulence_noise_scale = 6.0
|
||||
material.turbulence_noise_speed_random = 0.2
|
||||
material.turbulence_influence_min = 0.02
|
||||
material.turbulence_influence_max = 0.06
|
||||
material.scale_min = FLAKE_SIZE_M.x
|
||||
material.scale_max = FLAKE_SIZE_M.y
|
||||
# Fade in and out through alpha so flakes never pop at the box edge or at respawn.
|
||||
var alpha := Gradient.new()
|
||||
alpha.set_offset(0, 0.0)
|
||||
alpha.set_color(0, Color(1.0, 1.0, 1.0, 0.0))
|
||||
alpha.add_point(0.15, Color(1.0, 1.0, 1.0, 1.0))
|
||||
alpha.add_point(0.85, Color(1.0, 1.0, 1.0, 1.0))
|
||||
alpha.set_offset(alpha.get_point_count() - 1, 1.0)
|
||||
alpha.set_color(alpha.get_point_count() - 1, Color(1.0, 1.0, 1.0, 0.0))
|
||||
var ramp := GradientTexture1D.new()
|
||||
ramp.gradient = alpha
|
||||
material.color_ramp = ramp
|
||||
return material
|
||||
@@ -0,0 +1,36 @@
|
||||
shader_type spatial;
|
||||
render_mode blend_add, depth_draw_never, cull_disabled, unshaded;
|
||||
|
||||
// Marine snow: camera-facing soft discs drawn by GPUParticles3D. Each particle is a
|
||||
// unit quad; the flake is a radial falloff in UV. Brightness follows the daylight of
|
||||
// the water column (so flakes dim with depth) and fades in and out over the life of
|
||||
// the particle through COLOR.a set by the process material.
|
||||
|
||||
#include "res://game/world/sites/water_light.gdshaderinc"
|
||||
|
||||
uniform vec3 flake_color : source_color = vec3(0.78, 0.86, 0.9);
|
||||
uniform float flake_strength = 1.4;
|
||||
uniform vec3 water_absorption_per_m = vec3(0.11, 0.04, 0.032);
|
||||
uniform float reference_depth_m = 24.0;
|
||||
uniform float minimum_daylight = 0.08;
|
||||
|
||||
varying float brightness;
|
||||
|
||||
void vertex() {
|
||||
// Billboard: keep the particle's world position, face the camera.
|
||||
vec3 world_origin = (MODEL_MATRIX * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
|
||||
float size = length(MODEL_MATRIX[0].xyz);
|
||||
mat3 view_basis = mat3(INV_VIEW_MATRIX);
|
||||
vec3 world = world_origin + (view_basis[0] * VERTEX.x + view_basis[1] * VERTEX.y) * size;
|
||||
VERTEX = (VIEW_MATRIX * vec4(world, 1.0)).xyz;
|
||||
NORMAL = (VIEW_MATRIX * vec4(view_basis[2], 0.0)).xyz;
|
||||
vec3 transmittance = water_transmittance(-world_origin.y, water_absorption_per_m, reference_depth_m);
|
||||
brightness = water_daylight(transmittance, minimum_daylight) * COLOR.a;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
float radial = length(UV - vec2(0.5)) * 2.0;
|
||||
float disc = 1.0 - smoothstep(0.35, 1.0, radial);
|
||||
ALBEDO = flake_color * disc * brightness * flake_strength;
|
||||
ALPHA = disc;
|
||||
}
|
||||
@@ -20,7 +20,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 := 51
|
||||
const TEST_COUNT := 52
|
||||
|
||||
var _failures: Array[String] = []
|
||||
|
||||
@@ -210,6 +210,8 @@ func _test_dive_site_scene() -> void:
|
||||
var first_pass := flora.sample_transforms(SeabedFloraActor.Kind.SEAGRASS)
|
||||
flora.configure(field_for_flora, hash(String(definition.site_id)))
|
||||
_expect(first_pass == flora.sample_transforms(SeabedFloraActor.Kind.SEAGRASS), "flora placement is deterministic for a seed")
|
||||
var snow := site.get_node_or_null("MarineSnow") as GPUParticles3D
|
||||
_expect(snow != null and snow.emitting and snow.amount > 0 and not snow.local_coords, "site carries world-space marine snow around the viewer")
|
||||
var water_column := site.get_node_or_null("WaterColumn") as FogVolume
|
||||
_expect(
|
||||
water_column != null and water_column.material is ShaderMaterial
|
||||
|
||||
Reference in new issue
Block a user