techkaczynski.dev / code

summaryrefslogtreecommitdiff
path: root/res/sdr/ocean.fs
diff options
context:
space:
mode:
authortech kaczynski <tk@techkaczynski.dev>2026-09-22 22:38:57 +1000
committertech kaczynski <tk@techkaczynski.dev>2026-09-22 22:38:57 +1000
commit08cb8d6a585dd552b69a488d9db1be123f01467a (patch)
tree77d50ce30bbc102b08486929ba654b5bef70af43 /res/sdr/ocean.fs
initial commit.
Diffstat (limited to 'res/sdr/ocean.fs')
-rw-r--r--res/sdr/ocean.fs264
1 files changed, 264 insertions, 0 deletions
diff --git a/res/sdr/ocean.fs b/res/sdr/ocean.fs
new file mode 100644
index 0000000..53b81f5
--- /dev/null
+++ b/res/sdr/ocean.fs
@@ -0,0 +1,264 @@
+#version 330
+
+in vec3 world_position;
+
+uniform float time;
+uniform vec3 camera_position;
+
+uniform vec3 sun_direction;
+uniform vec3 moon_direction;
+uniform vec3 sun_colour;
+uniform vec3 moon_colour;
+uniform vec3 ambient_colour;
+uniform vec3 horizon_colour;
+uniform vec3 zenith_colour;
+
+uniform float sun_intensity;
+uniform float moon_intensity;
+uniform float ambient_intensity;
+
+out vec4 finalColor;
+
+float
+hash12(vec2 position) {
+ vec3 hash_value = fract(position.xyx * 0.1031);
+ hash_value += dot(hash_value, hash_value.yzx + vec3(33.33));
+
+ return fract((hash_value.x + hash_value.y) * hash_value.z);
+}
+
+float
+noise2(vec2 position) {
+ vec2 cell = floor(position);
+ vec2 fraction = fract(position);
+ vec2 curve = fraction * fraction *
+ (vec2(3.0) - 2.0 * fraction);
+
+ float a = hash12(cell);
+ float b = hash12(cell + vec2(1.0, 0.0));
+ float c = hash12(cell + vec2(0.0, 1.0));
+ float d = hash12(cell + vec2(1.0, 1.0));
+
+ return mix(
+ mix(a, b, curve.x),
+ mix(c, d, curve.x),
+ curve.y
+ );
+}
+
+float
+fbm(vec2 position) {
+ vec2 sample_position = position;
+ float value = 0.0;
+
+ value += noise2(sample_position) * 0.500;
+ sample_position *= 2.02;
+
+ value += noise2(sample_position) * 0.250;
+ sample_position *= 2.03;
+
+ value += noise2(sample_position) * 0.125;
+ sample_position *= 2.01;
+
+ value += noise2(sample_position) * 0.0625;
+
+ return value;
+}
+
+float
+wave_height(vec2 position) {
+ vec2 domain = position * 0.008;
+
+ vec2 warp = vec2(
+ fbm(domain * 0.8 + vec2(time * 0.030, 0.0)),
+ fbm(domain * 0.8 + vec2(7.3, time * 0.024))
+ );
+
+ domain += (warp - vec2(0.5)) * 1.8;
+
+ float wave_a = sin(dot(domain, normalize(vec2(1.0, 0.28))) *
+ 7.0 + time * 0.45);
+
+ float wave_b = sin(dot(domain, normalize(vec2(-0.42, 1.0))) *
+ 11.0 + time * 0.72);
+
+ float wave_c = sin(dot(domain, normalize(vec2(0.75, 0.63))) *
+ 15.0 - time * 0.95);
+
+ float wave_d = sin(dot(domain, normalize(vec2(-0.88, 0.18))) *
+ 5.0 + time * 0.28);
+
+ return wave_a * 0.50 + wave_b * 0.25 +
+ wave_c * 0.15 + wave_d * 0.10;
+}
+
+vec3
+wave_normal(vec2 position) {
+ const float offset = 2.0;
+
+ float height = wave_height(position);
+ float height_x = wave_height(position + vec2(offset, 0.0));
+ float height_z = wave_height(position + vec2(0.0, offset));
+
+ return normalize(vec3(
+ height - height_x,
+ 0.55,
+ height - height_z
+ ));
+}
+
+float
+light_glint(vec3 normal, vec3 view_direction,
+ vec3 light_direction, float power) {
+ vec3 half_direction = normalize(
+ light_direction + view_direction
+ );
+
+ return pow(max(dot(normal, half_direction), 0.0), power);
+}
+
+void
+main() {
+ vec2 position = world_position.xz;
+
+ vec3 normal = wave_normal(position);
+ vec3 view_direction = normalize(
+ camera_position - world_position
+ );
+
+ /* Large-scale irregular turquoise/blue variation. */
+ float patch_a = fbm(
+ position * 0.0025 + vec2(time * 0.010, 3.1)
+ );
+
+ float patch_b = fbm(
+ position * 0.0013 + vec2(-2.7, time * 0.006)
+ );
+
+ float water_patch = clamp(
+ patch_a * 0.65 + patch_b * 0.35,
+ 0.0, 1.0
+ );
+
+ vec3 deep_blue = vec3(0.02, 0.42, 0.70);
+ vec3 turquoise = vec3(0.03, 0.73, 0.74);
+ vec3 aqua = vec3(0.16, 0.78, 0.88);
+
+ vec3 base_colour = mix(
+ deep_blue, turquoise, water_patch
+ );
+
+ base_colour = mix(
+ base_colour,
+ aqua,
+ smoothstep(0.72, 1.0, water_patch)
+ );
+
+ /*
+ * Actually light the base water using the current environment.
+ * During the day this stays bright; at night it becomes properly
+ * dark and takes on the ambient sky colour.
+ */
+ vec3 ambient_light =
+ vec3(0.10) +
+ ambient_colour * ambient_intensity * 1.8;
+
+ vec3 colour = base_colour * ambient_light;
+
+ /*
+ * Fake reflected sky. The reflected wave normal determines
+ * whether we see more horizon or zenith colour.
+ */
+ float facing = max(dot(normal, view_direction), 0.0);
+ float fresnel = 1.0 - facing;
+ fresnel *= fresnel;
+ fresnel *= fresnel;
+
+ vec3 reflected_direction = reflect(
+ -view_direction,
+ normal
+ );
+
+ float sky_height = smoothstep(
+ -0.12, 0.70, reflected_direction.y
+ );
+
+ vec3 reflected_sky = mix(
+ horizon_colour,
+ zenith_colour,
+ sky_height
+ );
+
+ colour = mix(
+ colour,
+ reflected_sky,
+ 0.18 + fresnel * 0.45
+ );
+
+ /* Sun and moon glints. */
+ float sun_glint = light_glint(
+ normal,
+ view_direction,
+ normalize(sun_direction),
+ 24.0
+ );
+
+ float moon_glint = light_glint(
+ normal,
+ view_direction,
+ normalize(moon_direction),
+ 20.0
+ );
+
+ colour += sun_colour * sun_glint *
+ sun_intensity * 0.30;
+
+ colour += moon_colour * moon_glint *
+ moon_intensity * 0.18;
+
+ /* Irregular whitecaps. */
+ float cap_a = fbm(
+ position * 0.020 +
+ vec2(time * 0.080, -time * 0.050)
+ );
+
+ float cap_b = fbm(
+ position * 0.045 +
+ vec2(-4.0, time * 0.110)
+ );
+
+ float cap_field = cap_a * 0.55 + cap_b * 0.45;
+
+ float crest = wave_height(
+ position * 1.35 + vec2(17.0, -9.0)
+ ) * 0.5 + 0.5;
+
+ float steepness = clamp(
+ 1.0 - normal.y,
+ 0.0, 1.0
+ );
+
+ float whitecap =
+ smoothstep(0.72, 0.86, cap_field) *
+ smoothstep(0.58, 0.80, crest) *
+ smoothstep(0.02, 0.10, steepness);
+
+ /*
+ * Foam also receives environment lighting rather than remaining
+ * glowing white at night.
+ */
+ vec3 foam_colour = vec3(0.92, 0.98, 0.98);
+ foam_colour *= vec3(0.18) +
+ ambient_colour * ambient_intensity * 1.5;
+
+ foam_colour += sun_colour *
+ sun_intensity * 0.25;
+
+ colour = mix(
+ colour,
+ foam_colour,
+ whitecap * 0.90
+ );
+
+ finalColor = vec4(0.03, 0.73, 0.74, 1.0);
+}