290 lines
8.3 KiB
TypeScript
290 lines
8.3 KiB
TypeScript
"use client";
|
|
|
|
import { Canvas, useFrame, useThree } from "@react-three/fiber";
|
|
import { startTransition, useEffect, useMemo, useRef, useState } from "react";
|
|
import * as THREE from "three";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface ShaderPlaneProps {
|
|
vertexShader: string;
|
|
fragmentShader: string;
|
|
uniforms: { [key: string]: { value: unknown } };
|
|
}
|
|
|
|
const ShaderPlane = ({
|
|
vertexShader,
|
|
fragmentShader,
|
|
uniforms,
|
|
}: ShaderPlaneProps) => {
|
|
const meshRef = useRef<THREE.Mesh>(null);
|
|
const { size } = useThree();
|
|
|
|
useFrame((state) => {
|
|
if (meshRef.current) {
|
|
const material = meshRef.current.material as THREE.ShaderMaterial;
|
|
material.uniforms.u_time.value = state.clock.elapsedTime;
|
|
material.uniforms.u_resolution.value.set(size.width, size.height, 1.0);
|
|
}
|
|
});
|
|
|
|
return (
|
|
<mesh ref={meshRef}>
|
|
<planeGeometry args={[2, 2]} />
|
|
<shaderMaterial
|
|
vertexShader={vertexShader}
|
|
fragmentShader={fragmentShader}
|
|
uniforms={uniforms}
|
|
side={THREE.FrontSide}
|
|
depthTest={false}
|
|
depthWrite={false}
|
|
/>
|
|
</mesh>
|
|
);
|
|
};
|
|
|
|
interface Background28Props {
|
|
className?: string;
|
|
channel0Url?: string;
|
|
channel1Url?: string;
|
|
channel2Url?: string;
|
|
channel3Url?: string;
|
|
|
|
Silhouette_height?: number;
|
|
Vertical_drift?: number;
|
|
Warp_tex_scale?: number;
|
|
Edge_sharpness?: number;
|
|
Tiling_rate?: number;
|
|
}
|
|
|
|
export const Shader18 = ({
|
|
className = "absolute inset-0 w-full h-screen bg-black",
|
|
|
|
channel0Url = "/block-media/shadcnblocks/block/background28/noise1.jpg",
|
|
channel1Url = "/block-media/shadcnblocks/block/background28/noise1.jpg",
|
|
channel2Url = "/block-media/shadcnblocks/block/background28/noise2.png",
|
|
channel3Url = "/block-media/shadcnblocks/block/background28/noise2.png",
|
|
|
|
Silhouette_height = 0.45,
|
|
Vertical_drift = 0.57,
|
|
Warp_tex_scale = 2.9,
|
|
Edge_sharpness = 0.15,
|
|
Tiling_rate = 0.6,
|
|
}: Background28Props) => {
|
|
const [textures, setTextures] = useState<{
|
|
t0: THREE.Texture | null;
|
|
t1: THREE.Texture | null;
|
|
t2: THREE.Texture | null;
|
|
t3: THREE.Texture | null;
|
|
}>({
|
|
t0: null,
|
|
t1: null,
|
|
t2: null,
|
|
t3: null,
|
|
});
|
|
|
|
useEffect(() => {
|
|
const loader = new THREE.TextureLoader();
|
|
const t0 = loader.load(channel0Url);
|
|
const t1 = loader.load(channel1Url);
|
|
const t2 = loader.load(channel2Url);
|
|
const t3 = loader.load(channel3Url);
|
|
|
|
[t0, t1, t2, t3].forEach((t) => {
|
|
t.wrapS = t.wrapT = THREE.RepeatWrapping;
|
|
});
|
|
|
|
startTransition(() => {
|
|
setTextures({ t0, t1, t2, t3 });
|
|
});
|
|
}, [channel0Url, channel1Url, channel2Url, channel3Url]);
|
|
|
|
const vertexShader = `
|
|
varying vec2 vUv;
|
|
void main() {
|
|
vUv = uv;
|
|
gl_Position = vec4(position, 1.0);
|
|
}
|
|
`;
|
|
|
|
const fragmentShader = `
|
|
precision highp float;
|
|
|
|
varying vec2 vUv;
|
|
uniform float u_time;
|
|
uniform vec3 u_resolution;
|
|
|
|
uniform sampler2D u_channel0;
|
|
uniform sampler2D u_channel1;
|
|
uniform sampler2D u_channel2;
|
|
uniform sampler2D u_channel3;
|
|
|
|
uniform float u_silhouette_height;
|
|
uniform float u_vertical_drift;
|
|
uniform float u_warp_tex_scale;
|
|
uniform float u_edge_sharpness;
|
|
uniform float u_tiling_rate;
|
|
|
|
#define ENABLE_MASKING
|
|
#define ENABLE_SOFT_EDGE
|
|
#define ENABLE_DETAIL
|
|
#define ENABLE_WARP
|
|
#define ENABLE_COLOR
|
|
|
|
const int NUM_PARABOLAS = 26;
|
|
const float SPEED = 0.0025;
|
|
const float SCROLL_SPEED = -0.05;
|
|
const float HORIZONTAL_DRIFT = 0.1;
|
|
const float CURVATURE_BASE = 0.005;
|
|
const float CURVATURE_DRIFT = 0.02;
|
|
const float WARP_STRENGTH = 0.025;
|
|
|
|
vec3 flame_color_ramp(float val) {
|
|
vec3 color_a = 1.2 * vec3(1.2, 0.1, 0.2);
|
|
vec3 color_b = 1.8 * vec3(1.0, 0.8, 0.5);
|
|
vec3 color_c = 0.95 * vec3(0.25, 0.25, 0.4);
|
|
vec3 color_d = 0.5 * vec3(0.35, 0.2, 0.4);
|
|
|
|
float pos_a = 0.35;
|
|
float pos_b = 0.65;
|
|
|
|
if (val < pos_a)
|
|
return mix(color_a, color_b, val / pos_a);
|
|
else if (val < pos_b)
|
|
return mix(color_b, color_c, (val - pos_a) / (pos_b - pos_a));
|
|
return mix(color_c, color_d, (val - pos_b) / (1.0 - pos_b));
|
|
}
|
|
|
|
vec2 warp(vec2 uv, vec2 warp_uvs, float strength, sampler2D tex) {
|
|
vec4 w = texture2D(tex, warp_uvs);
|
|
return uv + strength * w.xy;
|
|
}
|
|
|
|
vec3 get_focus(float i) {
|
|
vec4 r = texture2D(u_channel0, vec2(i, u_time * SPEED * 0.1));
|
|
float x = i + mix(-HORIZONTAL_DRIFT, HORIZONTAL_DRIFT, r.r);
|
|
float y = (1.0 - u_silhouette_height) + mix(-u_vertical_drift, u_vertical_drift, r.g);
|
|
vec2 focus = vec2(x, y);
|
|
float directrix = y + CURVATURE_BASE + mix(0.0, CURVATURE_DRIFT, r.b);
|
|
return vec3(focus, directrix);
|
|
}
|
|
|
|
vec4 stretched_scrolling_noise(vec2 uv) {
|
|
vec2 scrolled_uvs = vec2(0.85 * uv.s, 0.2 * uv.t + u_time * 0.1 * SCROLL_SPEED);
|
|
vec4 n = texture2D(u_channel1, scrolled_uvs);
|
|
return 1.0 - n;
|
|
}
|
|
|
|
vec4 contrast(vec4 in_rgb, float c) {
|
|
return ((in_rgb - 0.5) * c) + 0.5;
|
|
}
|
|
|
|
vec3 desaturate(vec3 in_rgb) {
|
|
const vec3 W = vec3(0.2125, 0.7154, 0.0721);
|
|
float luminance = dot(in_rgb, W);
|
|
return vec3(luminance);
|
|
}
|
|
|
|
float quadratic_signed_distance(vec2 uv, vec2 focus, float c) {
|
|
float x = uv.s;
|
|
float a = (x - focus.x);
|
|
float a2 = a * a;
|
|
float b2 = focus.y * focus.y;
|
|
float c2 = c * c;
|
|
float denominator = 2.0 * (focus.y - c);
|
|
float y = (a2 + b2 - c2) / denominator;
|
|
return (1.0 - uv.y) - y;
|
|
}
|
|
|
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
vec2 uv = fragCoord.xy / u_resolution.xy;
|
|
|
|
float tilt_strength = 0.35;
|
|
vec2 tilt_dir = vec2(-1.0, 0.0);
|
|
float wind_strength = tilt_strength * pow(uv.y, 2.);
|
|
uv += wind_strength * tilt_dir;
|
|
|
|
uv *= vec2(u_tiling_rate, 1.0);
|
|
|
|
float Warp_strength_dynamic = mix(4.0 * WARP_STRENGTH, 0.4 * WARP_STRENGTH, pow(1.0 - uv.y, 0.2));
|
|
Warp_strength_dynamic = clamp(Warp_strength_dynamic, 0.0, 0.2);
|
|
|
|
vec2 warped_uv = uv;
|
|
#ifdef ENABLE_WARP
|
|
vec2 warp_tex_uvs = vec2(u_warp_tex_scale * uv.s + 0.0005 * u_time * 0.1, 0.25 * uv.t - 0.8 * u_time);
|
|
warped_uv = warp(uv, warp_tex_uvs, Warp_strength_dynamic, u_channel2);
|
|
#endif
|
|
|
|
float silhouette_mask = 1.0;
|
|
float edge_gradient = 1.0;
|
|
|
|
for (int i = 0; i < NUM_PARABOLAS; i++) {
|
|
float seed = float(i) / float(NUM_PARABOLAS - 1);
|
|
vec3 focus = get_focus(seed);
|
|
float d1 = quadratic_signed_distance(warped_uv, focus.xy, focus.z);
|
|
float vertical_dist = u_edge_sharpness * d1;
|
|
if (vertical_dist < 0.0)
|
|
silhouette_mask = min(silhouette_mask, 0.0);
|
|
else
|
|
edge_gradient = min(edge_gradient, vertical_dist);
|
|
}
|
|
|
|
float c = mix(1.0, 5.0, 1.0 - edge_gradient);
|
|
vec2 scroll_uvs = warped_uv;
|
|
scroll_uvs.y = mix(2.0 * scroll_uvs.y, 0.7 * scroll_uvs.y, pow(uv.y, 0.5));
|
|
vec4 scroll_tex = contrast(stretched_scrolling_noise(scroll_uvs), c);
|
|
scroll_tex = vec4(desaturate(scroll_tex.rgb), 1.0);
|
|
|
|
vec4 plasma_color = vec4(flame_color_ramp(edge_gradient), 1.0);
|
|
fragColor = plasma_color * silhouette_mask;
|
|
fragColor *= edge_gradient;
|
|
fragColor *= scroll_tex;
|
|
}
|
|
|
|
void main() {
|
|
vec4 fragColor;
|
|
vec2 fragCoord = vUv * u_resolution.xy;
|
|
mainImage(fragColor, fragCoord);
|
|
gl_FragColor = fragColor;
|
|
}
|
|
`;
|
|
|
|
const uniforms = useMemo(
|
|
() => ({
|
|
u_time: { value: 0 },
|
|
u_resolution: { value: new THREE.Vector3(1, 1, 1) },
|
|
u_channel0: { value: textures.t0 },
|
|
u_channel1: { value: textures.t1 },
|
|
u_channel2: { value: textures.t2 },
|
|
u_channel3: { value: textures.t3 },
|
|
u_silhouette_height: { value: Silhouette_height },
|
|
u_vertical_drift: { value: Vertical_drift },
|
|
u_warp_tex_scale: { value: Warp_tex_scale },
|
|
u_edge_sharpness: { value: Edge_sharpness },
|
|
u_tiling_rate: { value: Tiling_rate },
|
|
}),
|
|
[
|
|
textures,
|
|
Silhouette_height,
|
|
Vertical_drift,
|
|
Warp_tex_scale,
|
|
Edge_sharpness,
|
|
Tiling_rate,
|
|
],
|
|
);
|
|
|
|
return (
|
|
<section className={cn(className, "absolute inset-0 h-screen w-full")}>
|
|
<Canvas>
|
|
{textures.t0 && (
|
|
<ShaderPlane
|
|
vertexShader={vertexShader}
|
|
fragmentShader={fragmentShader}
|
|
uniforms={uniforms}
|
|
/>
|
|
)}
|
|
</Canvas>
|
|
</section>
|
|
);
|
|
};
|