Quality Gate / Quality Gate (pull_request) Failing after 2m58s
SeabedFloraActor scatters seagrass tufts on flat sand in the lit band and sea fans on rock between 15 and 60 m, deterministically from the site id, one MultiMesh per tile and kind, with procedural alpha-cut meshes and textures (SeabedFloraAssets), vertex sway and wrapped lighting, no collision and no sonar presence. Placement reads the field arrays directly and runs on the worker thread pool. The seabed shader grows turf algae on sunlit rock, coralline crusts in patches and encrusting sponges in the shade, fading out between 40 and 75 m. The water-column optics move to a shared shader include. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
179 lines
9.0 KiB
Plaintext
179 lines
9.0 KiB
Plaintext
shader_type spatial;
|
|
render_mode depth_draw_opaque;
|
|
|
|
// Sand / rock split by slope, matching the sonar material rule (sand under 20 deg,
|
|
// rock over 40 deg). Rock is normal-mapped triplanar in world space so cliffs and
|
|
// ledges keep their detail; sand carries an oriented ripple normal map on the xz plane.
|
|
// 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.
|
|
|
|
#include "res://game/world/sites/water_light.gdshaderinc"
|
|
|
|
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);
|
|
// Encrusting growth on rock: turf algae on sunlit faces, coralline crusts in patches,
|
|
// encrusting sponges in the shade. Patches come from the grain map read at large
|
|
// scales; all of it thins out with depth as the light goes.
|
|
uniform vec3 turf_color : source_color = vec3(0.3, 0.38, 0.2);
|
|
uniform vec3 coralline_color : source_color = vec3(0.6, 0.4, 0.46);
|
|
uniform vec3 sponge_color : source_color = vec3(0.82, 0.4, 0.18);
|
|
uniform float growth_tile_m = 4.2;
|
|
uniform float growth_strength = 1.0;
|
|
uniform float growth_fade_start_m = 40.0;
|
|
uniform float growth_fade_end_m = 75.0;
|
|
uniform float sand_slope_cosine = 0.9397; // cos(20 deg)
|
|
uniform float rock_slope_cosine = 0.7660; // cos(40 deg)
|
|
// 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;
|
|
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;
|
|
uniform float rock_normal_strength = 1.1;
|
|
uniform float rock_grain_normal_strength = 0.45;
|
|
uniform float grain_ao_strength = 0.5;
|
|
uniform float grain_albedo_contrast = 0.45;
|
|
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;
|
|
world_normal = normalize((MODEL_MATRIX * vec4(NORMAL, 0.0)).xyz);
|
|
}
|
|
|
|
vec3 unpack_normal(vec4 sample) {
|
|
return sample.xyz * 2.0 - 1.0;
|
|
}
|
|
|
|
// Triplanar normal perturbation (UDN blend): each plane's tangent-space xy is added
|
|
// onto the world normal along that plane's axes, weighted by how much the surface
|
|
// faces the plane.
|
|
vec3 triplanar_normal(sampler2D map, vec3 position, vec3 normal, float strength) {
|
|
vec3 weights = pow(abs(normal), vec3(4.0));
|
|
weights /= weights.x + weights.y + weights.z;
|
|
vec3 tx = unpack_normal(texture(map, position.zy));
|
|
vec3 ty = unpack_normal(texture(map, position.xz));
|
|
vec3 tz = unpack_normal(texture(map, position.xy));
|
|
vec3 perturbation = (
|
|
vec3(0.0, tx.y, tx.x) * weights.x
|
|
+ vec3(ty.x, 0.0, ty.y) * weights.y
|
|
+ vec3(tz.x, tz.y, 0.0) * weights.z
|
|
) * strength;
|
|
return normalize(normal + perturbation);
|
|
}
|
|
|
|
float triplanar_grain(sampler2D map, vec3 position, vec3 normal) {
|
|
vec3 weights = pow(abs(normal), vec3(4.0));
|
|
weights /= weights.x + weights.y + weights.z;
|
|
return texture(map, position.zy).r * weights.x
|
|
+ texture(map, position.xz).r * weights.y
|
|
+ texture(map, position.xy).r * weights.z;
|
|
}
|
|
|
|
void fragment() {
|
|
vec3 n = normalize(world_normal);
|
|
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. 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 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.
|
|
vec3 rock_normal = triplanar_normal(rock_normal_map, world_position / rock_tile_m, n, rock_normal_strength);
|
|
rock_normal = triplanar_normal(rock_normal_map, world_position / rock_grain_tile_m, rock_normal, rock_grain_normal_strength);
|
|
float grain = triplanar_grain(rock_grain_map, world_position / rock_grain_tile_m, n);
|
|
|
|
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.
|
|
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);
|
|
float depth_m = -world_position.y;
|
|
|
|
// Encrusting growth on the rock.
|
|
float growth_light = (1.0 - smoothstep(growth_fade_start_m, growth_fade_end_m, depth_m)) * growth_strength;
|
|
float sunlit = clamp(n.y * 0.5 + 0.5, 0.0, 1.0);
|
|
float patch_a = triplanar_grain(rock_grain_map, world_position / growth_tile_m + vec3(0.21, 0.73, 0.37), n);
|
|
float patch_b = triplanar_grain(rock_grain_map, world_position / (growth_tile_m * 0.53) + vec3(0.61, 0.11, 0.9), n);
|
|
float turf_mask = smoothstep(0.4, 0.58, patch_a) * sunlit * growth_light;
|
|
float coralline_mask = smoothstep(0.6, 0.74, patch_b) * growth_light;
|
|
float sponge_mask = smoothstep(0.62, 0.74, patch_a * 0.5 + patch_b * 0.5) * (1.0 - 0.6 * sunlit) * growth_light;
|
|
float grain_mottle = 1.0 + (grain - 0.5) * 0.6;
|
|
rock_albedo = mix(rock_albedo, turf_color * grain_mottle, turf_mask);
|
|
rock_albedo = mix(rock_albedo, coralline_color * grain_mottle, coralline_mask);
|
|
rock_albedo = mix(rock_albedo, sponge_color * grain_mottle, sponge_mask);
|
|
float rock_roughness = 0.78 + 0.12 * grain;
|
|
rock_roughness = mix(rock_roughness, 0.6, coralline_mask);
|
|
rock_roughness = mix(rock_roughness, 0.9, sponge_mask);
|
|
vec3 albedo = mix(sand_albedo, rock_albedo, rockness);
|
|
|
|
// Water column: spectral transmittance relative to the reference depth.
|
|
vec3 transmittance = water_transmittance(depth_m, water_absorption_per_m, reference_depth_m);
|
|
daylight = water_daylight(transmittance, minimum_daylight);
|
|
ALBEDO = albedo * water_chroma(transmittance, water_absorption_per_m, reference_depth_m);
|
|
|
|
// 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) * daylight;
|
|
AO_LIGHT_AFFECT = 0.35;
|
|
ROUGHNESS = mix(0.95, rock_roughness, 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 = water_sun_factor(LIGHT_IS_DIRECTIONAL, daylight);
|
|
float lambert = clamp(dot(NORMAL, LIGHT), 0.0, 1.0);
|
|
DIFFUSE_LIGHT += lambert * ATTENUATION * LIGHT_COLOR * ALBEDO * sun_factor / PI;
|
|
}
|