import * as THREE from 'three'; // Enhanced ripple simulation with multiple trailing ripples and lighting const FluidSimShader = { uniforms: { tPrev: { value: null }, iResolution: { value: new THREE.Vector2() }, iTime: { value: 0.0 }, mouse: { value: new THREE.Vector3(-1, -1, 0.0) }, dissipation: { value: 0.950 }, // Slightly more persistent for trails tension: { value: 1.5 }, // Higher tension for stronger ripples radius: { value: 10.0 }, // Larger splat radius trailLength: { value: 3 }, // Number of trailing ripples }, vertexShader: ` varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position.xy, 0.0, 1.0); } `, fragmentShader: ` precision highp float; varying vec2 vUv; uniform sampler2D tPrev; uniform vec2 iResolution; uniform float iTime; uniform vec3 mouse; uniform float dissipation; uniform float tension; uniform float radius; uniform float trailLength; vec2 readRG(vec2 uv) { vec4 c = texture2D(tPrev, uv); return c.rg; } void main() { vec2 texel = 1.0 / iResolution; vec2 currPrev = readRG(vUv); float curr = currPrev.r; float prev = currPrev.g; // Enhanced 8-neighbor laplacian for stronger ripples float up = readRG(vUv + vec2(0.0, texel.y)).r; float down = readRG(vUv + vec2(0.0, -texel.y)).r; float right = readRG(vUv + vec2( texel.x, 0.0)).r; float left = readRG(vUv + vec2(-texel.x, 0.0)).r; // Diagonal neighbors for smoother ripples float upLeft = readRG(vUv + vec2(-texel.x, texel.y)).r; float upRight = readRG(vUv + vec2( texel.x, texel.y)).r; float downLeft = readRG(vUv + vec2(-texel.x, -texel.y)).r; float downRight = readRG(vUv + vec2( texel.x, -texel.y)).r; // Enhanced laplacian with diagonal weights float lap = (up + down + left + right) * 0.2 + (upLeft + upRight + downLeft + downRight) * 0.05 - curr; // Wave equation with enhanced parameters float next = curr + (curr - prev) * dissipation + lap * tension; // Multiple trailing ripples from mouse movement if (mouse.z > 0.0001) { vec2 uvPx = vUv * iResolution; vec2 d = uvPx - mouse.xy; float dist = length(d); // Create multiple concentric ripples for (float i = 0.0; i < 4.0; i++) { if (i >= trailLength) break; float offset = i * radius * 0.4; // Spacing between ripples float r = radius + offset; float timeOffset = i * 0.3; // Temporal offset for trailing effect // Gaussian with sine wave for ripple pattern float g = exp(-pow(dist - offset, 2.0) / (r * r * 0.5)); float ripple = sin(dist * 0.2 - iTime * 8.0 + timeOffset) * g; // Diminishing strength for trailing ripples float strength = mouse.z * (1.0 - i * 0.2) * 0.8; next += ripple * strength; } } gl_FragColor = vec4(next, curr, 0.0, 1.0); } ` }; // Enhanced distortion shader with dynamic lighting export const FluidDistortionShader = { uniforms: { tDiffuse: { value: null }, tSim: { value: null }, iResolution: { value: new THREE.Vector2() }, amount: { value: 0.12 }, // Stronger base distortion chromaticAmount: { value: 0.015 }, // Enhanced chromatic aberration lightPosition: { value: new THREE.Vector3(0.5, 0.5, 1.0) }, // Light position lightIntensity: { value: 1.5 }, // Light brightness lightColor: { value: new THREE.Color(0.8, 0.9, 1.0) }, // Cool light color normalStrength: { value: 2.0 }, // How pronounced the lighting effect is ambientLight: { value: 0.15 }, // Base ambient lighting }, vertexShader: ` varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position.xy, 0.0, 1.0); } `, fragmentShader: ` precision highp float; varying vec2 vUv; uniform sampler2D tDiffuse; uniform sampler2D tSim; uniform vec2 iResolution; uniform float amount; uniform float chromaticAmount; uniform vec3 lightPosition; uniform float lightIntensity; uniform vec3 lightColor; uniform float normalStrength; uniform float ambientLight; void main() { vec2 texel = 1.0 / iResolution; // Sample height field for normal calculation float hC = texture2D(tSim, vUv).r; float hL = texture2D(tSim, vUv - vec2(texel.x, 0.0)).r; float hR = texture2D(tSim, vUv + vec2(texel.x, 0.0)).r; float hD = texture2D(tSim, vUv - vec2(0.0, texel.y)).r; float hU = texture2D(tSim, vUv + vec2(0.0, texel.y)).r; // Calculate gradient and normal vec2 grad = vec2(hR - hL, hU - hD) * normalStrength; vec3 normal = normalize(vec3(-grad.x, -grad.y, 1.0)); // Enhanced distortion with trailing effect vec2 baseOffset = grad * amount; // Add subtle trailing distortion based on height vec2 trailOffset = grad * abs(hC) * amount * 0.3; vec2 totalOffset = baseOffset + trailOffset; // Chromatic aberration with enhanced separation vec2 chromaticOffset = grad * chromaticAmount; vec2 uvR = vUv + totalOffset + chromaticOffset; vec2 uvG = vUv + totalOffset; vec2 uvB = vUv + totalOffset - chromaticOffset; // Clamp UVs uvR = clamp(uvR, vec2(0.0), vec2(1.0)); uvG = clamp(uvG, vec2(0.0), vec2(1.0)); uvB = clamp(uvB, vec2(0.0), vec2(1.0)); // Sample distorted colors float r = texture2D(tDiffuse, uvR).r; float g = texture2D(tDiffuse, uvG).g; float b = texture2D(tDiffuse, uvB).b; vec3 distortedColor = vec3(r, g, b); // Dynamic lighting calculation vec3 lightDir = normalize(vec3(lightPosition.xy - vUv, lightPosition.z)); float NdotL = max(dot(normal, lightDir), 0.0); // Create rim lighting effect for ripples float rimLight = pow(1.0 - abs(dot(normal, vec3(0.0, 0.0, 1.0))), 2.0); // Combine lighting effects vec3 lighting = lightColor * (NdotL * lightIntensity + rimLight * 0.3) + ambientLight; // Apply lighting selectively - stronger where there are ripples float rippleIntensity = abs(hC) + length(grad) * 0.5; rippleIntensity = clamp(rippleIntensity, 0.0, 1.0); // Blend original color with lit color based on ripple presence vec3 finalColor = mix(distortedColor, distortedColor * lighting, rippleIntensity); gl_FragColor = vec4(finalColor, 1.0); } ` }; // Enhanced fluid simulation factory export function createFluidSimulation(renderer, dpr = 1) { const simScene = new THREE.Scene(); const simCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); const quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), new THREE.ShaderMaterial({ uniforms: THREE.UniformsUtils.clone(FluidSimShader.uniforms), vertexShader: FluidSimShader.vertexShader, fragmentShader: FluidSimShader.fragmentShader, depthTest: false, depthWrite: false })); simScene.add(quad); // Higher precision for better ripple quality const params = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat, type: THREE.FloatType, // Use float for better precision depthBuffer: false, stencilBuffer: false }; let width = Math.max(2, Math.floor(window.innerWidth * dpr)); let height = Math.max(2, Math.floor(window.innerHeight * dpr)); let rtA = new THREE.WebGLRenderTarget(width, height, params); let rtB = new THREE.WebGLRenderTarget(width, height, params); // Initialize renderer.setRenderTarget(rtA); renderer.clear(); renderer.setRenderTarget(rtB); renderer.clear(); renderer.setRenderTarget(null); quad.material.uniforms.iResolution.value.set(width, height); quad.material.uniforms.tPrev.value = rtA.texture; function swap() { const tmp = rtA; rtA = rtB; rtB = tmp; } function update(mouseX, mouseY, strength, timeSec) { quad.material.uniforms.iTime.value = timeSec; if (mouseX < 0.0 || mouseY < 0.0) { quad.material.uniforms.mouse.value.set(-1, -1, 0.0); } else { // Enhanced strength for better trailing effect const enhancedStrength = Math.max(0.0, Math.min(1.0, strength * 1.5)); quad.material.uniforms.mouse.value.set(mouseX, mouseY, enhancedStrength); } quad.material.uniforms.tPrev.value = rtA.texture; renderer.setRenderTarget(rtB); renderer.render(simScene, simCamera); renderer.setRenderTarget(null); swap(); } function getTexture() { return rtA.texture; } function resize(w, h, newDpr = dpr) { width = Math.max(2, Math.floor(w * newDpr)); height = Math.max(2, Math.floor(h * newDpr)); rtA.setSize(width, height); rtB.setSize(width, height); quad.material.uniforms.iResolution.value.set(width, height); } return { update, getTexture, resize }; }