From 32730547662e77d3a32b6623af1d1d940ee57548 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Wed, 14 Oct 2026 14:37:31 -0700 Subject: [PATCH] Water Shader Update --- .../Shaders/water_pass/water_pass.frag.glsl | 129 ++++++++++++------ 1 file changed, 84 insertions(+), 45 deletions(-) diff --git a/leenkx/Shaders/water_pass/water_pass.frag.glsl b/leenkx/Shaders/water_pass/water_pass.frag.glsl index bce540ca..9dc0ad3d 100644 --- a/leenkx/Shaders/water_pass/water_pass.frag.glsl +++ b/leenkx/Shaders/water_pass/water_pass.frag.glsl @@ -96,41 +96,81 @@ vec3 sampleWaterNormals(vec2 hitXY, float speed, out vec2 tcnor0, out vec2 tcnor return normalize(n0 + n1 - 1.0); } +float waveHeight(vec2 xy, float speed) { + return (sin(xy.x * 10.0 / waterDisplace + speed) * cos(xy.y * 10.0 / waterDisplace + speed) + sin(xy.x * 20.0 / waterDisplace + speed * 1.3) * cos(xy.y * 20.0 / waterDisplace + speed * 1.3) * 0.5) / 50.0 * waterDisplace; +} + void main() { float gdepth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0; vec3 vray = normalize(viewRay); float speed = time * 2.0 * waterSpeed; - bool isSky = (gdepth == 1.0); + float farPlane = cameraProj.y / (1.0 - cameraProj.x); + float cosAng = dot(eyeLook, vray); + float domeR = farPlane * 0.94; + float tDome = -3.5 * vray.z + sqrt(max(domeR * domeR - 12.25 * (1.0 - vray.z * vray.z), 0.0)); + float domeDepth01 = cameraProj.x + cameraProj.y / (tDome * cosAng); + bool isSky = (gdepth * 0.5 + 0.5) >= domeDepth01; // Ray-plane intersection with water surface (z = waterLevel) - float denom = dot(vray, vec3(0.0, 0.0, 1.0)); + float denom = vray.z; float tWater = (waterLevel - eye.z) / denom; bool hasWaterHit = (abs(denom) > 0.0001) && (tWater > 0.0); if (eye.z < waterLevel) { vec2 tc = texCoord; - float fogFactor; - - if (hasWaterHit && denom > 0.0) { - // Looking up at water surface - apply normal distortion - vec3 hit = eye + tWater * vray; - vec2 tc0, tc1; - vec3 n2 = sampleWaterNormals(hit.xy * waterFreq, speed, tc0, tc1); - tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract; - fogFactor = clamp(tWater * waterDensity, 0.0, 0.95); - } else { - // Looking forward/down - distort via water surface above fragment - vec3 p = getPos(eye, eyeLook, vray, gdepth, cameraProj); - vec2 wxy = isSky ? (eye.xy + vray.xy * 50.0) : p.xy; - vec2 tc0, tc1; - vec3 n2 = sampleWaterNormals(wxy * waterFreq, speed, tc0, tc1); - tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract; - float waterDist = isSky ? 50.0 : length(p - eye); - fogFactor = clamp(waterDist * waterDensity, 0.0, 0.95); + float depthThick = 1.0 + (waterLevel - eye.z); + vec3 p = vec3(0.0); + float geomDist = farPlane; + if (!isSky) { + p = getPos(eye, eyeLook, vray, gdepth, cameraProj); + geomDist = length(p - eye); } + bool upRay = hasWaterHit && denom > 0.0; + float waterDist = upRay ? min(tWater, geomDist) : geomDist; + + vec2 wxy; + if (upRay && tWater < geomDist) { + wxy = (eye + tWater * vray).xy; + } else if (!isSky) { + wxy = p.xy; + } else { + wxy = eye.xy + vray.xy * 50.0; + } + vec2 tc0, tc1; + vec3 n2 = sampleWaterNormals(wxy * waterFreq, speed, tc0, tc1); + tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract; + float ddepth2 = textureLod(gbufferD, tc, 0.0).r * 2.0 - 1.0; + float dist2 = cameraProj.y / ((ddepth2 * 0.5 + 0.5) - cameraProj.x) / cosAng; + if (dist2 > geomDist + 1.0) tc = texCoord; vec3 refracted = textureLod(tex, tc, 0.0).rgb; - fragColor.rgb = mix(refracted, waterColor, fogFactor); + vec3 outCol = refracted; + + if (upRay && tWater < geomDist) { + outCol *= max(1.0 - length(n2.xy) * 1.5, 0.0); + vec3 h2 = normalize(-vray + ld); + outCol += pow(max(dot(n2, h2), 0.0), 80.0) * 0.6; + } + + if (!isSky) { + float fdw = abs(p.z - (waterLevel + waveHeight(p.xy, speed))); + float bandw = 0.1 + waterDist * 0.003; + if (fdw < bandw) { + float foamLod = clamp(waterDist / 15.0, 0.0, 5.0); + vec3 fm0 = textureLod(sfoam, tc0 * 3.0 + vec2(speed / 30.0, speed / 50.0), foamLod).rgb; + vec3 fm1 = textureLod(sfoam, tc1 * 4.0 + vec2(-speed / 35.0, speed / 45.0), foamLod).rgb; + float fstr = clamp(1.0 - fm0.r * 0.5 - fm1.b * 0.5, 0.0, 1.0); + float ffac = clamp(1.0 - fdw / bandw, 0.0, 1.0); + float foamFade = clamp(1.0 - (waterLevel - eye.z) * 0.4, 0.0, 1.0); + vec3 foamCol = mix(waterColor, vec3(1.0), 0.6); + outCol = mix(outCol, foamCol, ffac * fstr * 0.5 * foamFade); + } + } + + float fogFactor = 1.0 - exp(-waterDist * waterDensity * 0.006 * depthThick); + outCol = mix(outCol, waterColor, clamp(fogFactor, 0.0, 1.0)); + + fragColor.rgb = outCol; fragColor.a = 1.0; return; } @@ -144,21 +184,19 @@ void main() { if (isSky) tWater = min(tWater, 100.0); // Clamp to prevent aliasing at horizon vec3 p = isSky ? (eye + tWater * vray) : getPos(eye, eyeLook, vray, gdepth, cameraProj); - float horizonFactor = clamp(1.0 - tWater / 60.0, 0.0, 1.0); - if (!isSky && p.z > waterLevel) { - fragColor = vec4(0.0); + if (!isSky && p.z > waterLevel - 0.01) { + fragColor = textureLod(tex, texCoord, 0.0); return; } + float horizonFactor = clamp(1.0 - tWater / 60.0, 0.0, 1.0); // Displace surface float geomZ = p.z; // undisplaced depth for foam/fog tests - p.z += (sin(p.x * 10.0 / waterDisplace + speed) * cos(p.y * 10.0 / waterDisplace + speed) - + sin(p.x * 20.0 / waterDisplace + speed * 1.3) * cos(p.y * 20.0 / waterDisplace + speed * 1.3) * 0.5) - / 50.0 * waterDisplace; + p.z += waveHeight(p.xy, speed); // Hit plane to determine uvs vec3 v = normalize(eye - p); - float t = (waterLevel - eye.z) / dot(v, vec3(0.0, 0.0, 1.0)); + float t = (waterLevel - eye.z) / v.z; vec3 hit = eye + t * v; // Sample normal maps @@ -171,8 +209,8 @@ void main() { tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract; } else { float ddepth = textureLod(gbufferD, texCoord + (n2.xy * n2.z) / 40.0, 0.0).r * 2.0 - 1.0; - vec3 p2 = getPos(eye, eyeLook, vray, ddepth, cameraProj); - tc = p2.z > waterLevel ? texCoord : texCoord + (n2.xy * n2.z) / 30.0 * waterRefract; + float dist2 = cameraProj.y / ((ddepth * 0.5 + 0.5) - cameraProj.x) / cosAng; + tc = (eye.z + vray.z * dist2) > waterLevel ? texCoord : texCoord + (n2.xy * n2.z) / 30.0 * waterRefract; } // Light @@ -222,26 +260,25 @@ void main() { #else fragColor.rgb = mix(refracted, reflectedEnv, waterReflect * fresnel); #endif - // Water color tint - blend rather than multiply to preserve brightness float colorMix = isSky ? 0.7 : 0.5; - fragColor.rgb = mix(fragColor.rgb, fragColor.rgb * waterColor, colorMix * horizonFactor); + fragColor.rgb = mix(fragColor.rgb, waterColor, colorMix); // Blinn-Phong specular using half-vector, faded at horizon vec3 h = normalize(v + ld); float specAmount = pow(max(dot(n2, h), 0.0), 200.0) * (200.0 + 8.0) / (PI * 8.0); - fragColor.rgb += specAmount * (isSky ? 0.3 : 1.0); - // Depth fog - blend toward waterColor with depth, faded at horizon - float depthFog = clamp(-(geomZ - waterLevel) * waterDensity, 0.0, 0.9); - fragColor.rgb = mix(fragColor.rgb, waterColor, depthFog * horizonFactor); + fragColor.rgb += specAmount * (isSky ? 0.3 : 1.0) * horizonFactor; + float waterColumn = isSky ? 1.0 : max(length(p - eye) - tWater, 0.0); + float depthFog = 1.0 - exp(-waterColumn * waterDensity * 0.006); + vec3 surfaceCol = mix(waterColor, reflectedEnv, waterReflect * fresnel); + fragColor.rgb = mix(fragColor.rgb, surfaceCol, min(depthFog, 0.9)); // Alpha fades smoothly at horizon instead of hard cut // Skydome sits 3.5 below the camera (_skydomeMatrix), so its horizon // appears where the ray z reaches -3.5 / domeRadius - float farPlane = cameraProj.y / (1.0 - cameraProj.x); float horizonFade = clamp((-3.5 / (farPlane * 0.95) - vray.z) * 20.0, 0.0, 1.0); - fragColor.a = isSky ? horizonFade : clamp(abs(geomZ - waterLevel) * 5.0, 0.0, 1.0); - - // Foam - based on actual geometry depth below water surface - float fd = isSky ? 1.0 : abs(geomZ - waterLevel); - if (fd < 0.1) { + fragColor.a = isSky ? horizonFade : clamp(abs(geomZ - waterLevel) * 5.0 + 0.1, 0.0, 1.0); + float waveH = p.z - geomZ; // displacement already applied above + float band = 0.1 + tWater * 0.003; + float fd = isSky ? 1.0 : abs(geomZ - (waterLevel + waveH)); + if (fd < band) { // Based on foam by Owen Deery // http://fire-face.com/personal/water // Distance-based LOD blurs foam at range to reduce noise @@ -250,8 +287,10 @@ void main() { vec2 foamUV1 = tcnor1 * 4.0 + vec2(-speed / 35.0, speed / 45.0); vec3 foamMask0 = textureLod(sfoam, foamUV0, foamLod).rgb; vec3 foamMask1 = textureLod(sfoam, foamUV1, foamLod).rgb; - float foamStrength = clamp(1.0 - foamMask0.r * 0.5 - foamMask1.b * 0.5, 0.0, 1.0); - float fac = (1.0 - (fd * (1.0 / 0.1))) * horizonFactor; - fragColor.rgb = mix(fragColor.rgb, mix(fragColor.rgb, waterColor + 0.2, foamStrength), clamp(fac, 0.0, 1.0) * 0.5); + float foamMask = clamp(1.0 - foamMask0.r * 0.5 - foamMask1.b * 0.5, 0.0, 1.0); + float fac = clamp(1.0 - fd / band, 0.0, 1.0); + vec3 foamCol = mix(waterColor, vec3(1.0), 0.6); + fragColor.rgb = mix(fragColor.rgb, foamCol, fac * foamMask * 0.8); + fragColor.a = max(fragColor.a, fac * foamMask); } }