대시보드 중간 커밋
This commit is contained in:
244
components/ui/shader-background.tsx
Normal file
244
components/ui/shader-background.tsx
Normal file
@@ -0,0 +1,244 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface ShaderBackgroundProps {
|
||||
className?: string;
|
||||
opacity?: number;
|
||||
}
|
||||
|
||||
const VS_SOURCE = `
|
||||
attribute vec4 aVertexPosition;
|
||||
void main() {
|
||||
gl_Position = aVertexPosition;
|
||||
}
|
||||
`;
|
||||
|
||||
const FS_SOURCE = `
|
||||
precision highp float;
|
||||
uniform vec2 iResolution;
|
||||
uniform float iTime;
|
||||
|
||||
const float overallSpeed = 0.2;
|
||||
const float gridSmoothWidth = 0.015;
|
||||
const float axisWidth = 0.05;
|
||||
const float majorLineWidth = 0.025;
|
||||
const float minorLineWidth = 0.0125;
|
||||
const float majorLineFrequency = 5.0;
|
||||
const float minorLineFrequency = 1.0;
|
||||
const vec4 gridColor = vec4(0.5);
|
||||
const float scale = 5.0;
|
||||
const vec4 lineColor = vec4(0.4, 0.2, 0.8, 1.0);
|
||||
const float minLineWidth = 0.01;
|
||||
const float maxLineWidth = 0.2;
|
||||
const float lineSpeed = 1.0 * overallSpeed;
|
||||
const float lineAmplitude = 1.0;
|
||||
const float lineFrequency = 0.2;
|
||||
const float warpSpeed = 0.2 * overallSpeed;
|
||||
const float warpFrequency = 0.5;
|
||||
const float warpAmplitude = 1.0;
|
||||
const float offsetFrequency = 0.5;
|
||||
const float offsetSpeed = 1.33 * overallSpeed;
|
||||
const float minOffsetSpread = 0.6;
|
||||
const float maxOffsetSpread = 2.0;
|
||||
const int linesPerGroup = 16;
|
||||
|
||||
#define drawCircle(pos, radius, coord) smoothstep(radius + gridSmoothWidth, radius, length(coord - (pos)))
|
||||
#define drawSmoothLine(pos, halfWidth, t) smoothstep(halfWidth, 0.0, abs(pos - (t)))
|
||||
#define drawCrispLine(pos, halfWidth, t) smoothstep(halfWidth + gridSmoothWidth, halfWidth, abs(pos - (t)))
|
||||
#define drawPeriodicLine(freq, width, t) drawCrispLine(freq / 2.0, width, abs(mod(t, freq) - (freq) / 2.0))
|
||||
|
||||
float drawGridLines(float axis) {
|
||||
return drawCrispLine(0.0, axisWidth, axis)
|
||||
+ drawPeriodicLine(majorLineFrequency, majorLineWidth, axis)
|
||||
+ drawPeriodicLine(minorLineFrequency, minorLineWidth, axis);
|
||||
}
|
||||
|
||||
float drawGrid(vec2 space) {
|
||||
return min(1.0, drawGridLines(space.x) + drawGridLines(space.y));
|
||||
}
|
||||
|
||||
float random(float t) {
|
||||
return (cos(t) + cos(t * 1.3 + 1.3) + cos(t * 1.4 + 1.4)) / 3.0;
|
||||
}
|
||||
|
||||
float getPlasmaY(float x, float horizontalFade, float offset) {
|
||||
return random(x * lineFrequency + iTime * lineSpeed) * horizontalFade * lineAmplitude + offset;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 fragCoord = gl_FragCoord.xy;
|
||||
vec4 fragColor;
|
||||
vec2 uv = fragCoord.xy / iResolution.xy;
|
||||
vec2 space = (fragCoord - iResolution.xy / 2.0) / iResolution.x * 2.0 * scale;
|
||||
|
||||
float horizontalFade = 1.0 - (cos(uv.x * 6.28) * 0.5 + 0.5);
|
||||
float verticalFade = 1.0 - (cos(uv.y * 6.28) * 0.5 + 0.5);
|
||||
|
||||
space.y += random(space.x * warpFrequency + iTime * warpSpeed) * warpAmplitude * (0.5 + horizontalFade);
|
||||
space.x += random(space.y * warpFrequency + iTime * warpSpeed + 2.0) * warpAmplitude * horizontalFade;
|
||||
|
||||
vec4 lines = vec4(0.0);
|
||||
vec4 bgColor1 = vec4(0.1, 0.1, 0.3, 1.0);
|
||||
vec4 bgColor2 = vec4(0.3, 0.1, 0.5, 1.0);
|
||||
|
||||
for(int l = 0; l < linesPerGroup; l++) {
|
||||
float normalizedLineIndex = float(l) / float(linesPerGroup);
|
||||
float offsetTime = iTime * offsetSpeed;
|
||||
float offsetPosition = float(l) + space.x * offsetFrequency;
|
||||
float rand = random(offsetPosition + offsetTime) * 0.5 + 0.5;
|
||||
float halfWidth = mix(minLineWidth, maxLineWidth, rand * horizontalFade) / 2.0;
|
||||
float offset = random(offsetPosition + offsetTime * (1.0 + normalizedLineIndex)) * mix(minOffsetSpread, maxOffsetSpread, horizontalFade);
|
||||
float linePosition = getPlasmaY(space.x, horizontalFade, offset);
|
||||
float line = drawSmoothLine(linePosition, halfWidth, space.y) / 2.0 + drawCrispLine(linePosition, halfWidth * 0.15, space.y);
|
||||
|
||||
float circleX = mod(float(l) + iTime * lineSpeed, 25.0) - 12.0;
|
||||
vec2 circlePosition = vec2(circleX, getPlasmaY(circleX, horizontalFade, offset));
|
||||
float circle = drawCircle(circlePosition, 0.01, space) * 4.0;
|
||||
|
||||
line = line + circle;
|
||||
lines += line * lineColor * rand;
|
||||
}
|
||||
|
||||
fragColor = mix(bgColor1, bgColor2, uv.x);
|
||||
fragColor *= verticalFade;
|
||||
fragColor.a = 1.0;
|
||||
fragColor += lines;
|
||||
|
||||
gl_FragColor = fragColor;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* @description Compile one shader source.
|
||||
* @see components/ui/shader-background.tsx ShaderBackground useEffect - WebGL init flow
|
||||
*/
|
||||
function loadShader(gl: WebGLRenderingContext, type: number, source: string) {
|
||||
const shader = gl.createShader(type);
|
||||
if (!shader) return null;
|
||||
|
||||
gl.shaderSource(shader, source);
|
||||
gl.compileShader(shader);
|
||||
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
console.error("Shader compile error:", gl.getShaderInfoLog(shader));
|
||||
gl.deleteShader(shader);
|
||||
return null;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Create and link WebGL shader program.
|
||||
* @see components/ui/shader-background.tsx ShaderBackground useEffect - shader program setup
|
||||
*/
|
||||
function initShaderProgram(gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string) {
|
||||
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vertexSource);
|
||||
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
|
||||
if (!vertexShader || !fragmentShader) return null;
|
||||
|
||||
const shaderProgram = gl.createProgram();
|
||||
if (!shaderProgram) return null;
|
||||
|
||||
gl.attachShader(shaderProgram, vertexShader);
|
||||
gl.attachShader(shaderProgram, fragmentShader);
|
||||
gl.linkProgram(shaderProgram);
|
||||
|
||||
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
||||
console.error("Shader program link error:", gl.getProgramInfoLog(shaderProgram));
|
||||
gl.deleteProgram(shaderProgram);
|
||||
return null;
|
||||
}
|
||||
|
||||
return shaderProgram;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Animated shader background canvas.
|
||||
* @param className Tailwind class for canvas.
|
||||
* @param opacity Canvas opacity.
|
||||
* @see https://21st.dev/community/components/thanh/shader-background/default
|
||||
*/
|
||||
const ShaderBackground = ({ className, opacity = 0.9 }: ShaderBackgroundProps) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const gl = canvas.getContext("webgl");
|
||||
if (!gl) {
|
||||
console.warn("WebGL not supported.");
|
||||
return;
|
||||
}
|
||||
|
||||
const shaderProgram = initShaderProgram(gl, VS_SOURCE, FS_SOURCE);
|
||||
if (!shaderProgram) return;
|
||||
|
||||
const positionBuffer = gl.createBuffer();
|
||||
if (!positionBuffer) return;
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
const positions = [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0];
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
|
||||
|
||||
const vertexPosition = gl.getAttribLocation(shaderProgram, "aVertexPosition");
|
||||
const resolution = gl.getUniformLocation(shaderProgram, "iResolution");
|
||||
const time = gl.getUniformLocation(shaderProgram, "iTime");
|
||||
|
||||
const resizeCanvas = () => {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const nextWidth = Math.floor(window.innerWidth * dpr);
|
||||
const nextHeight = Math.floor(window.innerHeight * dpr);
|
||||
canvas.width = nextWidth;
|
||||
canvas.height = nextHeight;
|
||||
gl.viewport(0, 0, nextWidth, nextHeight);
|
||||
};
|
||||
|
||||
window.addEventListener("resize", resizeCanvas);
|
||||
resizeCanvas();
|
||||
|
||||
const startTime = Date.now();
|
||||
let frameId = 0;
|
||||
|
||||
const render = () => {
|
||||
const currentTime = (Date.now() - startTime) / 1000;
|
||||
|
||||
gl.clearColor(0.0, 0.0, 0.0, 1.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
gl.useProgram(shaderProgram);
|
||||
|
||||
if (resolution) gl.uniform2f(resolution, canvas.width, canvas.height);
|
||||
if (time) gl.uniform1f(time, currentTime);
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
gl.vertexAttribPointer(vertexPosition, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.enableVertexAttribArray(vertexPosition);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
frameId = requestAnimationFrame(render);
|
||||
};
|
||||
|
||||
frameId = requestAnimationFrame(render);
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(frameId);
|
||||
window.removeEventListener("resize", resizeCanvas);
|
||||
gl.deleteBuffer(positionBuffer);
|
||||
gl.deleteProgram(shaderProgram);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
aria-hidden="true"
|
||||
className={cn("fixed inset-0 -z-10 h-full w-full", className)}
|
||||
style={{ opacity }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShaderBackground;
|
||||
Reference in New Issue
Block a user