diff --git a/src/runtime-three/runtime-host.js b/src/runtime-three/runtime-host.js index 4a307217..1e0cccab 100644 --- a/src/runtime-three/runtime-host.js +++ b/src/runtime-three/runtime-host.js @@ -465,42 +465,102 @@ export class RuntimeHost { uniform float waveAmp; varying vec2 vUv; varying vec3 vNormal; + varying vec3 vWorldPos; varying vec3 vViewDir; + varying vec4 vScreenPos; + vec3 gerstnerWave(vec4 wave, vec3 p) { + float steepness = wave.z; + float wavelength = wave.w; + float k = 2.0 * 3.14159 / wavelength; + float c = sqrt(9.8 / k); + vec2 d = normalize(wave.xy); + float f = k * (dot(d, p.xz) - c * time); + float a = steepness / k; + return vec3( + d.x * a * cos(f), + a * sin(f), + d.y * a * cos(f) + ); + } void main() { vUv = uv; - vNormal = normalize(normalMatrix * normal); vec3 pos = position; float upFactor = max(0.0, normal.y); - float w1 = sin(pos.x * 3.2 + time * 1.7) * 0.045; - float w2 = sin(pos.z * 2.8 + time * 1.3 + 1.4) * 0.038; - float w3 = cos(pos.x * 1.6 + pos.z * 1.4 + time * 2.1) * 0.028; - pos.y += (w1 + w2 + w3) * waveAmp * upFactor; + if (upFactor > 0.9) { + vec3 gridPoint = pos; + vec3 wave1 = gerstnerWave(vec4(1.0, 0.0, 0.25, 60.0), gridPoint); + vec3 wave2 = gerstnerWave(vec4(0.2, 0.86, 0.15, 31.0), gridPoint); + vec3 wave3 = gerstnerWave(vec4(0.2, 0.86, 0.06, 18.0), gridPoint); + pos += (wave1 + wave2 + wave3) * waveAmp * 0.5; + vNormal = normalize(normalMatrix * normalize(normal + vec3( + -(wave1.x + wave2.x + wave3.x) * 2.0, + 1.0, + -(wave1.z + wave2.z + wave3.z) * 2.0 + ))); + } else { + vNormal = normalize(normalMatrix * normal); + } vec4 worldPos = modelMatrix * vec4(pos, 1.0); + vWorldPos = worldPos.xyz; vViewDir = normalize(cameraPosition - worldPos.xyz); - gl_Position = projectionMatrix * viewMatrix * worldPos; + vScreenPos = projectionMatrix * viewMatrix * worldPos; + gl_Position = vScreenPos; } `; const fragmentShader = ` + precision highp float; uniform vec3 waterColor; uniform float surfaceOpacity; uniform float waveStrength; uniform float time; varying vec2 vUv; varying vec3 vNormal; + varying vec3 vWorldPos; varying vec3 vViewDir; + varying vec4 vScreenPos; + float noise(vec3 p) { + vec3 pi = floor(p); + vec3 pf = p - pi; + pf *= pf * (3.0 - 2.0 * pf); + float n = pi.x + pi.y * 57.0 + pi.z * 113.0; + return mix( + mix(mix(sin(n) * 43758.5453, sin(n + 1.0) * 43758.5453, pf.x), + mix(sin(n + 57.0) * 43758.5453, sin(n + 58.0) * 43758.5453, pf.x), pf.y), + mix(mix(sin(n + 113.0) * 43758.5453, sin(n + 114.0) * 43758.5453, pf.x), + mix(sin(n + 170.0) * 43758.5453, sin(n + 171.0) * 43758.5453, pf.x), pf.y), + pf.z + ); + } void main() { - vec2 uv1 = vUv + vec2(time * 0.05, time * 0.03); - vec2 uv2 = vUv * 1.6 + vec2(-time * 0.03, time * 0.06); - float r1 = sin(uv1.x * 10.0 + uv1.y * 8.0) * 0.5 + 0.5; - float r2 = sin(uv2.x * 7.0 - uv2.y * 12.0) * 0.5 + 0.5; - float ripple = r1 * r2; - float fresnel = pow(1.0 - max(0.0, dot(vNormal, vViewDir)), 2.5); - vec3 highlight = mix(waterColor, vec3(1.0), 0.55); - vec3 color = mix(waterColor, highlight, ripple * waveStrength * 0.55); - color = mix(color, vec3(1.0), fresnel * 0.14); - float alpha = surfaceOpacity + ripple * waveStrength * 0.12 + fresnel * 0.2; - alpha = clamp(alpha, 0.02, 1.0); - gl_FragColor = vec4(color, alpha); + vec3 n1 = normalize(vec3( + noise(vWorldPos + time * 0.3) - 0.5, + 0.8, + noise(vWorldPos * 1.5 + time * 0.25) - 0.5 + )); + vec3 n2 = normalize(vec3( + noise(vWorldPos * 0.7 - time * 0.2) - 0.5, + 0.9, + noise(vWorldPos * 2.2 - time * 0.18) - 0.5 + )); + vec3 surfaceNormal = normalize(mix(vNormal, n1, 0.4) + n2 * 0.3); + vec3 viewDir = normalize(vViewDir); + float vDotN = max(0.0, dot(viewDir, surfaceNormal)); + float fresnel = pow(1.0 - vDotN, 3.0) * 0.8 + 0.2; + vec2 screenUv = vScreenPos.xy / vScreenPos.w * 0.5 + 0.5; + vec2 refractionOffset = surfaceNormal.xz * (waveStrength * 0.05) * fresnel; + vec2 refractedUv = screenUv + refractionOffset; + float edgeFade = smoothstep(0.0, 0.1, refractedUv.x) * smoothstep(1.0, 0.9, refractedUv.x) * + smoothstep(0.0, 0.1, refractedUv.y) * smoothstep(1.0, 0.9, refractedUv.y); + vec3 skyColor = mix(waterColor, vec3(0.7, 0.8, 0.9), fresnel * 0.6); + float foamAmount = smoothstep(0.5, 0.8, fresnel) * waveStrength; + foamAmount *= (0.3 + 0.7 * sin(vWorldPos.x * 2.0 + time) * sin(vWorldPos.z * 1.5 + time * 0.8)); + foamAmount = clamp(foamAmount, 0.0, 0.3); + vec3 waterBase = mix(waterColor * 0.8, waterColor * 1.2, fresnel * 0.4); + vec3 finalColor = mix(waterBase, skyColor, fresnel * 0.35); + finalColor = mix(finalColor, vec3(1.0), foamAmount * 0.9); + float alpha = surfaceOpacity + fresnel * 0.3 + foamAmount * 0.15; + alpha = clamp(alpha, 0.05, 1.0); + gl_FragColor = vec4(finalColor, alpha); } `; const mat = new ShaderMaterial({ @@ -511,14 +571,16 @@ export class RuntimeHost { waterColor: { value: [cr, cg, cb] }, surfaceOpacity: { value: opacity }, waveStrength: { value: waveStrength }, - waveAmp: { value: waveStrength } + waveAmp: { value: waveStrength * 0.08 } }, transparent: true, - depthWrite: false + depthWrite: false, + side: 2 }); this.volumeAnimatedMaterials.push(mat); return mat; } + createFogQualityMaterial(fog) { const hex = fog.colorHex.replace("#", ""); const cr = parseInt(hex.substring(0, 2), 16) / 255;