Files
gamedev-the-steward/world/jajce/materials/water.gdshader
T

82 lines
3.5 KiB
Plaintext

shader_type spatial;
render_mode blend_mix, depth_prepass_alpha, cull_disabled, diffuse_burley, specular_schlick_ggx;
uniform float wave_strength : hint_range(0.0, 0.3) = 0.025;
uniform float wave_speed : hint_range(0.0, 3.0) = 0.72;
uniform float flow_speed : hint_range(0.0, 2.0) = 0.28;
uniform bool pool_surface = false;
uniform float pool_outlet_z = -20.0;
uniform vec3 deep_color : source_color = vec3(0.035, 0.34, 0.54);
uniform vec3 shallow_color : source_color = vec3(0.12, 0.65, 0.69);
uniform vec3 sunlit_color : source_color = vec3(0.57, 0.83, 0.88);
uniform vec3 foam_color : source_color = vec3(0.9, 0.96, 0.91);
varying vec3 water_position;
float painted_noise(vec2 p) {
vec2 cell = floor(p);
vec2 blend = fract(p);
blend = blend * blend * (3.0 - 2.0 * blend);
vec4 corners = fract(sin(vec4(
dot(cell, vec2(127.1, 311.7)),
dot(cell + vec2(1.0, 0.0), vec2(127.1, 311.7)),
dot(cell + vec2(0.0, 1.0), vec2(127.1, 311.7)),
dot(cell + vec2(1.0), vec2(127.1, 311.7))
)) * 43758.5453);
return mix(mix(corners.x, corners.y, blend.x), mix(corners.z, corners.w, blend.x), blend.y);
}
void vertex() {
// World coordinates keep the pool and both ribbons at the same ripple scale.
water_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
float broad_wave = sin(water_position.x * 0.42 + water_position.z * 0.18 + TIME * wave_speed);
float crossing_wave = cos(water_position.x * 0.7 - water_position.z * 0.31 + TIME * wave_speed * 0.73);
VERTEX.y += (broad_wave + crossing_wave * 0.45) * wave_strength;
}
void fragment() {
// The ribbon owns the outlet so two transparent surfaces never overlap.
if (pool_surface && water_position.z >= pool_outlet_z) {
discard;
}
vec2 flow = water_position.xz - vec2(0.0, TIME * flow_speed);
float broad_patch = painted_noise(flow * vec2(0.18, 0.3));
float small_patch = painted_noise(flow * vec2(0.64, 0.85));
float ripple_phase = flow.y * 3.2 + sin(flow.x * 0.83) * 1.2 + small_patch * 0.8;
float ribbon = sin(ripple_phase);
float stroke_width = max(fwidth(ribbon) * 0.7, 0.012);
float strokes = smoothstep(0.975 - stroke_width, 0.975 + stroke_width, ribbon);
strokes *= smoothstep(0.58, 0.8, painted_noise(flow * vec2(1.1, 1.8))) * 0.16;
strokes *= 1.0 - smoothstep(0.28, 0.8, fwidth(ripple_phase));
vec3 ripple_normal = normalize(vec3(
cos(flow.x * 0.83 + flow.y * 0.37 + broad_patch * 6.0) * 0.006,
1.0,
cos(ripple_phase + small_patch * 5.0) * 0.008
));
NORMAL = normalize((VIEW_MATRIX * vec4(ripple_normal, 0.0)).xyz);
float fresnel = pow(1.0 - clamp(dot(NORMAL, normalize(VIEW)), 0.0, 1.0), 2.6);
// Painted sky patches need no screen/depth texture and work in Compatibility.
vec3 world_view = normalize((INV_VIEW_MATRIX * vec4(VIEW, 0.0)).xyz);
vec3 reflection = reflect(-world_view, ripple_normal);
vec2 sky_uv = reflection.xz / max(reflection.y + 0.35, 0.25);
float clouds = painted_noise(sky_uv * 2.0 + vec2(0.7, 2.4));
clouds = smoothstep(0.49, 0.77, clouds + painted_noise(sky_uv * 4.5) * 0.18);
float bank_distance = min(UV.x, 1.0 - UV.x) * 2.0;
if (pool_surface) {
bank_distance = 1.0 - length(UV * 2.0 - 1.0);
}
float bank_foam = (1.0 - smoothstep(0.01, 0.045, bank_distance));
bank_foam *= smoothstep(0.4, 0.75, small_patch) * 0.48;
vec3 water_color = mix(deep_color, shallow_color, 0.19 + broad_patch * 0.2);
water_color = mix(water_color, sunlit_color, clouds * (0.13 + fresnel * 0.35));
water_color = mix(water_color, foam_color, strokes + bank_foam);
ALBEDO = water_color;
ROUGHNESS = 0.38 + broad_patch * 0.08;
SPECULAR = 0.14;
ALPHA = smoothstep(0.0, 0.018, bank_distance) * 0.97;
}