diff --git a/src/main/resources/assets/etna/shaders/include/terrain_water.glsl b/src/main/resources/assets/etna/shaders/include/terrain_water.glsl index ace7b59..a463ac6 100644 --- a/src/main/resources/assets/etna/shaders/include/terrain_water.glsl +++ b/src/main/resources/assets/etna/shaders/include/terrain_water.glsl @@ -581,8 +581,12 @@ float waterDepthBelow(out float trust) { // the bottom seen through it, and on murky or deep water a soft shadow in the water itself. Before 0.8.1 // the surface took the sharp shadow of its own point, a second copy of every shadow next to the one on // the bottom (shifted from it by the depth, the more the lower the sun), and the two crossed each other out -// in strange shapes. Four points down the view's way into the water, each at an equal share of the light -// scattered back above the bottom, each the bilinear blend of four texels (shadowSoftAt). A point counts +// in strange shapes. Four points down the line of sight into the water, each at an equal share of the light +// scattered back above the bottom, each the bilinear blend of four texels (shadowSoftAt). The line of sight +// goes straight on, unbent: the bottom shows through the water unbent too (the blend just lays the water over +// it), so the shade in the water lands on the shadow seen on the bottom. Up to 0.9.0 it followed the +// refracted ray, which runs steeper, and the two shadows sat side by side: shaded water over a lit bottom +// looked colourless and see-through, lit water over a shaded bottom like a patch of colour. A point counts // as shaded only by what stands above the water's surface on its way to the sun: it is looked up where // that way crosses the surface (the same texels of the map, a shallower depth), so a point that ends up // inside a bottom rising ahead of the view (a beach stepping up out of a lake) doesn't take the bottom over @@ -596,7 +600,7 @@ float waterBodyShadow(vec3 normal) { if (trust <= 0.0) { return shadowVisibility(normal, false); } - vec3 down = refract(normalize(etnaViewPos), vec3(0.0, 1.0, 0.0), 1.0 / WATER_IOR); + vec3 down = normalize(etnaViewPos); float cosDown = max(-down.y, 0.1); // Light scattered back from z blocks down went z / sin(sun) blocks through the water on its way down // and comes z / cos(view) blocks back up: green's extinction over both, per block of depth. @@ -975,8 +979,8 @@ vec4 shadeWater(vec4 color, vec3 normal) { // would only shimmer. vec3 n = normalize(vec3(-waves.seen.x, 1.0, -waves.seen.y)); n.y *= sign(normal.y); - float fresnel = waterFresnel(dot(toEye, n)); - vec3 skyReflection = etnaWaterSky(reflect(-toEye, n)) * open; + float fresnel = waterFresnel(waterSeenCos(dot(toEye, n), etnaPixelFootprint)); + vec3 skyReflection = etnaWaterSky(waterSeenMirror(reflect(-toEye, n), etnaPixelFootprint)) * open; float sun = sunGlint(waves, toEye, h, light) + waterGlitter(p, t, etnaPixelFootprint, waves.seen, target, light); #ifdef ETNA_SHADOWS // No glint and no glitter in the shade of trees and cliffs. diff --git a/src/main/resources/assets/etna/shaders/include/water_waves.glsl b/src/main/resources/assets/etna/shaders/include/water_waves.glsl index c4ddfdd..5fee4fd 100644 --- a/src/main/resources/assets/etna/shaders/include/water_waves.glsl +++ b/src/main/resources/assets/etna/shaders/include/water_waves.glsl @@ -71,6 +71,26 @@ vec2 swellSeen(vec2 p, float t, float footprint) { return slope; } +// Far water seen nearly edge on. The waves a pixel no longer resolves (waveSeen) still tilt the facets it +// sees towards the eye, by about HIDDEN_TILT (the swell's and the ripples' slopes), which only matters when +// the view is about as flat as that: those facets mirror sky from above the horizon, and less of it than a +// flat mirror would. Without them, water far away seen from a block or two above it mirrored almost only +// the whitish horizon and looked white. How far the facets tilt, for a pixel footprint blocks wide. +const float HIDDEN_TILT = 0.08; +float waterHiddenTilt(float footprint) { + return HIDDEN_TILT * (1.0 - waveSeen(length(SWELL_WAVES[0].xy), footprint)); +} +// The cosine between the view and the facets the pixel sees, from cosView, the one to the resolved surface. +float waterSeenCos(float cosView, float footprint) { + float tilt = waterHiddenTilt(footprint); + return sqrt(cosView * cosView + tilt * tilt); +} +// Where those facets mirror the sky from, for `mirrored`, the reflection off the resolved surface. +vec3 waterSeenMirror(vec3 mirrored, float footprint) { + float lift = 2.0 * waterHiddenTilt(footprint); + float up = max(mirrored.y, 0.0); + return normalize(vec3(mirrored.x, sqrt(up * up + lift * lift), mirrored.z)); +} // Schlick's Fresnel for water seen from the air, at the cosine between the view and the surface's normal: // the share of the light the surface mirrors (4 % straight down, all of it at grazing angles). float waterFresnel(float cosView) { diff --git a/src/main/resources/assets/etna/shaders/post/water_ssr.fsh b/src/main/resources/assets/etna/shaders/post/water_ssr.fsh index 829d597..01a44e9 100644 --- a/src/main/resources/assets/etna/shaders/post/water_ssr.fsh +++ b/src/main/resources/assets/etna/shaders/post/water_ssr.fsh @@ -275,9 +275,9 @@ void main() { vec3 toEye = -position / dist; // The sky's reflection the water drew, and how strong, came off the full swell; the ray follows a // calmer one. - vec3 dir = reflect(-toEye, n); + vec3 dir = waterSeenMirror(reflect(-toEye, n), footprint); vec3 ray = reflect(-toEye, normalize(vec3(-slope.x * TRACE_WAVES, 1.0, -slope.y * TRACE_WAVES))); - float mirror = waterFresnel(dot(toEye, n)) * WaterSsr.x; + float mirror = waterFresnel(waterSeenCos(dot(toEye, n), footprint)) * WaterSsr.x; // The haze and the render-distance fog the water shader's colour went through (aerialHaze in // terrain_dark.glsl, calculateFinalColor in terrain.fsh), for the light the reflection adds or takes away. float openSky = Air.w > 0.5 ? 1.0 - Shafts.z : 0.0;