scopedvr-map-core/viewport/wasser.gdshader
2026-11-01 19:31:33 +01:00

92 lines
4.3 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

shader_type spatial;
// Wasser-Oberfläche pro Map (Karte t_28fdef03) — SPIEGELGLEICHE Datei:
// scopedvr-client → res://shaders/wasser.gdshader
// scopedvr-map-editor → res://src/viewport/wasser.gdshader (byteidentischer
// Spiegel, sync via tools/sync_wasser.sh — wie terrain_splat/terrain_arrays).
//
// Quest-3-Perf-Vertrag (bewusste Absagen der Spec): KEINE echte Spiegelung,
// KEINE Brechung, KEIN SSR. Spiegel-Impression = Skybox-Farbreflex (uniform)
// + Fresnel-Glanz. Wellen NUR im Fragment-Shader (analytische 2-Layer-Sinus-
// Normalen — keine Wellen-Texturen, keine Geometrie-Bewegung). Ufer-Schaum +
// Flachwasser-Transparenz aus der Tiefe-Textur (ufer_tex, R8 = Tiefe/TIEFE_MAX,
// siehe SvmapWasser.ufer_bild). Ferne Fragmente vereinfachen sich selbst
// (Wasserdistanz-Regel): nur Grundfarbe, keine Wellen/Schaum-Mathematik.
//
// Eine Fläche, ein Draw-Call — Overdraw ist die einzige echte Kostenquelle.
render_mode blend_mix, depth_draw_never, cull_back, unshaded, shadows_disabled;
uniform vec4 farbe_flach : source_color = vec4(0.10, 0.30, 0.34, 1.0);
uniform vec4 farbe_tief : source_color = vec4(0.02, 0.10, 0.15, 1.0);
uniform vec3 reflex : source_color = vec3(0.72, 0.83, 0.92); // Skybox-Farbreflex (Wetter)
uniform float glanz : hint_range(0.0, 2.0) = 1.0; // Glint-Stärke (Wetter)
uniform sampler2D ufer_tex : filter_linear; // R = Tiefe 0..1 (0..TIEFE_MAX)
uniform float tiefe_max = 4.0; // SvmapWasser.TIEFE_MAX (Spiegel-Konstante)
uniform float schaum_band = 0.35; // SvmapWasser.SCHAUM_BAND (Meter)
uniform float wellen_scale = 0.55; // Wellenfrequenz (1/m)
uniform float wellen_staerke = 0.16; // Normal-Neigung (flach = Spiegel-Optik)
varying vec3 welt_pos; // Welt-Position des Fragments (Fresnel + Distanz-Regel)
void vertex() {
welt_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
}
// Analytische Wellen-Normale aus 2 Scroll-Ebenen (kleine Skala, verschiedene
// Richtungen/Geschwindigkeiten — läuft nie synchron): h = Σ A·sin(dot(p,d)·f + t·s),
// n ∝ (−∂h/∂x, 1, −∂h/∂z) mit ∂h/∂p = Σ A·f·d·cos(…).
vec3 wellen_normal(vec2 p, float t) {
vec2 d1 = vec2(0.94, 0.34);
vec2 d2 = vec2(-0.42, 0.91);
float f1 = wellen_scale * 2.1;
float f2 = wellen_scale * 3.7;
float s1 = 1.25;
float s2 = 1.75;
float a1 = wellen_staerke;
float a2 = wellen_staerke * 0.6;
vec2 g1 = d1 * (f1 * a1 * cos(dot(p, d1) * f1 + t * s1));
vec2 g2 = d2 * (f2 * a2 * cos(dot(p, d2) * f2 + t * s2));
vec2 g = g1 + g2;
return normalize(vec3(-g.x, 1.0, -g.y));
}
void fragment() {
// Tiefe am Fragment (ufer_tex, UV deckt die Map 1:1 ab — SvmapWasser.ufer_bild).
float tiefe01 = texture(ufer_tex, UV).r;
float tiefe = tiefe01 * tiefe_max;
// Wasserdistanz-Regel: ferne Fragmente = vereinfachter Modus (Grundfarbe,
// flache Normale, kein Schaum). Weiches Fenster 45→90 m, pro Fragment.
float fern = smoothstep(45.0, 90.0, length(welt_pos - CAMERA_POSITION_WORLD));
float detail = 1.0 - fern;
vec3 n = wellen_normal(welt_pos.xz, TIME);
n = normalize(mix(vec3(0.0, 1.0, 0.0), n, detail));
vec3 v = normalize(CAMERA_POSITION_WORLD - welt_pos);
float fresnel = pow(1.0 - clamp(dot(n, v), 0.0, 1.0), 5.0);
// Grundfarbe: seicht heller/durchsichtiger, tief dunkler.
vec3 grund = mix(farbe_flach.rgb, farbe_tief.rgb, clamp(tiefe01 * 1.4, 0.0, 1.0));
// Spiegel-Impression: Skybox-Farbreflex via Fresnel + Sonnen-Glint.
vec3 reflex_f = reflex * mix(0.25, 1.0, fresnel);
vec3 l = normalize(vec3(-0.45, 0.75, 0.5)); // SvmapTerrain.RELIEF_SUN (Spiegel)
float glint = pow(max(dot(reflect(-v, n), l), 0.0), 48.0) * glanz;
// Ufer-Schaum: schmales Band an der Wasserlinie, Kante leicht wellig
// (Wellen-Phase pulsiert das Band ±20 %).
float schaum_grenze = schaum_band * (0.9 + 0.2 * sin(welt_pos.x * 1.7 + welt_pos.z * 1.3 + TIME * 1.2));
float schaum = (1.0 - smoothstep(0.0, max(schaum_grenze, 0.02), tiefe)) * detail;
vec3 farbe = grund + reflex_f + vec3(glint) + vec3(schaum * 0.85);
// Transparenz: Ufer fast klar, tiefes Wasser dicht. Schaum deckt voll.
float alpha = mix(0.38, 0.88, clamp(tiefe01 * 1.6, 0.0, 1.0));
alpha = max(alpha, schaum * 0.9);
// spatial-Vertrag: Farbe über ALBEDO, Transparenz über ALPHA (COLOR gibt
// es nur im canvas_item-Shader — schreibend dort = „Constants cannot be
// modified“).
ALBEDO = farbe;
ALPHA = alpha;
}