30-04-2026
This commit is contained in:
parent
761b1156c1
commit
d054732bf5
35 changed files with 2796 additions and 505 deletions
|
|
@ -7,7 +7,6 @@ import {
|
|||
ShaderMaterial,
|
||||
Vector3,
|
||||
Vector2,
|
||||
Clock,
|
||||
} from 'three'
|
||||
|
||||
const vertexShader = `
|
||||
|
|
@ -19,7 +18,7 @@ void main() {
|
|||
`
|
||||
|
||||
const fragmentShader = `
|
||||
precision highp float;
|
||||
precision mediump float;
|
||||
|
||||
uniform float iTime;
|
||||
uniform vec3 iResolution;
|
||||
|
|
@ -58,34 +57,24 @@ uniform float bendRadius;
|
|||
uniform float bendStrength;
|
||||
uniform float bendInfluence;
|
||||
|
||||
uniform int horizonMode; // 0=off 1=fog 2=split 3=glow
|
||||
uniform float horizonOpacity; // Nebel + Glow: Helligkeit/Dichte
|
||||
uniform float horizonBlend; // Trennung: 0=scharf, 1=weicher Übergang
|
||||
|
||||
uniform bool parallax;
|
||||
uniform float parallaxStrength;
|
||||
uniform vec2 parallaxOffset;
|
||||
|
||||
uniform float lineBrightness;
|
||||
uniform vec3 lineGradient[8];
|
||||
uniform int lineGradientCount;
|
||||
uniform vec3 bgColorCenter;
|
||||
uniform vec3 bgColorEdge;
|
||||
|
||||
const vec3 BLACK = vec3(0.0);
|
||||
const vec3 PINK = vec3(233.0, 71.0, 245.0) / 255.0;
|
||||
const vec3 BLUE = vec3(47.0, 75.0, 162.0) / 255.0;
|
||||
|
||||
mat2 rotate(float r) {
|
||||
return mat2(cos(r), sin(r), -sin(r), cos(r));
|
||||
}
|
||||
|
||||
vec3 background_color(vec2 uv) {
|
||||
vec3 col = vec3(0.0);
|
||||
|
||||
float y = sin(uv.x - 0.2) * 0.3 - 0.1;
|
||||
float m = uv.y - y;
|
||||
|
||||
col += mix(BLUE, BLACK, smoothstep(0.0, 1.0, abs(m)));
|
||||
col += mix(PINK, BLACK, smoothstep(0.0, 1.0, abs(m - 0.8)));
|
||||
return col * 0.5;
|
||||
}
|
||||
|
||||
vec3 getLineColor(float t, vec3 baseColor) {
|
||||
if (lineGradientCount <= 0) {
|
||||
return baseColor;
|
||||
|
|
@ -108,7 +97,7 @@ vec3 getLineColor(float t, vec3 baseColor) {
|
|||
gradientColor = mix(c1, c2, f);
|
||||
}
|
||||
|
||||
return gradientColor * 0.5;
|
||||
return gradientColor;
|
||||
}
|
||||
|
||||
vec3 drawCircle(vec2 uv, vec2 center, float r, vec3 color) {
|
||||
|
|
@ -163,25 +152,9 @@ float bezierClosestT(vec2 q, vec2 p0, vec2 pc, vec2 p1) {
|
|||
return t;
|
||||
}
|
||||
|
||||
float waveFocal(vec2 uv, float fi, float totalLines, vec2 sp, vec2 ep) {
|
||||
// Bézier-Kontrollpunkt: Mittelpunkt + senkrechter Versatz
|
||||
vec2 seg = ep - sp;
|
||||
float segLen = length(seg);
|
||||
if (segLen < 0.001) return 0.0;
|
||||
vec2 segDir = seg / segLen;
|
||||
vec2 segPerp = vec2(-segDir.y, segDir.x);
|
||||
vec2 pc = (sp + ep) * 0.5 + segPerp * segLen * bezierCurvature;
|
||||
|
||||
float t = bezierClosestT(uv, sp, pc, ep);
|
||||
float mt = 1.0 - t;
|
||||
|
||||
// Position und Tangente auf der Kurve
|
||||
vec2 curvePos = mt*mt*sp + 2.0*mt*t*pc + t*t*ep;
|
||||
vec2 tang = normalize(2.0*mt*(pc - sp) + 2.0*t*(ep - pc));
|
||||
vec2 norm = vec2(-tang.y, tang.x);
|
||||
|
||||
// Senkrechter Abstand von der Kurve
|
||||
float s = dot(uv - curvePos, norm);
|
||||
// Accepts precomputed bezier values (bt, bPos, bNorm) — computed once per segment
|
||||
float waveFocal(vec2 uv, float fi, float totalLines, float t, vec2 bPos, vec2 bNorm) {
|
||||
float s = dot(uv - bPos, bNorm);
|
||||
|
||||
float time = iTime * animationSpeed;
|
||||
float normalizedI = totalLines > 1.0 ? fi / (totalLines - 1.0) : 0.5;
|
||||
|
|
@ -227,7 +200,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|||
|
||||
vec3 col = vec3(0.0);
|
||||
|
||||
vec3 b = lineGradientCount > 0 ? bgColorCenter : background_color(baseUv);
|
||||
vec3 b = bgColorCenter;
|
||||
|
||||
vec2 mouseUv = vec2(0.0);
|
||||
if (interactive) {
|
||||
|
|
@ -269,33 +242,34 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|||
vec2 sp = vec2(x0, pointY[s]);
|
||||
vec2 ep = vec2(x1, pointY[s + 1]);
|
||||
|
||||
// Gradient: globaler t-Bereich [s, s+1] / (numPoints-1)
|
||||
vec2 pd = ep - sp;
|
||||
float pl = length(pd);
|
||||
vec2 pa = pl > 0.001 ? pd / pl : vec2(1.0, 0.0);
|
||||
float t_seg = clamp(dot(baseUv - sp, pa) / pl, 0.0, 1.0);
|
||||
// Segment-Geometrie (einmalig berechnet, von Gradient + Bézier genutzt)
|
||||
vec2 seg = ep - sp;
|
||||
float segL = length(seg);
|
||||
vec2 segDir = segL > 0.001 ? seg / segL : vec2(1.0, 0.0);
|
||||
vec2 sPerp = vec2(-segDir.y, segDir.x);
|
||||
vec2 pc = (sp + ep) * 0.5 + sPerp * segL * bezierCurvature;
|
||||
|
||||
// Gradient
|
||||
float t_seg = clamp(dot(baseUv - sp, segDir) / segL, 0.0, 1.0);
|
||||
float t_global = (float(s) + t_seg) * tScale;
|
||||
vec3 lineCol = getLineColor(t_global, b);
|
||||
vec3 lineCol = getLineColor(t_global, b);
|
||||
|
||||
// Bézier-Kontrollpunkt für Nebel (gleiche Logik wie in waveFocal)
|
||||
vec2 segD = ep - sp;
|
||||
float segL = length(segD);
|
||||
vec2 segDir = segL > 0.001 ? segD / segL : vec2(1.0, 0.0);
|
||||
vec2 sPerp = vec2(-segDir.y, segDir.x);
|
||||
vec2 pc = (sp + ep) * 0.5 + sPerp * segL * bezierCurvature;
|
||||
// Bézier einmal pro Segment — geteilt von Nebel + allen Linien
|
||||
float bt = bezierClosestT(baseUv, sp, pc, ep);
|
||||
float bmt = 1.0 - bt;
|
||||
vec2 bPos = bmt*bmt*sp + 2.0*bmt*bt*pc + bt*bt*ep;
|
||||
vec2 bTang = normalize(2.0*bmt*(pc - sp) + 2.0*bt*(ep - pc));
|
||||
vec2 bNorm = vec2(-bTang.y, bTang.x);
|
||||
|
||||
// Weicher Nebel entlang der Bézier-Kurve → füllt dunkle Winkel organisch
|
||||
float bt = bezierClosestT(baseUv, sp, pc, ep);
|
||||
float bmt = 1.0 - bt;
|
||||
vec2 bPos = bmt*bmt*sp + 2.0*bmt*bt*pc + bt*bt*ep;
|
||||
float bDist = length(baseUv - bPos);
|
||||
// Weicher Nebel entlang der Kurve
|
||||
float bDist = length(baseUv - bPos);
|
||||
float fogFade = smoothstep(-0.06, 0.05, bt) * smoothstep(1.06, 0.95, bt);
|
||||
float fogEnv = sin(bt * 3.14159265359);
|
||||
float segFog = fogFade * fogEnv * 0.0018 / max(bDist * bDist * 4.0 + 0.012, 0.001);
|
||||
col += lineCol * segFog;
|
||||
|
||||
for (int i = 0; i < middleLineCount; ++i) {
|
||||
col += lineCol * waveFocal(baseUv, float(i), float(middleLineCount), sp, ep);
|
||||
col += lineCol * waveFocal(baseUv, float(i), float(middleLineCount), bt, bPos, bNorm);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -304,7 +278,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|||
if (p >= numPoints) break;
|
||||
float px = pointsOffsetX + (float(p) - float(numPoints - 1) * 0.5) * pointSpacingX;
|
||||
float t_pt = numPoints > 1 ? float(p) * tScale : 0.0;
|
||||
vec3 circCol = getLineColor(t_pt, b) * 2.5;
|
||||
vec3 circCol = getLineColor(t_pt, b) * 1.5;
|
||||
col += drawCircle(baseUv, vec2(px, pointY[p]), r, circCol);
|
||||
}
|
||||
}
|
||||
|
|
@ -328,9 +302,33 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|||
}
|
||||
}
|
||||
|
||||
col *= lineBrightness;
|
||||
|
||||
// Hintergrundverlauf: radial von bgColorCenter (Mitte) nach bgColorEdge (Rand)
|
||||
float dist = length(baseUv) / 1.8;
|
||||
vec3 bg = mix(bgColorCenter, bgColorEdge, clamp(dist, 0.0, 1.0));
|
||||
|
||||
if (horizonMode == 1) {
|
||||
// Nebel: breites weiches Gaussband in Gradient-Mittelfarbe
|
||||
float band = exp(-baseUv.y * baseUv.y * 5.0);
|
||||
vec3 fogColor = getLineColor(0.5, bg) * 2.0;
|
||||
col += fogColor * band * horizonOpacity;
|
||||
} else if (horizonMode == 2) {
|
||||
// Farbtrennung: vertikaler Split an Y=0
|
||||
// bgColorCenter → oben (positives UV-Y), bgColorEdge → unten
|
||||
// horizonBlend: 0=harter Schnitt, 1=sehr weicher Übergang
|
||||
float blendW = max(horizonBlend * 0.7, 0.001);
|
||||
float t = smoothstep(-blendW, blendW, baseUv.y);
|
||||
bg = mix(bgColorEdge, bgColorCenter, t);
|
||||
} else if (horizonMode == 3) {
|
||||
// Glow: konzentriertes Leuchten in Gradient-Mittelfarbe
|
||||
float d2 = baseUv.y * baseUv.y;
|
||||
float softGlow = exp(-d2 * 10.0);
|
||||
float coreGlow = exp(-d2 * 70.0) * 0.7;
|
||||
vec3 glowColor = getLineColor(0.5, bg) * 3.0;
|
||||
col += glowColor * (softGlow + coreGlow) * horizonOpacity;
|
||||
}
|
||||
|
||||
fragColor = vec4(clamp(bg + col, 0.0, 1.0), 1.0);
|
||||
}
|
||||
|
||||
|
|
@ -376,7 +374,6 @@ export default class FloatingLines {
|
|||
lineCount = [6],
|
||||
lineDistance = [5],
|
||||
topWavePosition,
|
||||
middleWavePosition,
|
||||
bottomWavePosition = { x: 2.0, y: -0.7, rotate: -1 },
|
||||
numPoints = 4,
|
||||
pointSpacingX = 0.8,
|
||||
|
|
@ -389,6 +386,7 @@ export default class FloatingLines {
|
|||
bezierCurvature = 0.3,
|
||||
circleRadiusPx = 50,
|
||||
animationSpeed = 1,
|
||||
lineBrightness = 1.0,
|
||||
interactive = true,
|
||||
bendRadius = 5.0,
|
||||
bendStrength = -0.5,
|
||||
|
|
@ -397,6 +395,11 @@ export default class FloatingLines {
|
|||
parallaxStrength = 0.2,
|
||||
circleGlowSize = 18.0,
|
||||
circleGlowStrength = 1.5,
|
||||
horizonMode = 'off',
|
||||
horizonOpacity = 0.5,
|
||||
horizonBlend = 0.2,
|
||||
bgColorCenter = '#0a0514',
|
||||
bgColorEdge = '#000000',
|
||||
mixBlendMode = 'screen',
|
||||
} = {},
|
||||
) {
|
||||
|
|
@ -427,14 +430,12 @@ export default class FloatingLines {
|
|||
return lineDistance[index] ?? 0.1
|
||||
}
|
||||
|
||||
const topLineCount = enabledWaves.includes('top') ? getLineCount('top') : 0
|
||||
const middleLineCount = enabledWaves.includes('middle') ? getLineCount('middle') : 0
|
||||
const bottomLineCount = enabledWaves.includes('bottom') ? getLineCount('bottom') : 0
|
||||
const topLineCount = getLineCount('top')
|
||||
const middleLineCount = getLineCount('middle')
|
||||
const bottomLineCount = getLineCount('bottom')
|
||||
|
||||
const topLineDistance = enabledWaves.includes('top') ? getLineDistance('top') * 0.01 : 0.01
|
||||
const bottomLineDistance = enabledWaves.includes('bottom')
|
||||
? getLineDistance('bottom') * 0.01
|
||||
: 0.01
|
||||
const topLineDistance = getLineDistance('top') * 0.01
|
||||
const bottomLineDistance = getLineDistance('bottom') * 0.01
|
||||
|
||||
this.scene = new Scene()
|
||||
this.camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1)
|
||||
|
|
@ -451,6 +452,7 @@ export default class FloatingLines {
|
|||
iTime: { value: 0 },
|
||||
iResolution: { value: new Vector3(1, 1, 1) },
|
||||
animationSpeed: { value: animationSpeed },
|
||||
lineBrightness: { value: lineBrightness },
|
||||
|
||||
enableTop: { value: enabledWaves.includes('top') },
|
||||
enableMiddle: { value: enabledWaves.includes('middle') },
|
||||
|
|
@ -497,6 +499,10 @@ export default class FloatingLines {
|
|||
bendStrength: { value: bendStrength },
|
||||
bendInfluence: { value: 0 },
|
||||
|
||||
horizonMode: { value: { off: 0, fog: 1, split: 2, glow: 3 }[horizonMode] ?? 0 },
|
||||
horizonOpacity: { value: horizonOpacity },
|
||||
horizonBlend: { value: horizonBlend },
|
||||
|
||||
parallax: { value: parallax },
|
||||
parallaxStrength: { value: parallaxStrength },
|
||||
parallaxOffset: { value: new Vector2(0, 0) },
|
||||
|
|
@ -505,7 +511,8 @@ export default class FloatingLines {
|
|||
value: Array.from({ length: MAX_GRADIENT_STOPS }, () => new Vector3(1, 1, 1)),
|
||||
},
|
||||
lineGradientCount: { value: 0 },
|
||||
bgColor: { value: new Vector3(0, 0, 0) },
|
||||
bgColorCenter: { value: new Vector3(0, 0, 0) },
|
||||
bgColorEdge: { value: new Vector3(0, 0, 0) },
|
||||
}
|
||||
|
||||
if (linesGradient && linesGradient.length > 0) {
|
||||
|
|
@ -517,6 +524,11 @@ export default class FloatingLines {
|
|||
})
|
||||
}
|
||||
|
||||
const center = hexToVec3(bgColorCenter)
|
||||
this.uniforms.bgColorCenter.value.set(center.x, center.y, center.z)
|
||||
const edge = hexToVec3(bgColorEdge)
|
||||
this.uniforms.bgColorEdge.value.set(edge.x, edge.y, edge.z)
|
||||
|
||||
const material = new ShaderMaterial({
|
||||
uniforms: this.uniforms,
|
||||
vertexShader,
|
||||
|
|
@ -529,7 +541,7 @@ export default class FloatingLines {
|
|||
|
||||
this.geometry = geometry
|
||||
this.material = material
|
||||
this.clock = new Clock()
|
||||
this._startTime = performance.now()
|
||||
|
||||
this._setSize = () => {
|
||||
const width = container.clientWidth || 1
|
||||
|
|
@ -571,7 +583,7 @@ export default class FloatingLines {
|
|||
|
||||
this.raf = 0
|
||||
const renderLoop = () => {
|
||||
this.uniforms.iTime.value = this.clock.getElapsedTime()
|
||||
this.uniforms.iTime.value = (performance.now() - this._startTime) * 0.001
|
||||
|
||||
if (this.interactive) {
|
||||
this.currentMouse.lerp(this.targetMouse, this.mouseDamping)
|
||||
|
|
@ -589,12 +601,24 @@ export default class FloatingLines {
|
|||
this.renderer.render(this.scene, this.camera)
|
||||
this.raf = requestAnimationFrame(renderLoop)
|
||||
}
|
||||
|
||||
this._handleVisibility = () => {
|
||||
if (document.hidden) {
|
||||
cancelAnimationFrame(this.raf)
|
||||
this.raf = 0
|
||||
} else if (!this.raf) {
|
||||
renderLoop()
|
||||
}
|
||||
}
|
||||
document.addEventListener('visibilitychange', this._handleVisibility)
|
||||
|
||||
renderLoop()
|
||||
}
|
||||
|
||||
destroy() {
|
||||
cancelAnimationFrame(this.raf)
|
||||
if (this.ro) this.ro.disconnect()
|
||||
document.removeEventListener('visibilitychange', this._handleVisibility)
|
||||
|
||||
this.renderer.domElement.removeEventListener('pointermove', this._handlePointerMove)
|
||||
this.renderer.domElement.removeEventListener('pointerleave', this._handlePointerLeave)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue