forked from BRETT/BRETT
195 lines
6.3 KiB
Vue
195 lines
6.3 KiB
Vue
<script setup lang="ts">
|
|
import { hexToRgbTuple, shadeHex } from '@/lib/color'
|
|
|
|
// Portiert aus Vue Bits (DavidHDev/vue-bits), von three.js auf ogl umgeschrieben,
|
|
// 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;
|
|
|
|
varying vec2 vUv;
|
|
uniform float uTime;
|
|
uniform float uSpeed;
|
|
uniform vec3 uColor;
|
|
uniform vec3 uBackgroundColor;
|
|
uniform float uCurvature;
|
|
uniform float uScanlineStrength;
|
|
uniform float uScanlineFrequency;
|
|
uniform float uWaveAmplitude;
|
|
uniform float uWaveFrequency;
|
|
uniform float uBloom;
|
|
uniform float uBloomRadius;
|
|
uniform float uNoise;
|
|
uniform float uVignette;
|
|
uniform float uBrightness;
|
|
uniform float uPixelation;
|
|
uniform float uRgbShift;
|
|
uniform vec2 uPointer;
|
|
uniform float uMouseStrength;
|
|
uniform vec2 uCardRes;
|
|
uniform float uRadius;
|
|
|
|
float hash21(vec2 p) {
|
|
p = fract(p * vec2(123.34, 456.21));
|
|
p += dot(p, p + 45.32);
|
|
return fract(p.x * p.y);
|
|
}
|
|
|
|
vec2 crtCurve(vec2 uv, float radius) {
|
|
vec2 p = (uv - 0.5) * 2.0;
|
|
float safeRadius = max(radius, 1.415);
|
|
float cornerScale = safeRadius / sqrt(max(safeRadius * safeRadius - 2.0, 0.001));
|
|
p = safeRadius * p / sqrt(max(safeRadius * safeRadius - dot(p, p), 0.001));
|
|
p /= cornerScale;
|
|
return p * 0.5 + 0.5;
|
|
}
|
|
|
|
float referencePlasma(vec2 uv, float t) {
|
|
float frequencyScale = max(uWaveFrequency / 2.2, 0.001);
|
|
uv = (uv - 0.5) * frequencyScale + 0.5;
|
|
|
|
float scanline = 0.5 - 0.5 * cos(uv.y * 3.14159265 * uScanlineFrequency);
|
|
scanline = mix(1.0, scanline, uScanlineStrength);
|
|
|
|
// Pixelkonsistente Zellen (~16px) statt fixem 80x24-Raster,
|
|
// damit das CRT-Pixeln in flachen Containern nicht staucht
|
|
vec2 cells = max(round(uCardRes / 16.0), vec2(4.0));
|
|
uv *= cells;
|
|
uv = ceil(uv);
|
|
uv /= cells;
|
|
|
|
float amplitude = uWaveAmplitude / 0.28;
|
|
float field = 0.0;
|
|
field += 0.7 * sin(0.5 * uv.x + t / 5.0);
|
|
field += 3.0 * sin(1.6 * uv.y + t / 5.0);
|
|
field += sin(10.0 * (uv.y * sin(t / 2.0) + uv.x * cos(t / 5.0)) + t / 2.0);
|
|
|
|
float cx = uv.x + 0.5 * sin(t / 2.0);
|
|
float cy = uv.y + 0.5 * cos(t / 4.0);
|
|
field += 0.4 * sin(sqrt(100.0 * cx * cx + 100.0 * cy * cy + 1.0) + t);
|
|
field += 0.9 * sin(sqrt(75.0 * cx * cx + 25.0 * cy * cy + 1.0) + t);
|
|
field -= 1.4 * sin(sqrt(256.0 * cx * cx + 25.0 * cy * cy + 1.0) + t);
|
|
field += 0.3 * sin(0.5 * uv.y + uv.x + sin(t));
|
|
|
|
return scanline * floor(3.0 * (0.5 + 0.499 * sin(field * amplitude))) / 3.0;
|
|
}
|
|
|
|
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() {
|
|
vec2 uv = vUv;
|
|
if (uPixelation > 1.001) {
|
|
vec2 cells = max(uCardRes / uPixelation, vec2(1.0));
|
|
uv = (floor(uv * cells) + 0.5) / cells;
|
|
}
|
|
|
|
float curveRadius = 1.1 + 0.42 / max(uCurvature, 0.001);
|
|
curveRadius *= exp(-uPointer.y * uMouseStrength * 0.4);
|
|
vec2 curvedUv = crtCurve(uv, curveRadius);
|
|
curvedUv.x -= uPointer.x * uMouseStrength * 0.035;
|
|
|
|
float t = uTime * uSpeed;
|
|
float signal = referencePlasma(curvedUv, t);
|
|
float radius = 0.01 * uBloomRadius;
|
|
float glow = signal * 0.2;
|
|
glow += referencePlasma(curvedUv + vec2(radius, 0.0), t) * 0.12;
|
|
glow += referencePlasma(curvedUv - vec2(radius, 0.0), t) * 0.12;
|
|
glow += referencePlasma(curvedUv + vec2(0.0, radius), t) * 0.12;
|
|
glow += referencePlasma(curvedUv - vec2(0.0, radius), t) * 0.12;
|
|
glow += referencePlasma(curvedUv + vec2(radius), t) * 0.08;
|
|
glow += referencePlasma(curvedUv - vec2(radius), t) * 0.08;
|
|
glow += referencePlasma(curvedUv + vec2(radius, -radius), t) * 0.08;
|
|
glow += referencePlasma(curvedUv + vec2(-radius, radius), t) * 0.08;
|
|
|
|
float redSignal = referencePlasma(curvedUv + vec2(uRgbShift, 0.0), t);
|
|
float blueSignal = referencePlasma(curvedUv - vec2(uRgbShift, 0.0), t);
|
|
vec3 channelSignal = vec3(redSignal, signal, blueSignal);
|
|
vec3 waveColor = uColor * (0.3 + signal * 0.7 + glow * uBloom * 0.65);
|
|
waveColor += (channelSignal - signal) * 0.42;
|
|
|
|
float edge = clamp(1.0 - dot(vUv - 0.5, vUv - 0.5) * 2.0, 0.0, 1.0);
|
|
float edgeFade = mix(1.0, smoothstep(0.0, 1.0, edge), uVignette);
|
|
float waveMask = clamp(signal * 0.82 + glow * 0.52, 0.0, 1.0) * edgeFade;
|
|
|
|
waveColor = max(waveColor * uBrightness, vec3(0.0));
|
|
float a = clamp(waveMask, 0.0, 1.0) * cardMask(gl_FragCoord.xy);
|
|
vec3 col = waveColor;
|
|
if (a > 0.01) {
|
|
float grain = (hash21(gl_FragCoord.xy + vec2(fract(uTime) * 173.0)) - 0.5) * uNoise;
|
|
col = max(col + grain, vec3(0.0));
|
|
}
|
|
gl_FragColor = vec4(col * a, a);
|
|
}
|
|
`
|
|
|
|
const ctnDom = ref<HTMLDivElement | null>(null)
|
|
|
|
let elapsed = 0
|
|
let lastT = 0
|
|
|
|
useCardShader(ctnDom, {
|
|
vertex: VERT,
|
|
fragment: FRAG,
|
|
mouse: true,
|
|
uniforms: {
|
|
uTime: { value: 0 },
|
|
uSpeed: { value: 0.5 },
|
|
uColor: { value: new Float32Array(hexToRgbTuple(props.color)) },
|
|
uBackgroundColor: { value: new Float32Array(hexToRgbTuple(shadeHex(props.color, -0.93))) },
|
|
uCurvature: { value: 0.25 },
|
|
uScanlineStrength: { value: 0.25 },
|
|
uScanlineFrequency: { value: 200 },
|
|
uWaveAmplitude: { value: 0.3 },
|
|
uWaveFrequency: { value: 2.5 },
|
|
uBloom: { value: 1.5 },
|
|
uBloomRadius: { value: 1 },
|
|
uNoise: { value: 0.1 },
|
|
uVignette: { value: 0 },
|
|
uBrightness: { value: 1.25 },
|
|
uPixelation: { value: 1 },
|
|
uRgbShift: { value: 0.015 },
|
|
uPointer: { value: new Float32Array([0, 0]) },
|
|
uMouseStrength: { value: 0.5 },
|
|
},
|
|
color: () => props.color,
|
|
colorUniforms: { uColor: 0, uBackgroundColor: -0.93 },
|
|
frame: ({ t, program, mouse }) => {
|
|
// fps-Cap ~30 wie im Original (CRT-Flackern)
|
|
const u = program.uniforms
|
|
const delta = Math.min((t - lastT) * 0.001, 0.1)
|
|
lastT = t
|
|
elapsed += delta
|
|
u.uTime.value = elapsed
|
|
const p = u.uPointer.value as Float32Array
|
|
p[0] = (mouse.x * 2 - 1) * mouse.active
|
|
p[1] = (mouse.y * 2 - 1) * mouse.active
|
|
// Scanline-Dichte in Pixel-Pitch (eine Linie alle ~6 Buffer-Pixel)
|
|
// statt fixer Zeilenzahl — flache Karten sonst subpixel-gestaucht
|
|
const res = u.uCardRes!.value as Float32Array
|
|
u.uScanlineFrequency!.value = res[1] / 6
|
|
},
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div ref="ctnDom" class="relative size-full overflow-hidden rounded-[inherit]" />
|
|
</template>
|