Files
BRETT/app/components/effects/EffectLineWaves.vue
T
2026-10-03 20:07:49 +02:00

171 lines
5.2 KiB
Vue

<script setup lang="ts">
import { hexToRgbTuple, shadeHex } from '@/lib/color'
// Portiert aus Vue Bits (DavidHDev/vue-bits), vereinfacht auf color-Prop
const props = withDefaults(defineProps<{ color?: string }>(), {
color: '#6366f1',
})
const VERT = `
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`
const FRAG = `
precision highp float;
uniform float uTime;
uniform float uSpeed;
uniform float uInnerLines;
uniform float uOuterLines;
uniform float uWarpIntensity;
uniform float uRotation;
uniform float uEdgeFadeWidth;
uniform float uColorCycleSpeed;
uniform float uBrightness;
uniform vec3 uColor1;
uniform vec3 uColor2;
uniform vec3 uColor3;
uniform vec2 uMouse;
uniform float uMouseActive;
uniform float uMouseInfluence;
uniform vec2 uCardRes;
uniform float uRadius;
#define HALF_PI 1.5707963
float hashF(float n) {
return fract(sin(n * 127.1) * 43758.5453123);
}
float smoothNoise(float x) {
float i = floor(x);
float f = fract(x);
float u = f * f * (3.0 - 2.0 * f);
return mix(hashF(i), hashF(i + 1.0), u);
}
float displaceA(float coord, float t) {
float result = sin(coord * 2.123) * 0.2;
result += sin(coord * 3.234 + t * 4.345) * 0.1;
result += sin(coord * 0.589 + t * 0.934) * 0.5;
return result;
}
float displaceB(float coord, float t) {
float result = sin(coord * 1.345) * 0.3;
result += sin(coord * 2.734 + t * 3.345) * 0.2;
result += sin(coord * 0.189 + t * 0.934) * 0.3;
return result;
}
vec2 rotate2D(vec2 p, float angle) {
float c = cos(angle);
float s = sin(angle);
return vec2(p.x * c - p.y * s, p.x * s + p.y * c);
}
float cardMask(vec2 fc) {
vec2 p = fc - uCardRes * 0.5;
vec2 hb = uCardRes * 0.5 - vec2(uRadius);
float rd = length(max(abs(p) - hb, 0.0)) - uRadius;
return 1.0 - smoothstep(-0.75, 0.75, rd);
}
void main() {
// Pixelkonsistent: Koordinaten auf eine Referenzhöhe von 180px normieren,
// damit die Liniendichte in flachen Containern nicht zusammengestaucht wird
vec2 coords = (gl_FragCoord.xy * 2.0 - uCardRes.xy) / 180.0;
coords = rotate2D(coords, uRotation);
float halfT = uTime * uSpeed * 0.5;
float fullT = uTime * uSpeed;
vec2 mPos = rotate2D((uMouse * 2.0 - 1.0) * (uCardRes / 360.0), uRotation);
float mDist = length(coords - mPos);
float mouseWarp = uMouseInfluence * exp(-mDist * mDist * 4.0) * uMouseActive;
float warpAx = coords.x + displaceA(coords.y, halfT) * uWarpIntensity + mouseWarp;
float warpAy = coords.y - displaceA(coords.x * cos(fullT) * 1.235, halfT) * uWarpIntensity;
float warpBx = coords.x + displaceB(coords.y, halfT) * uWarpIntensity + mouseWarp;
float warpBy = coords.y - displaceB(coords.x * sin(fullT) * 1.235, halfT) * uWarpIntensity;
vec2 fieldA = vec2(warpAx, warpAy);
vec2 fieldB = vec2(warpBx, warpBy);
vec2 blended = mix(fieldA, fieldB, mix(fieldA, fieldB, 0.5));
float fadeTop = smoothstep(uEdgeFadeWidth, uEdgeFadeWidth + 0.4, blended.y);
float fadeBottom = smoothstep(-uEdgeFadeWidth, -(uEdgeFadeWidth + 0.4), blended.y);
float vMask = 1.0 - max(fadeTop, fadeBottom);
float tileCount = mix(uOuterLines, uInnerLines, vMask);
float scaledY = blended.y * tileCount;
float nY = smoothNoise(abs(scaledY));
float ridge = pow(
step(abs(nY - blended.x) * 2.0, HALF_PI) * cos(2.0 * (nY - blended.x)),
5.0
);
float waveProfile = max(0.0, (max(fract(scaledY), fract(-scaledY)) - 0.55) / 0.45);
float lines = pow(waveProfile, 2.0) + pow(waveProfile, 4.0);
float pattern = vMask * lines;
float cycleT = fullT * uColorCycleSpeed;
float rChannel = (pattern + lines * ridge) * (cos(blended.y + cycleT * 0.234) * 0.5 + 1.0);
float gChannel = (pattern + vMask * ridge) * (sin(blended.x + cycleT * 1.745) * 0.5 + 1.0);
float bChannel = (pattern + lines * ridge) * (cos(blended.x + cycleT * 0.534) * 0.5 + 1.0);
vec3 col = (rChannel * uColor1 + gChannel * uColor2 + bChannel * uColor3) * uBrightness;
float alpha = clamp(length(col), 0.0, 1.0) * cardMask(gl_FragCoord.xy);
gl_FragColor = vec4(col * alpha, alpha);
}
`
const ctnDom = ref<HTMLDivElement | null>(null)
useCardShader(ctnDom, {
vertex: VERT,
fragment: FRAG,
mouse: true,
uniforms: {
uTime: { value: 0 },
uSpeed: { value: 0.3 },
uInnerLines: { value: 32 },
uOuterLines: { value: 36 },
uWarpIntensity: { value: 1 },
uRotation: { value: -45 * Math.PI / 180 },
uEdgeFadeWidth: { value: 0 },
uColorCycleSpeed: { value: 1 },
uBrightness: { value: 0.35 },
uColor1: { value: new Float32Array(hexToRgbTuple(props.color)) },
uColor2: { value: new Float32Array(hexToRgbTuple(shadeHex(props.color, 0.35))) },
uColor3: { value: new Float32Array(hexToRgbTuple(shadeHex(props.color, -0.25))) },
uMouse: { value: new Float32Array([0.5, 0.5]) },
uMouseActive: { value: 0 },
uMouseInfluence: { value: 2.0 },
},
color: () => props.color,
colorUniforms: { uColor1: 0, uColor2: 0.35, uColor3: -0.25 },
frame: ({ t, program, mouse }) => {
const u = program.uniforms
u.uTime.value = t * 0.001
const m = u.uMouse.value as Float32Array
m[0] = mouse.x
m[1] = mouse.y
u.uMouseActive.value = mouse.active
},
})
</script>
<template>
<div ref="ctnDom" class="relative size-full overflow-hidden rounded-[inherit]" />
</template>