Feature: Synthwave visualizer
This commit is contained in:
parent
c6324d3ccd
commit
ec2465222c
111
party-stage/src/visualizers/FloatingShapesVisualizer.js
Normal file
111
party-stage/src/visualizers/FloatingShapesVisualizer.js
Normal file
@ -0,0 +1,111 @@
|
||||
import { VisualizerInterface } from './VisualizerInterface.js';
|
||||
|
||||
const fragmentShader = `
|
||||
uniform float u_time;
|
||||
uniform float u_beat;
|
||||
uniform float u_opacity;
|
||||
uniform vec2 u_resolution;
|
||||
uniform vec3 u_colors[16];
|
||||
uniform int u_colorCount;
|
||||
uniform float u_seed;
|
||||
varying vec2 vUv;
|
||||
|
||||
float sdCircle(vec2 p, float r) {
|
||||
return length(p) - r;
|
||||
}
|
||||
|
||||
float sdBox(vec2 p, vec2 b) {
|
||||
vec2 d = abs(p) - b;
|
||||
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
|
||||
}
|
||||
|
||||
vec2 rotate(vec2 p, float a) {
|
||||
float s = sin(a);
|
||||
float c = cos(a);
|
||||
return mat2(c, -s, s, c) * p;
|
||||
}
|
||||
|
||||
void main() {
|
||||
// LED Grid Setup
|
||||
float ledCountX = u_resolution.x;
|
||||
float ledCountY = u_resolution.y;
|
||||
vec2 gridUV = vec2(vUv.x * ledCountX, vUv.y * ledCountY);
|
||||
vec2 gridDeriv = fwidth(gridUV);
|
||||
float gridDensity = max(gridDeriv.x, gridDeriv.y);
|
||||
float blurFactor = smoothstep(0.3, 0.8, gridDensity);
|
||||
vec2 cell = fract(gridUV);
|
||||
vec2 uv = mix((floor(gridUV) + 0.5) / vec2(ledCountX, ledCountY), vUv, blurFactor);
|
||||
|
||||
float distMask = distance(cell, vec2(0.5));
|
||||
float edgeSoftness = clamp(gridDensity * 1.5, 0.0, 0.5);
|
||||
float mask = 1.0 - smoothstep(0.35 - edgeSoftness, 0.45 + edgeSoftness, distMask);
|
||||
mask = mix(mask, 1.0, blurFactor);
|
||||
|
||||
// Aspect correction for shapes
|
||||
vec2 p = (uv - 0.5);
|
||||
p.x *= u_resolution.x / u_resolution.y;
|
||||
|
||||
float t = u_time * 0.4 + u_seed;
|
||||
float beatImpact = u_beat * 0.1;
|
||||
|
||||
// Background color shift
|
||||
vec3 colorA = vec3(0.05, 0.05, 0.1);
|
||||
vec3 colorB = vec3(0.1, 0.0, 0.2);
|
||||
if (u_colorCount >= 2) {
|
||||
colorA = u_colors[0] * 0.2;
|
||||
colorB = u_colors[int(mod(1.0 + floor(t * 0.1), float(u_colorCount)))] * 0.3;
|
||||
}
|
||||
vec3 finalColor = mix(colorA, colorB, sin(t) * 0.5 + 0.5);
|
||||
|
||||
// Render shapes
|
||||
float shapes = 0.0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
float fi = float(i);
|
||||
float shapeSeed = u_seed + fi * 123.456;
|
||||
|
||||
// Motion
|
||||
vec2 pos = vec2(
|
||||
sin(t * 0.5 + shapeSeed) * 0.8,
|
||||
cos(t * 0.3 + shapeSeed * 1.1) * 0.4
|
||||
);
|
||||
|
||||
vec2 sp = p - pos;
|
||||
sp = rotate(sp, t * (0.5 + fract(shapeSeed)));
|
||||
|
||||
float d;
|
||||
float size = 0.1 + fract(shapeSeed * 0.7) * 0.2 + beatImpact;
|
||||
|
||||
if (mod(fi, 2.0) == 0.0) {
|
||||
d = sdCircle(sp, size);
|
||||
} else {
|
||||
d = sdBox(sp, vec2(size * 0.8));
|
||||
}
|
||||
|
||||
float intensity = smoothstep(0.01, 0.0, d);
|
||||
|
||||
// Pick a color for the shape
|
||||
vec3 shapeColor = vec3(1.0);
|
||||
if (u_colorCount > 0) {
|
||||
int colorIdx = int(mod(fi + floor(t), float(u_colorCount)));
|
||||
shapeColor = u_colors[colorIdx];
|
||||
}
|
||||
|
||||
finalColor = mix(finalColor, shapeColor, intensity * 0.8);
|
||||
|
||||
// Add a slight glow/aura
|
||||
finalColor += shapeColor * (1.0 - smoothstep(0.0, size * 2.0, abs(d))) * 0.2 * (0.5 + u_beat);
|
||||
}
|
||||
|
||||
gl_FragColor = vec4(finalColor, mask * u_opacity);
|
||||
}
|
||||
`;
|
||||
|
||||
export class FloatingShapesVisualizer extends VisualizerInterface {
|
||||
getName() {
|
||||
return "Floating Geometry";
|
||||
}
|
||||
|
||||
getFragmentShader() {
|
||||
return fragmentShader;
|
||||
}
|
||||
}
|
||||
138
party-stage/src/visualizers/SynthwaveRunVisualizer.js
Normal file
138
party-stage/src/visualizers/SynthwaveRunVisualizer.js
Normal file
@ -0,0 +1,138 @@
|
||||
import { VisualizerInterface } from './VisualizerInterface.js';
|
||||
|
||||
const fragmentShader = `
|
||||
uniform float u_time;
|
||||
uniform float u_beat;
|
||||
uniform float u_opacity;
|
||||
uniform vec2 u_resolution;
|
||||
uniform vec3 u_colors[16];
|
||||
uniform int u_colorCount;
|
||||
uniform float u_seed;
|
||||
varying vec2 vUv;
|
||||
|
||||
float hash(float n) { return fract(sin(n) * 43758.5453123); }
|
||||
|
||||
float noise(vec2 p) {
|
||||
vec2 i = floor(p);
|
||||
vec2 f = fract(p);
|
||||
f = f * f * (3.0 - 2.0 * f);
|
||||
float n = i.x + i.y * 57.0;
|
||||
return mix(mix(hash(n + 0.0), hash(n + 1.0), f.x),
|
||||
mix(hash(n + 57.0), hash(n + 58.0), f.x), f.y);
|
||||
}
|
||||
|
||||
void main() {
|
||||
// LED Grid Setup
|
||||
float ledCountX = u_resolution.x;
|
||||
float ledCountY = u_resolution.y;
|
||||
vec2 gridUV = vec2(vUv.x * ledCountX, vUv.y * ledCountY);
|
||||
vec2 gridDeriv = fwidth(gridUV);
|
||||
float gridDensity = max(gridDeriv.x, gridDeriv.y);
|
||||
float blurFactor = smoothstep(0.3, 0.8, gridDensity);
|
||||
vec2 cell = fract(gridUV);
|
||||
vec2 uv = mix((floor(gridUV) + 0.5) / vec2(ledCountX, ledCountY), vUv, blurFactor);
|
||||
|
||||
float distMask = distance(cell, vec2(0.5));
|
||||
float edgeSoftness = clamp(gridDensity * 1.5, 0.0, 0.5);
|
||||
float mask = 1.0 - smoothstep(0.35 - edgeSoftness, 0.45 + edgeSoftness, distMask);
|
||||
mask = mix(mask, 1.0, blurFactor);
|
||||
|
||||
// --- Synthwave Logic ---
|
||||
vec2 p = uv * 2.0 - 1.0;
|
||||
p.x *= u_resolution.x / u_resolution.y;
|
||||
|
||||
float t = u_time * 1.5 + u_seed;
|
||||
float beat = u_beat;
|
||||
|
||||
// Colors
|
||||
vec3 colorMain = vec3(1.0, 0.0, 0.8); // Pink/Magenta
|
||||
vec3 colorSec = vec3(0.0, 0.8, 1.0); // Cyan
|
||||
vec3 colorBg = vec3(0.02, 0.0, 0.05);
|
||||
|
||||
if (u_colorCount >= 2) {
|
||||
colorMain = u_colors[0];
|
||||
colorSec = u_colors[int(mod(1.0, float(u_colorCount)))];
|
||||
}
|
||||
|
||||
vec3 finalColor = colorBg;
|
||||
|
||||
// Horizon
|
||||
float horizon = 0.0;
|
||||
|
||||
// 1. Perspective Grid (Ground)
|
||||
if (p.y < horizon) {
|
||||
float perspective = 1.0 / (horizon - p.y + 0.05);
|
||||
vec2 gridP = vec2(p.x * perspective, perspective + t * 2.0);
|
||||
|
||||
float gridLines = smoothstep(0.05, 0.0, abs(fract(gridP.x + 0.5) - 0.5)) +
|
||||
smoothstep(0.05, 0.0, abs(fract(gridP.y + 0.5) - 0.5));
|
||||
|
||||
finalColor = mix(finalColor, colorSec, gridLines * 0.5 * (0.5 + beat));
|
||||
|
||||
// Road highlight
|
||||
if (abs(p.x * perspective) < 0.8) {
|
||||
finalColor += colorMain * 0.2 * perspective;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Wireframe Mountains
|
||||
float mountainHeight = 0.0;
|
||||
if (p.y > horizon - 0.1) {
|
||||
float side = sign(p.x);
|
||||
float mountainX = abs(p.x) - 0.5;
|
||||
if (mountainX > 0.0) {
|
||||
float mNoise = noise(vec2(mountainX * 2.0 + t * 0.1, u_seed));
|
||||
mountainHeight = mNoise * 0.6 * smoothstep(0.0, 0.5, mountainX);
|
||||
|
||||
if (p.y - horizon < mountainHeight) {
|
||||
float distToEdge = abs(p.y - horizon - mountainHeight);
|
||||
float wireframe = smoothstep(0.02, 0.0, distToEdge);
|
||||
|
||||
// Internal wireframe lines
|
||||
wireframe += smoothstep(0.01, 0.0, abs(fract((p.y - horizon) * 10.0) - 0.5));
|
||||
wireframe += smoothstep(0.01, 0.0, abs(fract(mountainX * 10.0) - 0.5));
|
||||
|
||||
finalColor = mix(finalColor, colorMain, wireframe * 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Retro Sun
|
||||
vec2 sunPos = vec2(0.0, 0.1);
|
||||
float sunRad = 0.4;
|
||||
float distToSun = length(p - sunPos);
|
||||
if (distToSun < sunRad && p.y > horizon) {
|
||||
// Scanlines / Cutouts
|
||||
float scanline = sin((p.y - t * 0.1) * 50.0);
|
||||
if (scanline < mix(0.8, -1.0, (p.y - sunPos.y + sunRad) / (sunRad * 2.0))) {
|
||||
float grad = 1.0 - (distToSun / sunRad);
|
||||
vec3 sunCol = mix(colorMain, vec3(1.0, 0.8, 0.0), p.y * 2.0);
|
||||
finalColor = mix(finalColor, sunCol, 0.8 + beat * 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Car Silhouette (Simplified)
|
||||
vec2 carP = p - vec2(0.0, -0.75);
|
||||
carP.x = abs(carP.x);
|
||||
if (carP.y > -0.1 && carP.y < 0.1 && carP.x < 0.25) {
|
||||
float body = step(carP.x, 0.25) * step(abs(carP.y), 0.05);
|
||||
float cabin = step(carP.x, 0.15) * step(carP.y, 0.1) * step(0.0, carP.y);
|
||||
if (body + cabin > 0.0) {
|
||||
finalColor = mix(finalColor, vec3(0.0), 0.9);
|
||||
finalColor += colorSec * 0.5 * body * (smoothstep(0.0, 0.05, abs(carP.x - 0.2))); // Tail lights / glow
|
||||
}
|
||||
}
|
||||
|
||||
gl_FragColor = vec4(finalColor, mask * u_opacity);
|
||||
}
|
||||
`;
|
||||
|
||||
export class SynthwaveRunVisualizer extends VisualizerInterface {
|
||||
getName() {
|
||||
return "Synthwave Run";
|
||||
}
|
||||
|
||||
getFragmentShader() {
|
||||
return fragmentShader;
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,13 @@
|
||||
import { ClassicVisualizer } from './ClassicVisualizer.js';
|
||||
import { NebulaVisualizer } from './NebulaVisualizer.js';
|
||||
import { FloatingShapesVisualizer } from './FloatingShapesVisualizer.js';
|
||||
import { SynthwaveRunVisualizer } from './SynthwaveRunVisualizer.js';
|
||||
|
||||
export const visualizerLibrary = [
|
||||
new ClassicVisualizer(),
|
||||
new NebulaVisualizer(),
|
||||
new FloatingShapesVisualizer(),
|
||||
new SynthwaveRunVisualizer(),
|
||||
];
|
||||
|
||||
export default visualizerLibrary;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user