Add freshwater habitats and saltwater tide ribbons

This commit is contained in:
Alexander Sellite 2026-07-31 16:05:44 -04:00
parent 53fdc9ef21
commit 69b7969e25
40 changed files with 1323 additions and 148 deletions

View file

@ -0,0 +1,71 @@
shader_type spatial;
render_mode blend_mix, depth_draw_never, cull_disabled, unshaded;
uniform vec4 lead_color : source_color = vec4(0.620, 0.875, 0.855, 1.0);
uniform vec4 trail_color : source_color = vec4(0.430, 0.780, 0.770, 1.0);
uniform float ribbon_width : hint_range(0.2, 2.0, 0.01) = 0.85;
uniform float land_inset : hint_range(0.0, 0.4, 0.01) = 0.10;
uniform float tide_speed : hint_range(0.1, 3.0, 0.01) = 1.42;
uniform float tide_distance : hint_range(0.0, 0.6, 0.01) = 0.30;
uniform float lead_width : hint_range(0.01, 0.2, 0.005) = 0.065;
uniform float trail_width : hint_range(0.02, 0.4, 0.005) = 0.18;
uniform float lead_strength : hint_range(0.0, 1.0, 0.01) = 0.48;
uniform float trail_strength : hint_range(0.0, 1.0, 0.01) = 0.25;
uniform float phase_scale : hint_range(0.001, 0.2, 0.001) = 0.035;
uniform float phase_strength : hint_range(0.0, 2.0, 0.01) = 0.55;
varying vec3 world_position;
float hash_21(vec2 point) {
return fract(sin(dot(point, vec2(127.1, 311.7))) * 43758.5453);
}
float value_noise(vec2 point) {
vec2 cell = floor(point);
vec2 local = fract(point);
vec2 blend = local * local * (3.0 - 2.0 * local);
float bottom = mix(hash_21(cell), hash_21(cell + vec2(1.0, 0.0)), blend.x);
float top = mix(
hash_21(cell + vec2(0.0, 1.0)),
hash_21(cell + vec2(1.0, 1.0)),
blend.x
);
return mix(bottom, top, blend.y);
}
float soft_line(float coordinate, float center, float width) {
float normalized_width = max(width / ribbon_width, 0.001);
float derivative_width = min(fwidth(coordinate) * 1.25, normalized_width * 0.65);
return 1.0 - smoothstep(
max(normalized_width * 0.25 - derivative_width, 0.0),
normalized_width + derivative_width,
abs(coordinate - center)
);
}
void vertex() {
world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
}
void fragment() {
float phase = (
value_noise(world_position.xz * phase_scale) * 2.0 - 1.0
) * phase_strength;
float ebb = sin(TIME * tide_speed + phase) * 0.5 + 0.5;
float shoreline = land_inset / ribbon_width;
float lead_position = shoreline + ebb * tide_distance / ribbon_width;
float trail_position = lead_position + max(tide_distance * 0.72, 0.10) / ribbon_width;
float lead = soft_line(UV.y, lead_position, lead_width);
float trail = soft_line(UV.y, trail_position, trail_width);
float edge_fade = smoothstep(0.0, 0.055, UV.y) * (1.0 - smoothstep(0.90, 1.0, UV.y));
float lead_alpha = lead * lead_strength;
float trail_alpha = trail * trail_strength;
float combined_alpha = max(lead_alpha, trail_alpha) * edge_fade;
ALBEDO = mix(trail_color.rgb, lead_color.rgb, lead_alpha);
ALPHA = combined_alpha;
}

View file

@ -0,0 +1 @@
uid://5dauni8hb2lv

View file

