"use client"; import { useFBO } from "@react-three/drei"; import { Canvas, useFrame, useThree } from "@react-three/fiber"; import { useEffect, useMemo, useRef, useState } from "react"; import * as THREE from "three"; import { cn } from "@/lib/utils"; interface UniformMap { [key: string]: { value: unknown }; } interface ShaderBackgroundProps { vertexShader?: string; fragmentShader?: string; uniforms?: UniformMap; className?: string; color?: string; } const defaultVertexShader = ` varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position, 1.0); } `; const defaultFragmentShader = ` precision highp float; varying vec2 vUv; uniform float u_time; uniform vec3 u_resolution; uniform sampler2D u_fluid; uniform float u_distortionAmount; uniform vec3 u_color; #define time u_time const float arrow_density = 4.5; const float arrow_length = .45; const int iterationTime1 = 20; const int iterationTime2 = 20; const int vector_field_mode = 0; const float scale = 6.; const float velocity_x = 0.1; const float velocity_y = 0.2; const float mode_2_speed = 2.5; const float mode_1_detail = 200.; const float mode_1_twist = 50.; const bool isArraw = true; const vec3 luma = vec3(0.2126, 0.7152, 0.0722); float f(in vec2 p) { return tanh(p.x+sin(p.y+time*velocity_x)) * sin(p.y*p.x*0.1+time*velocity_y); } struct Field { vec2 vel; vec2 pos; }; Field field(in vec2 p,in int mode) { Field field; if(mode == 0){ vec2 ep = vec2(0.05,0.); vec2 rz= vec2(0); for( int i=0; i 0.0) { vec2 mousePos = u_mouse.xy; vec2 mousePrev = u_mouse.zw; vec2 mouseVel = mousePos - mousePrev; float velMagnitude = length(mouseVel); float q = ln(U, mousePos, mousePrev); vec2 m = mousePos - mousePrev; float l = length(m); if (l > 0.0) m = min(l, 10.0) * m / l; float brushSizeFactor = 1e-4 / uBrushSize; float strengthFactor = 0.03 * uBrushStrength; float falloff = exp(-brushSizeFactor*q*q*q); falloff = pow(falloff, 0.5); me.xyw += strengthFactor * falloff * vec3(m, 10.); if (velMagnitude < 2.0) { float distToCursor = length(U - mousePos); float influence = exp(-distToCursor * 0.01); float cursorDecay = mix(1.0, uStopDecay, influence); me.xy *= cursorDecay; me.z *= cursorDecay; } } gl_FragColor = clamp(me, -0.4, 0.4); } } `; const config = { brushSize: 12.0, brushStrength: 0.45, fluidDecay: 0.95, trailLength: 0.85, stopDecay: 0.85, distortionAmount: 1.5, lerpFactor: 0.15, }; interface SimulationProps { vertexShader: string; fragmentShader: string; uniforms: UniformMap; color: string; } const ShaderPlane = ({ vertexShader, fragmentShader, uniforms, color, }: SimulationProps) => { const { size, gl } = useThree(); const fluidMaterial = useRef(null); const displayMaterial = useRef(null); const fluidMeshRef = useRef(null); const displayMeshRef = useRef(null); const fluidTarget1 = useFBO(size.width, size.height, { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat, type: THREE.FloatType, }); const fluidTarget2 = useFBO(size.width, size.height, { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat, type: THREE.FloatType, }); const currentFluidTarget = useRef(fluidTarget1); const previousFluidTarget = useRef(fluidTarget2); const frameCount = useRef(0); const mouse = useRef({ x: 0, y: 0, prevX: 0, prevY: 0 }); const targetMouse = useRef({ x: 0, y: 0 }); const smoothMouse = useRef({ x: 0, y: 0 }); useEffect(() => { const halfX = size.width / 2; const halfY = size.height / 2; mouse.current = { x: halfX, y: halfY, prevX: halfX, prevY: halfY }; targetMouse.current = { x: halfX, y: halfY }; smoothMouse.current = { x: halfX, y: halfY }; }, [size]); useEffect(() => { const { domElement } = gl; const handleMouseMove = (event: MouseEvent) => { const rect = domElement.getBoundingClientRect(); targetMouse.current.x = event.clientX - rect.left; targetMouse.current.y = rect.height - (event.clientY - rect.top); }; const handleMouseLeave = () => {}; domElement.addEventListener("mousemove", handleMouseMove); domElement.addEventListener("mouseleave", handleMouseLeave); return () => { domElement.removeEventListener("mousemove", handleMouseMove); domElement.removeEventListener("mouseleave", handleMouseLeave); }; }, [gl]); const fluidUniforms = useMemo( () => ({ u_time: { value: 0 }, u_resolution: { value: new THREE.Vector2(size.width, size.height) }, u_mouse: { value: new THREE.Vector4(0, 0, 0, 0) }, u_frame: { value: 0 }, iPreviousFrame: { value: null }, uBrushSize: { value: config.brushSize }, uBrushStrength: { value: config.brushStrength }, uFluidDecay: { value: config.fluidDecay }, uTrailLength: { value: config.trailLength }, uStopDecay: { value: config.stopDecay }, }), [size.width, size.height], ); const displayUniforms = useMemo( () => ({ u_time: { value: 0 }, u_resolution: { value: new THREE.Vector3(size.width, size.height, 1) }, u_fluid: { value: null }, u_distortionAmount: { value: config.distortionAmount }, u_color: { value: new THREE.Color(color) }, ...uniforms, }), [size.width, size.height, uniforms, color], ); useEffect(() => { frameCount.current = 0; fluidTarget1.setSize(size.width, size.height); fluidTarget2.setSize(size.width, size.height); if (fluidMaterial.current) { const resolution = fluidMaterial.current.uniforms.u_resolution .value as THREE.Vector2; resolution.set(size.width, size.height); } if (displayMaterial.current) { const resolution = displayMaterial.current.uniforms.u_resolution .value as THREE.Vector3; resolution.set(size.width, size.height, 1); } }, [size, fluidTarget1, fluidTarget2]); useEffect(() => { if (displayMaterial.current) { displayMaterial.current.uniforms.u_fluid.value = currentFluidTarget.current.texture; } }, []); useFrame(({ gl, clock, camera }) => { const time = clock.getElapsedTime(); mouse.current.prevX = smoothMouse.current.x; mouse.current.prevY = smoothMouse.current.y; smoothMouse.current.x += (targetMouse.current.x - smoothMouse.current.x) * config.lerpFactor; smoothMouse.current.y += (targetMouse.current.y - smoothMouse.current.y) * config.lerpFactor; mouse.current.x = smoothMouse.current.x; mouse.current.y = smoothMouse.current.y; if (fluidMaterial.current) { fluidMaterial.current.uniforms.u_time.value = time; fluidMaterial.current.uniforms.u_frame.value = frameCount.current; fluidMaterial.current.uniforms.u_mouse.value.set( mouse.current.x, mouse.current.y, mouse.current.prevX, mouse.current.prevY, ); fluidMaterial.current.uniforms.u_resolution.value.set( size.width, size.height, ); } if (fluidMaterial.current && fluidMeshRef.current) { fluidMaterial.current.uniforms.iPreviousFrame.value = previousFluidTarget.current.texture; gl.setRenderTarget(currentFluidTarget.current); gl.render(fluidMeshRef.current, camera); } if (displayMaterial.current) { displayMaterial.current.uniforms.u_time.value = time; displayMaterial.current.uniforms.u_resolution.value.set( size.width, size.height, 1, ); displayMaterial.current.uniforms.u_fluid.value = currentFluidTarget.current.texture; } if (displayMaterial.current && displayMeshRef.current) { gl.setRenderTarget(null); gl.render(displayMeshRef.current, camera); } const temp = currentFluidTarget.current; currentFluidTarget.current = previousFluidTarget.current; previousFluidTarget.current = temp; frameCount.current += 1; }); const fluidPlane = useMemo( () => ( ), [fluidUniforms], ); const displayPlane = useMemo( () => ( ), [displayUniforms, fragmentShader, vertexShader], ); return ( <> {fluidPlane} {displayPlane} ); }; const Shader7 = ({ vertexShader = defaultVertexShader, fragmentShader = defaultFragmentShader, uniforms = {}, className, color = "#bbffcc", }: ShaderBackgroundProps) => { const [simKey, setSimKey] = useState(0); useEffect(() => { const handleResize = () => setSimKey((prev) => prev + 1); window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); return (
); }; export { Shader7 };