diff --git a/frontend/shaders/compositor.frag b/frontend/shaders/compositor.frag new file mode 100644 index 0000000..ae2ebfa --- /dev/null +++ b/frontend/shaders/compositor.frag @@ -0,0 +1,195 @@ +precision highp float; + +// Textures +uniform sampler2D u_currentImage; +uniform sampler2D u_nextImage; + +// Transition +uniform float u_blend; // 0=current, 1=next +uniform int u_transitionMode; // 0=crossfade, 1=dissolve, 2=glitch_cut, 3=melt_morph + +// Shader params (from escalation engine, all 0-1) +uniform float u_morphSpeed; +uniform float u_shaderSeverity; +uniform float u_noiseLevel; +uniform float u_time; + +// Scare flash +uniform float u_flashIntensity; // 0=none, 1=full +uniform int u_flashType; // 0=white, 1=inversion, 2=face_flash + +// Vignette and overlay +uniform float u_vignetteStrength; +uniform vec3 u_colorTint; // palette color shift + +varying vec2 v_uv; + +// --- Noise functions --- + +float hash(vec2 p) { + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); +} + +float noise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + float a = hash(i); + float b = hash(i + vec2(1.0, 0.0)); + float c = hash(i + vec2(0.0, 1.0)); + float d = hash(i + vec2(1.0, 1.0)); + vec2 u = f * f * (3.0 - 2.0 * f); + return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y; +} + +// --- Effect functions --- + +vec2 meshWarp(vec2 uv, float severity, float time) { + float freq = 3.0 + severity * 8.0; + float amp = 0.002 + severity * 0.03; + uv.x += sin(uv.y * freq + time * 0.5) * amp; + uv.y += cos(uv.x * freq + time * 0.7) * amp; + return uv; +} + +vec3 chromaticAberration(sampler2D tex, vec2 uv, float severity) { + float offset = 0.001 + severity * 0.015; + float r = texture2D(tex, uv + vec2(offset, 0.0)).r; + float g = texture2D(tex, uv).g; + float b = texture2D(tex, uv - vec2(offset, 0.0)).b; + return vec3(r, g, b); +} + +vec2 meltEffect(vec2 uv, float severity, float time) { + float melt = severity * 0.05 * noise(vec2(uv.x * 10.0, time * 0.3)); + uv.y += melt; + return uv; +} + +vec2 glitchEffect(vec2 uv, float severity, float time) { + float glitchLine = step(0.98 - severity * 0.15, hash(vec2(floor(uv.y * 50.0), floor(time * 8.0)))); + uv.x += glitchLine * (hash(vec2(time, uv.y)) - 0.5) * severity * 0.1; + return uv; +} + +float scanlines(vec2 uv, float time) { + return 0.95 + 0.05 * sin(uv.y * 800.0 + time * 2.0); +} + +float filmGrain(vec2 uv, float time, float amount) { + return 1.0 - amount * 0.5 * (hash(uv * 1000.0 + time) - 0.5); +} + +float vignette(vec2 uv, float strength) { + vec2 center = uv - 0.5; + float dist = length(center); + return 1.0 - smoothstep(0.3, 0.9, dist) * strength; +} + +float pulse(float time, float severity) { + return 1.0 + sin(time * (1.0 + severity * 3.0)) * severity * 0.1; +} + +// --- Transition functions --- + +vec3 transitionCrossfade(vec2 uv, float blend) { + vec3 a = texture2D(u_currentImage, uv).rgb; + vec3 b = texture2D(u_nextImage, uv).rgb; + return mix(a, b, blend); +} + +vec3 transitionDissolve(vec2 uv, float blend) { + vec3 a = texture2D(u_currentImage, uv).rgb; + vec3 b = texture2D(u_nextImage, uv).rgb; + // Dissolve through black + float mid = 0.5; + if (blend < mid) { + return a * (1.0 - blend / mid); + } else { + return b * ((blend - mid) / (1.0 - mid)); + } +} + +vec3 transitionGlitchCut(vec2 uv, float blend, float time) { + // Hard cut with glitch artifacts + float threshold = 0.5 + 0.1 * sin(time * 20.0); + vec3 img = blend < threshold + ? texture2D(u_currentImage, uv).rgb + : texture2D(u_nextImage, uv).rgb; + // Add RGB split at transition point + if (abs(blend - threshold) < 0.1) { + img = chromaticAberration(blend < threshold ? u_currentImage : u_nextImage, uv, 0.8); + } + return img; +} + +vec3 transitionMeltMorph(vec2 uv, float blend, float time) { + vec2 meltUV = uv; + meltUV.y += blend * 0.1 * noise(vec2(uv.x * 5.0, time)); + vec3 a = texture2D(u_currentImage, meltUV).rgb; + vec3 b = texture2D(u_nextImage, uv).rgb; + return mix(a, b, smoothstep(0.3, 0.7, blend)); +} + +// --- Main --- + +void main() { + vec2 uv = v_uv; + float severity = u_shaderSeverity; + + // Apply distortion effects + uv = meshWarp(uv, severity * u_morphSpeed, u_time); + uv = meltEffect(uv, severity * 0.5, u_time); + uv = glitchEffect(uv, severity, u_time); + + // Clamp UV to prevent sampling outside texture + uv = clamp(uv, 0.0, 1.0); + + // Image transition + vec3 color; + if (u_transitionMode == 0) { + color = transitionCrossfade(uv, u_blend); + } else if (u_transitionMode == 1) { + color = transitionDissolve(uv, u_blend); + } else if (u_transitionMode == 2) { + color = transitionGlitchCut(uv, u_blend, u_time); + } else { + color = transitionMeltMorph(uv, u_blend, u_time); + } + + // Chromatic aberration on composited image + if (severity > 0.1) { + vec3 aberrated = chromaticAberration(u_currentImage, uv, severity); + color = mix(color, aberrated, severity * 0.3); + } + + // Color tint / palette shift + color = mix(color, color * u_colorTint, severity * 0.3); + + // Pulse brightness + color *= pulse(u_time, severity); + + // Film grain / noise + color *= filmGrain(v_uv, u_time, u_noiseLevel); + + // Scanlines + color *= scanlines(v_uv, u_time); + + // Vignette + color *= vignette(v_uv, 0.5 + severity * 0.5); + + // Flash effects + if (u_flashIntensity > 0.0) { + if (u_flashType == 0) { + // White-out + color = mix(color, vec3(1.0), u_flashIntensity); + } else if (u_flashType == 1) { + // Inversion + color = mix(color, 1.0 - color, u_flashIntensity); + } else { + // Face flash (red tint) + color = mix(color, vec3(1.0, 0.0, 0.0), u_flashIntensity * 0.7); + } + } + + gl_FragColor = vec4(color, 1.0); +}