Files
reui-blocks/blocks/shader17.tsx
T

979 lines
28 KiB
TypeScript

"use client";
import { Canvas, useFrame, useLoader, useThree } from "@react-three/fiber";
import { useEffect, useMemo, useRef } from "react";
import * as THREE from "three";
import { cn } from "@/lib/utils";
interface Background27Props {
className?: string;
imageUrl?: string;
pointerColor?: string;
}
interface PointerState {
uv: THREE.Vector2;
prev: THREE.Vector2;
delta: THREE.Vector2;
instant: THREE.Vector2;
moved: boolean;
target: THREE.Vector2;
}
const POINTER_LERP_SPEED = 10;
const POINTER_VELOCITY_LERP = 40;
const POINTER_MIN_DELTA = 0.0001;
const POINTER_POSITION_EPSILON = 0.0002;
const MAX_INFLUENCE_TEXTURE_SIZE = 1024;
const createDefaultInfluenceTexture = () => {
const texture = new THREE.DataTexture(
new Uint8Array([255, 255, 255, 255]),
1,
1,
THREE.RGBAFormat,
);
texture.needsUpdate = true;
texture.wrapS = THREE.ClampToEdgeWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
return texture;
};
const getImageDimensions = (image: CanvasImageSource) => {
const video = image as HTMLVideoElement;
if (video && typeof video.videoWidth === "number" && video.videoWidth > 0) {
return { width: video.videoWidth, height: video.videoHeight };
}
const htmlImage = image as HTMLImageElement;
if (
htmlImage &&
typeof htmlImage.naturalWidth === "number" &&
htmlImage.naturalWidth > 0
) {
return { width: htmlImage.naturalWidth, height: htmlImage.naturalHeight };
}
const bitmap = image as ImageBitmap;
if (
bitmap &&
typeof bitmap.width === "number" &&
typeof bitmap.height === "number" &&
bitmap.width > 0 &&
bitmap.height > 0
) {
return { width: bitmap.width, height: bitmap.height };
}
const canvas = image as HTMLCanvasElement;
if (
canvas &&
typeof canvas.width === "number" &&
typeof canvas.height === "number" &&
canvas.width > 0 &&
canvas.height > 0
) {
return { width: canvas.width, height: canvas.height };
}
return { width: 1, height: 1 };
};
const createLuminanceInfluenceTexture = (
source: THREE.Texture,
targetWidth?: number,
targetHeight?: number,
) => {
const image = source.image as CanvasImageSource | undefined;
if (!image) {
return null;
}
const { width: imageWidth, height: imageHeight } = getImageDimensions(image);
if (!imageWidth || !imageHeight) {
return null;
}
let canvasWidth = Math.max(1, targetWidth ?? imageWidth);
let canvasHeight = Math.max(1, targetHeight ?? imageHeight);
const widthScale = Math.min(MAX_INFLUENCE_TEXTURE_SIZE / canvasWidth, 1);
const heightScale = Math.min(MAX_INFLUENCE_TEXTURE_SIZE / canvasHeight, 1);
const scale = Math.min(widthScale, heightScale);
if (scale < 1) {
canvasWidth = Math.max(1, Math.floor(canvasWidth * scale));
canvasHeight = Math.max(1, Math.floor(canvasHeight * scale));
}
const canvas = document.createElement("canvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const context = canvas.getContext("2d");
if (!context) {
return null;
}
context.imageSmoothingEnabled = true;
context.drawImage(image, 0, 0, canvasWidth, canvasHeight);
const imageData = context.getImageData(0, 0, canvasWidth, canvasHeight);
const { data } = imageData;
const output = new Uint8Array(canvasWidth * canvasHeight * 4);
for (let i = 0; i < canvasWidth * canvasHeight; i++) {
const baseIndex = i * 4;
const luminance = Math.round(
data[baseIndex] * 0.299 +
data[baseIndex + 1] * 0.587 +
data[baseIndex + 2] * 0.114,
);
output[baseIndex] = luminance;
output[baseIndex + 1] = luminance;
output[baseIndex + 2] = luminance;
output[baseIndex + 3] = 255;
}
const texture = new THREE.DataTexture(
output,
canvasWidth,
canvasHeight,
THREE.RGBAFormat,
);
texture.needsUpdate = true;
texture.wrapS = THREE.ClampToEdgeWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
return texture;
};
const BASE_VERTEX_SHADER = `
precision highp float;
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUv;
varying vec2 vL;
varying vec2 vR;
varying vec2 vT;
varying vec2 vB;
uniform vec2 u_texel;
void main () {
vUv = uv;
vL = vUv - vec2(u_texel.x, 0.0);
vR = vUv + vec2(u_texel.x, 0.0);
vT = vUv + vec2(0.0, u_texel.y);
vB = vUv - vec2(0.0, u_texel.y);
gl_Position = vec4(position.xy, 0.0, 1.0);
}
`;
const ADVECTION_SHADER = `
precision highp float;
precision highp sampler2D;
varying vec2 vUv;
uniform sampler2D u_velocity_texture;
uniform sampler2D u_input_texture;
uniform sampler2D u_influence_texture;
uniform vec2 u_texel;
uniform float u_dt;
uniform float u_use_influence;
vec4 bilerp (sampler2D sam, vec2 uv, vec2 tsize) {
vec2 st = uv / tsize - 0.5;
vec2 iuv = floor(st);
vec2 fuv = fract(st);
vec4 a = texture2D(sam, (iuv + vec2(0.5, 0.5)) * tsize);
vec4 b = texture2D(sam, (iuv + vec2(1.5, 0.5)) * tsize);
vec4 c = texture2D(sam, (iuv + vec2(0.5, 1.5)) * tsize);
vec4 d = texture2D(sam, (iuv + vec2(1.5, 1.5)) * tsize);
return mix(mix(a, b, fuv.x), mix(c, d, fuv.x), fuv.y);
}
void main () {
vec2 coord = vUv - u_dt * bilerp(u_velocity_texture, vUv, u_texel).xy * u_texel;
float influence = texture2D(u_influence_texture, vec2(vUv.x, 1.0 - vUv.y)).r;
float dissipation = (0.85 + influence * 0.1 * u_use_influence);
gl_FragColor = dissipation * bilerp(u_input_texture, coord, u_texel);
gl_FragColor.a = 1.0;
}
`;
const DIVERGENCE_SHADER = `
precision highp float;
precision highp sampler2D;
varying vec2 vUv;
varying vec2 vL;
varying vec2 vR;
varying vec2 vT;
varying vec2 vB;
uniform sampler2D u_velocity_texture;
void main () {
float L = texture2D(u_velocity_texture, vL).x;
float R = texture2D(u_velocity_texture, vR).x;
float T = texture2D(u_velocity_texture, vT).y;
float B = texture2D(u_velocity_texture, vB).y;
float div = 0.6 * (R - L + T - B);
gl_FragColor = vec4(div, 0.0, 0.0, 1.0);
}
`;
const PRESSURE_SHADER = `
precision highp float;
precision highp sampler2D;
varying vec2 vUv;
varying vec2 vL;
varying vec2 vR;
varying vec2 vT;
varying vec2 vB;
uniform sampler2D u_pressure_texture;
uniform sampler2D u_divergence_texture;
uniform sampler2D u_influence_texture;
void main () {
float influence = texture2D(u_influence_texture, vec2(vUv.x, 1.0 - vUv.y)).r;
float L = texture2D(u_pressure_texture, vL).x;
float R = texture2D(u_pressure_texture, vR).x;
float T = texture2D(u_pressure_texture, vT).x;
float B = texture2D(u_pressure_texture, vB).x;
float divergence = texture2D(u_divergence_texture, vUv).x;
float pressure = (L + R + B + T - divergence) * 0.25;
pressure += (0.2 * influence);
gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0);
}
`;
const GRADIENT_SHADER = `
precision highp float;
precision highp sampler2D;
varying vec2 vUv;
varying vec2 vL;
varying vec2 vR;
varying vec2 vT;
varying vec2 vB;
uniform sampler2D u_pressure_texture;
uniform sampler2D u_velocity_texture;
uniform sampler2D u_influence_texture;
void main () {
float L = texture2D(u_pressure_texture, vL).x;
float R = texture2D(u_pressure_texture, vR).x;
float T = texture2D(u_pressure_texture, vT).x;
float B = texture2D(u_pressure_texture, vB).x;
vec2 velocity = texture2D(u_velocity_texture, vUv).xy;
velocity.xy -= vec2(R - L, T - B);
gl_FragColor = vec4(velocity, 0.0, 1.0);
}
`;
const POINT_SHADER = `
precision highp float;
precision highp sampler2D;
varying vec2 vUv;
uniform sampler2D u_input_texture;
uniform float u_ratio;
uniform vec3 u_point_value;
uniform vec2 u_point;
uniform float u_point_size;
uniform sampler2D u_influence_texture;
void main () {
vec2 p = vUv - u_point.xy;
p.x *= u_ratio;
vec3 splat = pow(3.0, -dot(p, p) / u_point_size) * u_point_value;
float influence = texture2D(u_influence_texture, vec2(vUv.x, 1.0 - vUv.y)).r;
splat *= (0.7 + 0.2 * influence);
vec3 base = texture2D(u_input_texture, vUv).xyz;
gl_FragColor = vec4(base + splat, 1.0);
}
`;
const DISPLAY_VERTEX_SHADER = `
varying vec2 vUv;
void main () {
vUv = uv;
gl_Position = vec4(position.xy, 0.0, 1.0);
}
`;
const DISPLAY_FRAGMENT_SHADER = `
precision highp float;
varying vec2 vUv;
uniform sampler2D u_colorTexture;
uniform sampler2D u_fluidTexture;
float luma(vec3 color) {
return dot(color, vec3(0.299, 0.587, 0.114));
}
void main () {
vec4 image = texture2D(u_colorTexture, vUv);
vec3 gray = vec3(luma(image.rgb));
vec3 fluid = texture2D(u_fluidTexture, vUv).rgb;
vec3 shifted = vec3(
texture2D(u_fluidTexture, vUv + vec2(0.0006, 0.0)).r,
texture2D(u_fluidTexture, vUv).g,
texture2D(u_fluidTexture, vUv - vec2(0.0006, 0.0)).b
);
float mask = clamp(length(fluid) * 1.5, 0.0, 1.0);
mask = pow(mask, 0.6);
mask = smoothstep(0.05, 0.8, mask);
vec3 fluidColor = mix(fluid, shifted, 3.0);
fluidColor = normalize(fluidColor + 0.1) * 0.8;
vec3 color = mix(gray, image.rgb + fluidColor * 0.4, mask);
gl_FragColor = vec4(color, image.a);
}
`;
const requestFloatSupport = (renderer: THREE.WebGLRenderer) => {
const gl = renderer.getContext();
const extensions = [
"OES_texture_float",
"OES_texture_float_linear",
"OES_texture_half_float",
"OES_texture_half_float_linear",
"WEBGL_color_buffer_float",
"EXT_color_buffer_float",
"EXT_color_buffer_half_float",
];
extensions.forEach((name) => {
if (name && gl.getExtension(name) === null) {
return;
}
});
};
const getSupportedTextureType = (renderer: THREE.WebGLRenderer) => {
const gl = renderer.getContext();
const isWebGL2 = renderer.capabilities.isWebGL2;
if (isWebGL2) {
if (
gl.getExtension("EXT_color_buffer_float") ||
gl.getExtension("WEBGL_color_buffer_float")
) {
return THREE.FloatType;
}
if (gl.getExtension("EXT_color_buffer_half_float")) {
return THREE.HalfFloatType;
}
} else {
if (
gl.getExtension("OES_texture_float") &&
gl.getExtension("WEBGL_color_buffer_float")
) {
return THREE.FloatType;
}
if (
gl.getExtension("OES_texture_half_float") &&
gl.getExtension("EXT_color_buffer_half_float")
) {
return THREE.HalfFloatType;
}
}
return THREE.UnsignedByteType;
};
const createRenderTarget = (
width: number,
height: number,
type: THREE.TextureDataType,
) => {
const target = new THREE.WebGLRenderTarget(width, height, {
type,
format: THREE.RGBAFormat,
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
depthBuffer: false,
stencilBuffer: false,
});
target.texture.wrapS = THREE.ClampToEdgeWrapping;
target.texture.wrapT = THREE.ClampToEdgeWrapping;
target.texture.generateMipmaps = false;
return target;
};
class DoubleFBO {
read: THREE.WebGLRenderTarget;
write: THREE.WebGLRenderTarget;
constructor(width: number, height: number, type: THREE.TextureDataType) {
this.read = createRenderTarget(width, height, type);
this.write = createRenderTarget(width, height, type);
}
swap() {
const temp = this.read;
this.read = this.write;
this.write = temp;
}
dispose() {
this.read.dispose();
this.write.dispose();
}
}
class FluidSimulation {
private renderer: THREE.WebGLRenderer;
private scene: THREE.Scene;
private camera: THREE.OrthographicCamera;
private quad: THREE.Mesh<THREE.PlaneGeometry, THREE.RawShaderMaterial>;
private geometry: THREE.PlaneGeometry;
private advectionMaterial: THREE.RawShaderMaterial;
private divergenceMaterial: THREE.RawShaderMaterial;
private pressureMaterial: THREE.RawShaderMaterial;
private gradientMaterial: THREE.RawShaderMaterial;
private pointMaterial: THREE.RawShaderMaterial;
private defaultInfluenceTexture: THREE.DataTexture;
private influenceTexture: THREE.Texture;
private ownsInfluenceTexture: boolean;
private velocity: DoubleFBO;
private dye: DoubleFBO;
private divergence: THREE.WebGLRenderTarget;
private pressure: DoubleFBO;
private pointerColor: THREE.Vector3;
private aspect: number;
constructor(
renderer: THREE.WebGLRenderer,
width: number,
height: number,
pointerColor: THREE.Vector3,
) {
this.renderer = renderer;
this.pointerColor = pointerColor.clone();
requestFloatSupport(renderer);
this.defaultInfluenceTexture = createDefaultInfluenceTexture();
this.influenceTexture = this.defaultInfluenceTexture;
this.ownsInfluenceTexture = false;
const type = getSupportedTextureType(renderer);
this.scene = new THREE.Scene();
this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
this.geometry = new THREE.PlaneGeometry(2, 2);
this.advectionMaterial = this.createAdvectionMaterial(width, height);
this.divergenceMaterial = this.createDivergenceMaterial(width, height);
this.pressureMaterial = this.createPressureMaterial(width, height);
this.gradientMaterial = this.createGradientMaterial(width, height);
this.pointMaterial = this.createPointMaterial(width, height);
this.updateInfluenceUniforms(this.influenceTexture, false);
this.quad = new THREE.Mesh(this.geometry, this.advectionMaterial);
this.quad.frustumCulled = false;
this.scene.add(this.quad);
this.velocity = new DoubleFBO(width, height, type);
this.dye = new DoubleFBO(width, height, type);
this.pressure = new DoubleFBO(width, height, type);
this.divergence = createRenderTarget(width, height, type);
this.aspect = width / Math.max(height, 1);
this.updateTexelUniforms(width, height);
}
private createAdvectionMaterial(width: number, height: number) {
return new THREE.RawShaderMaterial({
vertexShader: BASE_VERTEX_SHADER,
fragmentShader: ADVECTION_SHADER,
uniforms: {
u_velocity_texture: { value: null },
u_input_texture: { value: null },
u_influence_texture: { value: this.influenceTexture },
u_texel: { value: new THREE.Vector2(1 / width, 1 / height) },
u_dt: { value: 0.016 },
u_use_influence: { value: 0.0 },
},
depthTest: false,
depthWrite: false,
blending: THREE.NoBlending,
});
}
private createDivergenceMaterial(width: number, height: number) {
return new THREE.RawShaderMaterial({
vertexShader: BASE_VERTEX_SHADER,
fragmentShader: DIVERGENCE_SHADER,
uniforms: {
u_velocity_texture: { value: null },
u_texel: { value: new THREE.Vector2(1 / width, 1 / height) },
},
depthTest: false,
depthWrite: false,
blending: THREE.NoBlending,
});
}
private createPressureMaterial(width: number, height: number) {
return new THREE.RawShaderMaterial({
vertexShader: BASE_VERTEX_SHADER,
fragmentShader: PRESSURE_SHADER,
uniforms: {
u_pressure_texture: { value: null },
u_divergence_texture: { value: null },
u_influence_texture: { value: this.influenceTexture },
u_texel: { value: new THREE.Vector2(1 / width, 1 / height) },
},
depthTest: false,
depthWrite: false,
blending: THREE.NoBlending,
});
}
private createGradientMaterial(width: number, height: number) {
return new THREE.RawShaderMaterial({
vertexShader: BASE_VERTEX_SHADER,
fragmentShader: GRADIENT_SHADER,
uniforms: {
u_pressure_texture: { value: null },
u_velocity_texture: { value: null },
u_influence_texture: { value: this.influenceTexture },
u_texel: { value: new THREE.Vector2(1 / width, 1 / height) },
},
depthTest: false,
depthWrite: false,
blending: THREE.NoBlending,
});
}
private createPointMaterial(width: number, height: number) {
return new THREE.RawShaderMaterial({
vertexShader: BASE_VERTEX_SHADER,
fragmentShader: POINT_SHADER,
uniforms: {
u_input_texture: { value: null },
u_ratio: { value: width / Math.max(height, 1) },
u_point_value: { value: new THREE.Vector3() },
u_point: { value: new THREE.Vector2(0.5, 0.5) },
u_point_size: { value: 0.02 },
u_influence_texture: { value: this.influenceTexture },
u_texel: { value: new THREE.Vector2(1 / width, 1 / height) },
},
depthTest: false,
depthWrite: false,
blending: THREE.NoBlending,
});
}
private updateInfluenceUniforms(
texture: THREE.Texture,
enableInfluence: boolean,
) {
this.advectionMaterial.uniforms.u_influence_texture.value = texture;
this.pressureMaterial.uniforms.u_influence_texture.value = texture;
this.gradientMaterial.uniforms.u_influence_texture.value = texture;
this.pointMaterial.uniforms.u_influence_texture.value = texture;
this.advectionMaterial.uniforms.u_use_influence.value = enableInfluence
? 1.0
: 0.0;
}
setInfluenceTexture(texture: THREE.Texture | null, ownsTexture = false) {
if (
this.ownsInfluenceTexture &&
this.influenceTexture &&
this.influenceTexture !== this.defaultInfluenceTexture
) {
this.influenceTexture.dispose();
}
if (texture) {
this.influenceTexture = texture;
this.ownsInfluenceTexture = ownsTexture;
this.updateInfluenceUniforms(texture, true);
} else {
this.influenceTexture = this.defaultInfluenceTexture;
this.ownsInfluenceTexture = false;
this.updateInfluenceUniforms(this.defaultInfluenceTexture, false);
}
}
private updateTexelUniforms(width: number, height: number) {
const texel = new THREE.Vector2(1 / width, 1 / height);
(this.advectionMaterial.uniforms.u_texel.value as THREE.Vector2).copy(
texel,
);
(this.divergenceMaterial.uniforms.u_texel.value as THREE.Vector2).copy(
texel,
);
(this.pressureMaterial.uniforms.u_texel.value as THREE.Vector2).copy(texel);
(this.gradientMaterial.uniforms.u_texel.value as THREE.Vector2).copy(texel);
(this.pointMaterial.uniforms.u_texel.value as THREE.Vector2).copy(texel);
this.pointMaterial.uniforms.u_ratio.value = width / Math.max(height, 1);
}
private blit(
material: THREE.RawShaderMaterial,
target: THREE.WebGLRenderTarget | null,
) {
this.quad.material = material;
this.renderer.setRenderTarget(target);
this.renderer.render(this.scene, this.camera);
this.renderer.setRenderTarget(null);
}
private splat(pointer: PointerState) {
const material = this.pointMaterial;
material.uniforms.u_point.value.copy(pointer.uv);
material.uniforms.u_ratio.value = this.aspect;
const deltaLength = pointer.delta.length();
if (deltaLength <= 0.0) {
return;
}
const velocityValue = material.uniforms.u_point_value
.value as THREE.Vector3;
velocityValue.set(pointer.delta.x, pointer.delta.y, 0.0);
velocityValue.multiplyScalar(10.0);
material.uniforms.u_point_size.value = 0.005;
material.uniforms.u_input_texture.value = this.velocity.read.texture;
this.blit(material, this.velocity.write);
this.velocity.swap();
const colorValue = material.uniforms.u_point_value.value as THREE.Vector3;
const colorIntensity = THREE.MathUtils.clamp(deltaLength * 20.0, 0.0, 1.0);
if (colorIntensity <= 0.0) {
return;
}
colorValue.copy(this.pointerColor).multiplyScalar(300 * colorIntensity);
material.uniforms.u_point_size.value = 0.0025;
material.uniforms.u_input_texture.value = this.dye.read.texture;
this.blit(material, this.dye.write);
this.dye.swap();
}
update(delta: number, pointer: PointerState) {
if (pointer?.moved) {
this.splat(pointer);
}
this.advectionMaterial.uniforms.u_dt.value = delta;
this.advectionMaterial.uniforms.u_velocity_texture.value =
this.velocity.read.texture;
this.advectionMaterial.uniforms.u_input_texture.value =
this.velocity.read.texture;
this.blit(this.advectionMaterial, this.velocity.write);
this.velocity.swap();
this.advectionMaterial.uniforms.u_velocity_texture.value =
this.velocity.read.texture;
this.advectionMaterial.uniforms.u_input_texture.value =
this.dye.read.texture;
this.blit(this.advectionMaterial, this.dye.write);
this.dye.swap();
this.divergenceMaterial.uniforms.u_velocity_texture.value =
this.velocity.read.texture;
this.blit(this.divergenceMaterial, this.divergence);
for (let i = 0; i < 10; i++) {
this.pressureMaterial.uniforms.u_pressure_texture.value =
this.pressure.read.texture;
this.pressureMaterial.uniforms.u_divergence_texture.value =
this.divergence.texture;
this.blit(this.pressureMaterial, this.pressure.write);
this.pressure.swap();
}
this.gradientMaterial.uniforms.u_pressure_texture.value =
this.pressure.read.texture;
this.gradientMaterial.uniforms.u_velocity_texture.value =
this.velocity.read.texture;
this.blit(this.gradientMaterial, this.velocity.write);
this.velocity.swap();
}
setPointerColor(color: THREE.Vector3) {
this.pointerColor.copy(color);
}
getTexture() {
return this.dye.read.texture;
}
dispose() {
this.geometry.dispose();
this.advectionMaterial.dispose();
this.divergenceMaterial.dispose();
this.pressureMaterial.dispose();
this.gradientMaterial.dispose();
this.pointMaterial.dispose();
this.velocity.dispose();
this.dye.dispose();
this.pressure.dispose();
this.divergence.dispose();
if (
this.ownsInfluenceTexture &&
this.influenceTexture &&
this.influenceTexture !== this.defaultInfluenceTexture
) {
this.influenceTexture.dispose();
}
this.defaultInfluenceTexture.dispose();
}
}
const FluidRevealPlane = ({
imageUrl,
pointerColor,
}: {
imageUrl: string;
pointerColor: string;
}) => {
const colorVector = useMemo(() => {
const color = new THREE.Color(pointerColor);
return new THREE.Vector3(color.r, color.g, color.b);
}, [pointerColor]);
const pointerColorRef = useRef(colorVector.clone());
const texture = useLoader(THREE.TextureLoader, imageUrl);
const { gl, size } = useThree();
const materialRef = useRef<THREE.ShaderMaterial>(null);
const simulationRef = useRef<FluidSimulation | null>(null);
const pointerRef = useRef<PointerState>({
uv: new THREE.Vector2(0.5, 0.5),
prev: new THREE.Vector2(0.5, 0.5),
delta: new THREE.Vector2(),
instant: new THREE.Vector2(),
moved: false,
target: new THREE.Vector2(0.5, 0.5),
});
useEffect(() => {
pointerColorRef.current.copy(colorVector);
simulationRef.current?.setPointerColor(colorVector);
}, [colorVector]);
const uniforms = useMemo(
() => ({
u_colorTexture: { value: null as THREE.Texture | null },
u_fluidTexture: { value: null as THREE.Texture | null },
}),
[],
);
const uniformsRef = useRef(uniforms);
const textureRef = useRef(texture);
useEffect(() => {
uniformsRef.current = uniforms;
}, [uniforms]);
useEffect(() => {
textureRef.current = texture;
const currentTexture = textureRef.current;
const currentUniforms = uniformsRef.current;
currentUniforms.u_colorTexture.value = currentTexture;
currentTexture.wrapS = THREE.ClampToEdgeWrapping;
currentTexture.wrapT = THREE.ClampToEdgeWrapping;
currentTexture.minFilter = THREE.LinearFilter;
currentTexture.magFilter = THREE.LinearFilter;
currentTexture.generateMipmaps = false;
currentTexture.colorSpace = THREE.LinearSRGBColorSpace;
currentTexture.needsUpdate = true;
if (materialRef.current) {
materialRef.current.uniforms.u_colorTexture.value = currentTexture;
}
}, [texture]);
useEffect(() => {
const width = Math.max(1, Math.floor(size.width * gl.getPixelRatio()));
const height = Math.max(1, Math.floor(size.height * gl.getPixelRatio()));
const previous = simulationRef.current;
const simulation = new FluidSimulation(
gl,
width,
height,
pointerColorRef.current,
);
simulationRef.current = simulation;
previous?.dispose();
return () => {
simulation.dispose();
if (simulationRef.current === simulation) {
simulationRef.current = null;
}
};
}, [gl, size.width, size.height]);
useEffect(() => {
const simulation = simulationRef.current;
if (!simulation) {
return;
}
if (!texture.image) {
simulation.setInfluenceTexture(null);
return;
}
const pixelRatio = gl.getPixelRatio();
const targetWidth = Math.max(1, Math.floor(size.width * pixelRatio));
const targetHeight = Math.max(1, Math.floor(size.height * pixelRatio));
const influenceTexture = createLuminanceInfluenceTexture(
texture,
targetWidth,
targetHeight,
);
if (!influenceTexture) {
simulation.setInfluenceTexture(null);
return;
}
simulation.setInfluenceTexture(influenceTexture, true);
}, [gl, texture, size.width, size.height]);
useEffect(() => {
const element = gl.domElement;
const updatePointer = (event: PointerEvent) => {
const rect = element.getBoundingClientRect();
const x = (event.clientX - rect.left) / rect.width;
const y = (event.clientY - rect.top) / rect.height;
const pointer = pointerRef.current;
pointer.target.set(
THREE.MathUtils.clamp(x, 0, 1),
THREE.MathUtils.clamp(1 - y, 0, 1),
);
pointer.moved = true;
};
const handleLeave = () => {
const pointer = pointerRef.current;
pointer.target.copy(pointer.uv);
pointer.moved = false;
pointer.delta.set(0, 0);
pointer.instant.set(0, 0);
};
element.addEventListener("pointermove", updatePointer);
element.addEventListener("pointerdown", updatePointer);
element.addEventListener("pointerenter", updatePointer);
element.addEventListener("pointerleave", handleLeave);
return () => {
element.removeEventListener("pointermove", updatePointer);
element.removeEventListener("pointerdown", updatePointer);
element.removeEventListener("pointerenter", updatePointer);
element.removeEventListener("pointerleave", handleLeave);
};
}, [gl]);
useFrame(() => {
const simulation = simulationRef.current;
const material = materialRef.current;
if (!simulation || !material) return;
const pointer = pointerRef.current;
pointer.prev.copy(pointer.uv);
const positionLerp = 1 - Math.exp((-1 / 60) * POINTER_LERP_SPEED);
if (
pointer.target.distanceToSquared(pointer.uv) <=
POINTER_POSITION_EPSILON * POINTER_POSITION_EPSILON
) {
pointer.uv.copy(pointer.target);
} else {
pointer.uv.lerp(pointer.target, positionLerp);
}
pointer.instant.copy(pointer.uv).sub(pointer.prev);
const velocityLerp = 1 - Math.exp((-1 / 60) * POINTER_VELOCITY_LERP);
pointer.delta.lerp(pointer.instant, velocityLerp);
const deltaLength = pointer.delta.length();
const hasMovement = deltaLength > POINTER_MIN_DELTA;
pointer.moved = hasMovement;
if (!hasMovement) {
pointer.delta.set(0, 0);
}
const FIXED_DT = 1 / 60;
simulation.update(FIXED_DT, pointer);
const textureRef = simulation.getTexture();
material.uniforms.u_fluidTexture.value = textureRef;
uniformsRef.current.u_fluidTexture.value = textureRef;
});
return (
<mesh frustumCulled={false}>
<planeGeometry args={[2, 2]} />
<shaderMaterial
ref={materialRef}
uniforms={uniforms}
vertexShader={DISPLAY_VERTEX_SHADER}
fragmentShader={DISPLAY_FRAGMENT_SHADER}
depthTest={false}
depthWrite={false}
toneMapped={false}
/>
</mesh>
);
};
const Shader17 = ({
className,
imageUrl = "/block-media/shadcnblocks/block/background27/bg-photo.png",
pointerColor = "#ffffff",
}: Background27Props) => {
return (
<section className={cn(className, "absolute inset-0 h-screen w-full")}>
<Canvas
gl={{ powerPreference: "high-performance", antialias: false }}
frameloop="always"
className={cn(className, "absolute inset-0 h-screen w-full")}
orthographic
camera={{ position: [0, 0, 1], near: 0.01, far: 10, zoom: 1 }}
dpr={[1, 2]}
>
<FluidRevealPlane imageUrl={imageUrl} pointerColor={pointerColor} />
</Canvas>
</section>
);
};
export { Shader17 };