diff --git a/src/runtime-three/runtime-host.ts b/src/runtime-three/runtime-host.ts index 96aea0ba..53e7b525 100644 --- a/src/runtime-three/runtime-host.ts +++ b/src/runtime-three/runtime-host.ts @@ -668,200 +668,6 @@ export class RuntimeHost { }); } - // High-quality water shader with normal map animation, refraction, foam, and object interaction. - private createWaterQualityMaterial( - water: { colorHex: string; surfaceOpacity: number; waveStrength: number }, - faceId: string - ): ShaderMaterial { - const isTopFace = faceId === "posY"; - const baseOpacity = Math.max(0.05, Math.min(1, water.surfaceOpacity)); - const opacity = isTopFace ? Math.min(1, baseOpacity + 0.2) : baseOpacity * 0.45; - const waveStrength = water.waveStrength; - - // Parse hex color into r/g/b floats for GLSL - const hex = water.colorHex.replace("#", ""); - const cr = parseInt(hex.substring(0, 2), 16) / 255; - const cg = parseInt(hex.substring(2, 4), 16) / 255; - const cb = parseInt(hex.substring(4, 6), 16) / 255; - - const vertexShader = /* glsl */ ` - uniform float time; - uniform float waveAmp; - varying vec2 vUv; - varying vec3 vNormal; - varying vec3 vWorldPos; - varying vec3 vViewDir; - varying vec4 vScreenPos; - varying float vDepth; - - // Gerstner wave calculation for realistic water motion - 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; - vec3 pos = position; - float upFactor = max(0.0, normal.y); - - // Composite three Gerstner waves at different frequencies - 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); - vScreenPos = projectionMatrix * viewMatrix * worldPos; - vDepth = -vScreenPos.z; // Store depth for refraction calculations - gl_Position = vScreenPos; - } - `; - - const fragmentShader = /* glsl */ ` - 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; - varying float vDepth; - - // Simplex-like procedural noise for normal variation and foam - 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() { - // Multi-scale normal perturbation — subtle, data-driven - 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)); - - // Fresnel effect (brighter at grazing angles) - float fresnel = pow(1.0 - vDotN, 3.0) * 0.8 + 0.2; - - // Specular highlight: sharp, shiny water reflection - vec3 reflection = reflect(-viewDir, surfaceNormal); - float specular = pow(max(0.0, dot(reflection, normalize(vec3(0.3, 0.8, 0.5)))), 16.0) * fresnel * 0.6; - - // Depth-based coloring: water darkens with depth - float depthFade = 1.0 / (1.0 + vDepth * 0.008); - - // **Color dominance**: user water color is primary, only subtle environment tint - vec3 baseWaterColor = waterColor; - - // Slight sky tinting only at grazing angles for realism - vec3 environmentTint = vec3(0.85, 0.9, 1.0); - baseWaterColor = mix(baseWaterColor, environmentTint, fresnel * 0.12); - - // Apply depth darkening - vec3 waterWithDepth = baseWaterColor * mix(0.3, 1.0, depthFade); - - // **Foam with object interaction**: - // Layer 1: Wave peaks (fresnel-based, high curvature) - float foamPeaks = smoothstep(0.6, 0.85, fresnel) * sin(vWorldPos.x * 2.0 + time) * waveStrength; - foamPeaks = clamp(foamPeaks, 0.0, 0.2); - - // Layer 2: Procedural detail (never-repeating, time-varies) - float foamDetail = abs(noise(vWorldPos * 3.0 + time * 0.4)) * 0.15; - - // Layer 3: "Object interaction" — use screen-space depth gradient - // Objects touching the water create discontinuities in the depth buffer - vec2 screenUv = vScreenPos.xy / vScreenPos.w * 0.5 + 0.5; - vec2 off1 = screenUv + vec2(0.01, 0.0); - vec2 off2 = screenUv + vec2(-0.01, 0.0); - vec2 off3 = screenUv + vec2(0.0, 0.01); - vec2 off4 = screenUv + vec2(0.0, -0.01); - - // Estimate depth gradient using texture coordinate variation - float depthGradient = abs(sin(vWorldPos.x * 0.5) - sin(vWorldPos.x * 0.5 + 0.3)) * waveStrength; - float foamInteraction = smoothstep(0.2, 0.7, depthGradient) * 0.25; - - float totalFoam = min(0.4, foamPeaks + foamDetail + foamInteraction); - - // Foam appears as bright white, blended with water - vec3 foamColor = vec3(1.0); - vec3 waterWithFoam = mix(waterWithDepth, foamColor, totalFoam); - - // Add specular highlight on top - waterWithFoam += specular * vec3(1.0); - - // Final alpha combines opacity, fresnel, and foam - float alpha = surfaceOpacity + fresnel * 0.25 + totalFoam * 0.2; - alpha = clamp(alpha, 0.08, 1.0); - - gl_FragColor = vec4(waterWithFoam, alpha); - } - `; - - const mat = new ShaderMaterial({ - vertexShader, - fragmentShader, - uniforms: { - time: { value: this.volumeTime }, - waterColor: { value: [cr, cg, cb] }, - surfaceOpacity: { value: opacity }, - waveStrength: { value: waveStrength }, - waveAmp: { value: waveStrength * 0.08 } - }, - transparent: true, - depthWrite: false, - side: 2 // DoubleSide for side/bottom faces - }); - this.volumeAnimatedMaterials.push(mat); - return mat; - } - // Soft edge-faded fog shader with slow drift animation — quality mode only. private createFogQualityMaterial(fog: { colorHex: string; density: number }): ShaderMaterial { const hex = fog.colorHex.replace("#", "");