@ -0,0 +1,19 @@
[gd_resource type="ShaderMaterial" load_steps=2 format=3]
[ext_resource type="Shader" path="res://world/materials/shoreline_tide.gdshader" id="1_shader"]
[resource]
render_priority = 1
shader = ExtResource("1_shader")
shader_parameter/lead_color = Color(0.619608, 0.87451, 0.854902, 1)
shader_parameter/trail_color = Color(0.431373, 0.780392, 0.768627, 1)
shader_parameter/ribbon_width = 0.85
shader_parameter/land_inset = 0.1
shader_parameter/tide_speed = 1.42
shader_parameter/tide_distance = 0.3
shader_parameter/lead_width = 0.065
shader_parameter/trail_width = 0.18
shader_parameter/lead_strength = 0.48
shader_parameter/trail_strength = 0.25
shader_parameter/phase_scale = 0.035
shader_parameter/phase_strength = 0.55

View file

@ -1,93 +1,241 @@
shader_type spatial;
render_mode cull_back, diffuse_burley, specular_schlick_ggx;
render_mode blend_mix, depth_draw_never, cull_back, unshaded;
uniform vec4 deep_color : source_color = vec4(0.055, 0.31, 0.50, 1.0);
uniform vec4 wave_color : source_color = vec4(0.18, 0.59, 0.74, 1.0);
uniform vec4 highlight_color : source_color = vec4(0.64, 0.88, 0.93, 1.0);
uniform sampler2D depth_texture : hint_depth_texture, repeat_disable, filter_nearest;
uniform float primary_wave_scale : hint_range(0.01, 2.0, 0.01) = 0.52;
uniform float secondary_wave_scale : hint_range(0.01, 2.0, 0.01) = 0.29;
uniform vec2 primary_speed = vec2(0.16, 0.07);
uniform vec2 secondary_speed = vec2(-0.08, 0.12);
uniform float wave_strength : hint_range(0.0, 1.0, 0.01) = 0.42;
uniform vec4 shore_color : source_color = vec4(0.475, 0.788, 0.796, 1.0);
uniform vec4 shallow_color : source_color = vec4(0.227, 0.604, 0.663, 1.0);
uniform vec4 deep_color : source_color = vec4(0.110, 0.345, 0.435, 1.0);
uniform vec4 tide_wash_color : source_color = vec4(0.360, 0.720, 0.735, 1.0);
uniform float highlight_fresnel_strength : hint_range(0.0, 1.0, 0.01) = 0.22;
uniform float fresnel_power : hint_range(0.5, 12.0, 0.1) = 4.0;
uniform float roughness : hint_range(0.0, 1.0, 0.01) = 0.32;
uniform float specular_strength : hint_range(0.0, 1.0, 0.01) = 0.28;
uniform float shallow_depth : hint_range(0.1, 3.0, 0.01) = 0.70;
uniform float deep_depth : hint_range(0.5, 12.0, 0.05) = 3.60;
uniform float shore_depth_width : hint_range(0.02, 1.0, 0.01) = 0.16;
uniform float shore_alpha : hint_range(0.1, 1.0, 0.01) = 0.44;
uniform float shallow_alpha : hint_range(0.1, 1.0, 0.01) = 0.60;
uniform float deep_alpha : hint_range(0.1, 1.0, 0.01) = 0.82;
uniform bool tide_effect_enabled = true;
uniform float tide_speed : hint_range(0.1, 3.0, 0.01) = 1.42;
uniform float tide_distance : hint_range(0.0, 0.6, 0.01) = 0.30;
uniform float tide_wash_width : hint_range(0.05, 1.0, 0.01) = 0.38;
uniform float tide_wash_strength : hint_range(0.0, 0.3, 0.01) = 0.11;
uniform float tide_wash_alpha_shift : hint_range(0.0, 0.15, 0.005) = 0.045;
uniform float phase_noise_scale : hint_range(0.001, 0.5, 0.001) = 0.055;
uniform float phase_noise_strength : hint_range(0.0, 2.0, 0.01) = 0.68;
uniform float contour_corner_variation : hint_range(0.0, 0.15, 0.005) = 0.045;
uniform float contour_smoothing_pixels : hint_range(0.0, 2.5, 0.05) = 1.0;
uniform float contour_smoothing_strength : hint_range(0.0, 1.0, 0.01) = 0.72;
uniform float contour_depth_reject : hint_range(0.05, 2.0, 0.01) = 0.60;
uniform float open_water_drift_scale : hint_range(0.001, 0.3, 0.001) = 0.018;
uniform vec2 open_water_drift_speed = vec2(0.010, -0.007);
uniform float open_water_drift_strength : hint_range(0.0, 0.08, 0.001) = 0.045;
uniform float distant_alpha_start : hint_range(10.0, 2000.0, 1.0) = 260.0;
uniform float distant_alpha_end : hint_range(20.0, 5000.0, 1.0) = 1400.0;
varying vec3 world_position;
varying float surface_view_depth;
float hash_21(vec2 point) {
return fract(sin(dot(point, vec2(127.1, 311.7))) * 43758.5453);
}
float value_noise(vec2 point) {
vec2 cell = floor(point);
vec2 local = fract(point);
vec2 blend = local * local * (3.0 - 2.0 * local);
float bottom = mix(hash_21(cell), hash_21(cell + vec2(1.0, 0.0)), blend.x);
float top = mix(
hash_21(cell + vec2(0.0, 1.0)),
hash_21(cell + vec2(1.0, 1.0)),
blend.x
);
return mix(bottom, top, blend.y);
}
float reconstruct_view_depth(
vec2 screen_uv,
float raw_depth,
mat4 inverse_projection
) {
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
vec3 ndc = vec3(screen_uv, raw_depth) * 2.0 - 1.0;
#else
vec3 ndc = vec3(screen_uv * 2.0 - 1.0, raw_depth);
#endif
vec4 view_position = inverse_projection * vec4(ndc, 1.0);
return -view_position.z / max(abs(view_position.w), 0.00001);
}
float sample_water_depth(
vec2 screen_uv,
float water_surface_depth,
mat4 inverse_projection
) {
float raw_depth = texture(depth_texture, screen_uv).r;
float scene_depth = reconstruct_view_depth(
screen_uv,
raw_depth,
inverse_projection
);
return scene_depth - water_surface_depth;
}
float contour_neighbor_weight(
float center_depth,
float neighbor_depth,
float rejection_distance
) {
if (neighbor_depth <= 0.0) {
return 0.0;
}
float safe_rejection = max(rejection_distance, 0.001);
return 1.0 - smoothstep(
safe_rejection * 0.35,
safe_rejection,
abs(neighbor_depth - center_depth)
);
}
void vertex() {
world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
vec4 world_vertex = MODEL_MATRIX * vec4(VERTEX, 1.0);
vec4 view_vertex = VIEW_MATRIX * world_vertex;
world_position = world_vertex.xyz;
surface_view_depth = -view_vertex.z;
}
void fragment() {
const vec2 primary_direction = vec2(0.835, 0.550);
const vec2 secondary_direction = vec2(-0.485, 0.875);
float raw_depth = texture(depth_texture, SCREEN_UV).r;
float scene_view_depth = reconstruct_view_depth(
SCREEN_UV,
raw_depth,
INV_PROJECTION_MATRIX
);
float water_depth = max(scene_view_depth - surface_view_depth, 0.0);
water_depth = min(water_depth, deep_depth * 4.0);
vec2 primary_coordinates = (
world_position.xz * primary_wave_scale
+ TIME * primary_speed
float contour_depth = water_depth;
float contour_feature_support = 1.0;
if (tide_effect_enabled) {
vec2 contour_texel = (
vec2(1.0) / vec2(textureSize(depth_texture, 0))
* contour_smoothing_pixels
);
vec2 secondary_coordinates = (
world_position.xz * secondary_wave_scale
+ TIME * secondary_speed
float contour_depth_sum = water_depth;
float contour_weight_sum = 1.0;
float left_depth = sample_water_depth(
SCREEN_UV - vec2(contour_texel.x, 0.0),
surface_view_depth,
INV_PROJECTION_MATRIX
);
float primary_phase = dot(primary_coordinates, primary_direction);
float secondary_phase = dot(
secondary_coordinates,
secondary_direction
float right_depth = sample_water_depth(
SCREEN_UV + vec2(contour_texel.x, 0.0),
surface_view_depth,
INV_PROJECTION_MATRIX
);
float primary_wave = sin(primary_phase);
float secondary_wave = sin(secondary_phase);
float combined_wave = (
primary_wave * 0.62
+ secondary_wave * 0.38
float upper_depth = sample_water_depth(
SCREEN_UV - vec2(0.0, contour_texel.y),
surface_view_depth,
INV_PROJECTION_MATRIX
);
float wave_band = smoothstep(
0.34,
0.66,
combined_wave * 0.5 + 0.5
float lower_depth = sample_water_depth(
SCREEN_UV + vec2(0.0, contour_texel.y),
surface_view_depth,
INV_PROJECTION_MATRIX
);
float left_weight = contour_neighbor_weight(
water_depth, left_depth, contour_depth_reject
);
float right_weight = contour_neighbor_weight(
water_depth, right_depth, contour_depth_reject
);
float upper_weight = contour_neighbor_weight(
water_depth, upper_depth, contour_depth_reject
);
float lower_weight = contour_neighbor_weight(
water_depth, lower_depth, contour_depth_reject
);
contour_depth_sum += left_depth * left_weight;
contour_depth_sum += right_depth * right_weight;
contour_depth_sum += upper_depth * upper_weight;
contour_depth_sum += lower_depth * lower_weight;
float neighbor_support = left_weight + right_weight + upper_weight + lower_weight;
contour_weight_sum += neighbor_support;
float smoothed_contour_depth = contour_depth_sum / contour_weight_sum;
contour_depth = mix(
water_depth,
smoothed_contour_depth,
contour_smoothing_strength
);
contour_feature_support = smoothstep(0.75, 1.75, neighbor_support);
}
vec2 wave_slope = (
cos(primary_phase)
* primary_direction
* primary_wave_scale
* 0.62
+ cos(secondary_phase)
* secondary_direction
* secondary_wave_scale
* 0.38
);
vec3 world_normal = normalize(
vec3(
-wave_slope.x * wave_strength * 0.34,
1.0,
-wave_slope.y * wave_strength * 0.34
)
);
NORMAL = normalize(
(VIEW_MATRIX * vec4(world_normal, 0.0)).xyz
);
float shallow_mix = smoothstep(0.0, shallow_depth, water_depth);
float deep_mix = smoothstep(shallow_depth, deep_depth, water_depth);
vec3 water_color = mix(shore_color.rgb, shallow_color.rgb, shallow_mix);
water_color = mix(water_color, deep_color.rgb, deep_mix);
float water_alpha = mix(shore_alpha, shallow_alpha, shallow_mix);
water_alpha = mix(water_alpha, deep_alpha, deep_mix);
float fresnel = pow(
1.0 - clamp(dot(NORMAL, VIEW), 0.0, 1.0),
fresnel_power
if (tide_effect_enabled) {
vec2 phase_coordinates = world_position.xz * phase_noise_scale;
float phase_variation = (
value_noise(phase_coordinates)
+ value_noise(phase_coordinates * 0.47 + vec2(9.7, 3.1)) * 0.5
- 0.75
) * phase_noise_strength;
float corner_variation = (
value_noise(phase_coordinates * 1.73 + vec2(4.2, 13.6)) * 2.0 - 1.0
) * contour_corner_variation;
float ebb = sin(TIME * tide_speed + phase_variation) * 0.5 + 0.5;
float lead_threshold = shore_depth_width + ebb * tide_distance + corner_variation;
float tide_wash = 1.0 - smoothstep(
lead_threshold,
lead_threshold + max(tide_wash_width, 0.001),
contour_depth
);
vec3 water_color = mix(
deep_color.rgb,
wave_color.rgb,
wave_band * wave_strength
);
ALBEDO = mix(
tide_wash *= contour_feature_support;
tide_wash *= 1.0 - smoothstep(shallow_depth, deep_depth, water_depth);
water_color = mix(
water_color,
highlight_color.rgb,
fresnel * highlight_fresnel_strength
tide_wash_color.rgb,
tide_wash * tide_wash_strength
);
ROUGHNESS = roughness;
SPECULAR = specular_strength;
water_alpha = min(
water_alpha
+ tide_wash * tide_wash_alpha_shift,
0.96
);
}
vec2 drift_coordinates = (
world_position.xz * open_water_drift_scale
+ TIME * open_water_drift_speed
);
float broad_drift = value_noise(drift_coordinates) * 2.0 - 1.0;
water_color *= 1.0 + broad_drift * open_water_drift_strength * deep_mix;
float distant_mix = smoothstep(
distant_alpha_start,
max(distant_alpha_end, distant_alpha_start + 1.0),
surface_view_depth
);
water_color = mix(water_color, deep_color.rgb, distant_mix * 0.72);
water_alpha = mix(water_alpha, 0.96, distant_mix);
ALBEDO = water_color;
ALPHA = clamp(water_alpha, 0.1, 0.96);
METALLIC = 0.0;
SPECULAR = 0.0;
ROUGHNESS = 1.0;
}

View file

@ -5,3 +5,30 @@
[resource]
render_priority = 0
shader = ExtResource("1_water_shader")
shader_parameter/shore_color = Color(0.47451, 0.788235, 0.796078, 1)
shader_parameter/shallow_color = Color(0.227451, 0.603922, 0.662745, 1)
shader_parameter/deep_color = Color(0.109804, 0.345098, 0.435294, 1)
shader_parameter/tide_wash_color = Color(0.360784, 0.721569, 0.733333, 1)
shader_parameter/shallow_depth = 0.7
shader_parameter/deep_depth = 3.6
shader_parameter/shore_depth_width = 0.16
shader_parameter/shore_alpha = 0.44
shader_parameter/shallow_alpha = 0.6
shader_parameter/deep_alpha = 0.82
shader_parameter/tide_effect_enabled = true
shader_parameter/tide_speed = 1.42
shader_parameter/tide_distance = 0.3
shader_parameter/tide_wash_width = 0.38
shader_parameter/tide_wash_strength = 0.11
shader_parameter/tide_wash_alpha_shift = 0.045
shader_parameter/phase_noise_scale = 0.055
shader_parameter/phase_noise_strength = 0.68
shader_parameter/contour_corner_variation = 0.045
shader_parameter/contour_smoothing_pixels = 1.0
shader_parameter/contour_smoothing_strength = 0.72
shader_parameter/contour_depth_reject = 0.6
shader_parameter/open_water_drift_scale = 0.018
shader_parameter/open_water_drift_speed = Vector2(0.01, -0.007)
shader_parameter/open_water_drift_strength = 0.045
shader_parameter/distant_alpha_start = 260.0
shader_parameter/distant_alpha_end = 1400.0

View file

@ -0,0 +1,23 @@
[gd_resource type="ShaderMaterial" load_steps=2 format=3]
[ext_resource type="Shader" path="res://world/materials/stylized_water.gdshader" id="1_water_shader"]
[resource]
render_priority = 0
shader = ExtResource("1_water_shader")
shader_parameter/shore_color = Color(0.47451, 0.788235, 0.796078, 1)
shader_parameter/shallow_color = Color(0.227451, 0.603922, 0.662745, 1)
shader_parameter/deep_color = Color(0.109804, 0.345098, 0.435294, 1)
shader_parameter/tide_wash_color = Color(0.360784, 0.721569, 0.733333, 1)
shader_parameter/shallow_depth = 0.7
shader_parameter/deep_depth = 3.6
shader_parameter/shore_depth_width = 0.16
shader_parameter/shore_alpha = 0.44
shader_parameter/shallow_alpha = 0.6
shader_parameter/deep_alpha = 0.82
shader_parameter/tide_effect_enabled = false
shader_parameter/open_water_drift_scale = 0.018
shader_parameter/open_water_drift_speed = Vector2(0.01, -0.007)
shader_parameter/open_water_drift_strength = 0.045
shader_parameter/distant_alpha_start = 260.0
shader_parameter/distant_alpha_end = 1400.0