/** * Файл содержит все исходные коды шейдеров для проекта Matrix. * Разделение помогает фокусироваться на графике отдельно от логики. */ export const vsMatrix = `#version 300 es precision mediump float; in vec2 a_quad; in vec2 a_position; in float a_charIdx; in float a_atlasRow; in float a_scale; uniform vec2 u_resolution; uniform float u_fontSize; out vec2 v_uv; out float v_atlasRow; void main() { float size = u_fontSize * a_scale; vec2 pos = a_position + a_quad * size; vec2 zeroToOne = pos / u_resolution; vec2 zeroToTwo = zeroToOne * 2.0; vec2 clipSpace = zeroToTwo - 1.0; gl_Position = vec4(clipSpace * vec2(1.0, -1.0), 0.0, 1.0); v_uv = vec2(a_charIdx * u_fontSize + a_quad.x * u_fontSize, a_quad.y * u_fontSize); v_atlasRow = a_atlasRow; }`.trim(); export const vsOverlay = `#version 300 es precision mediump float; in vec2 a_position; out vec2 v_uv; void main() { v_uv = a_position * 0.5 + 0.5; gl_Position = vec4(a_position, 0.0, 1.0); }`.trim(); export const vsGlitch = `#version 300 es in vec2 a_quad; in vec4 a_glitchData; in float a_colorType; uniform vec2 u_resolution; out float v_colorType; void main() { vec2 originalSize = vec2(a_glitchData.z, a_glitchData.w); vec2 newSize = originalSize * 3.0; vec2 offset = (newSize - originalSize) * 0.5; vec2 pixelPos = vec2(a_glitchData.x, a_glitchData.y) - offset + a_quad * newSize; vec2 zeroToOne = pixelPos / u_resolution; vec2 clipSpace = (zeroToOne * 2.0) - 1.0; gl_Position = vec4(clipSpace * vec2(1.0, -1.0), 0.0, 1.0); v_colorType = a_colorType; }`.trim(); export const fsMatrix = `#version 300 es precision mediump float; in vec2 v_uv; in float v_atlasRow; uniform sampler2D u_atlas; uniform float u_fontSize; uniform vec2 u_atlasSize; out vec4 outColor; void main() { float yOffset = v_atlasRow * u_fontSize; vec2 finalUV = vec2(v_uv.x, v_uv.y + yOffset) / u_atlasSize; vec4 texColor = texture(u_atlas, finalUV); if (texColor.a < 0.001) discard; outColor = texColor; }`.trim(); export const fsGlitch = `#version 300 es precision mediump float; in float v_colorType; uniform vec3 u_themeMain; uniform vec3 u_themeBg; out vec4 outColor; void main() { int type = int(v_colorType + 0.1); if (type == 0) { outColor = vec4(u_themeBg, 1.0); } else if (type == 1) { outColor = vec4(u_themeMain, 0.9); } else if (type == 2) { outColor = vec4(vec3(1.0) - u_themeMain, 0.7); } else if (type == 3) { outColor = vec4(1.0, 1.0, 1.0, 0.95); } else if (type == 4) { outColor = vec4(1.0, 0.08, 0.31, 0.95); } else { discard; } }`.trim(); export const postProcessSource = `#version 300 es precision highp float; in vec2 v_uv; out vec4 outColor; uniform sampler2D u_mainTex; uniform float u_time; uniform vec2 u_resolution; uniform float u_eyeClosedness; uniform float u_isVpnOn; uniform float u_eyeActive; uniform vec2 u_eyePosL; uniform vec2 u_eyePosR; uniform vec2 u_gaze; uniform vec3 u_shockwave; uniform float u_isLowQuality; uniform float u_distortion; uniform float u_scanlines; uniform float u_noise; uniform float u_isDarkMode; uniform float u_vpnTimer; // Таймер анимации перехода при нажатии float hash(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); } vec2 crt_coords(vec2 uv, float bend) { uv = uv * 2.0 - 1.0; vec2 offset = abs(uv.yx) / vec2(bend); uv = uv + uv * offset * offset; return uv * 0.5 + 0.5; } // ============================================================================ // КИБЕРНЕТИЧЕСКИЙ ГЛАЗ V8.2: SYNTHWAVE LASHES & REALITY DISTORTION // ============================================================================ void applyClassicEye(vec2 uv, vec2 eyePosNormalized, vec2 gaze, float rawClosed, bool isVpn, bool isRightEye, inout vec3 col) { vec2 pixelPos = uv * u_resolution; vec2 center = eyePosNormalized * u_resolution; vec2 p = pixelPos - center; float scale = u_resolution.x < 500.0 ? 1.0 : (u_resolution.x < 800.0 ? 1.1 : 1.2); bool isLight = u_isDarkMode < 0.5; // --- ПАЛИТРА ИЗ ТЕМЫ (TAILWIND CSS) --- vec3 themePrimary = vec3(0.545, 0.239, 1.0); // #8B3DFF (Сочный фиолетовый) vec3 themeSecondary = vec3(0.0, 0.898, 0.949); // #00E5F2 (Светлый циан) vec3 themeBgDark = vec3(0.043, 0.051, 0.090); // #0B0D17 (Темный фон) vec3 themeBgLight = vec3(0.973, 0.980, 0.988); // #F8FAFC (Светлый фон) vec3 themeAccent = vec3(1.0, 0.15, 0.5); // Розовый для классического Outrun солнца // VPN State: Более агрессивные и смещенные цвета vec3 colorPurple = isVpn ? vec3(0.8, 0.0, 1.0) : themePrimary; vec3 colorCyan = isVpn ? vec3(1.0, 0.1, 0.4) : themeSecondary; vec3 eyeScleraColor = isLight ? themeBgLight : themeBgDark; vec3 eyeLinerColor = isLight ? themePrimary * 0.3 : vec3(0.01); // --- ГЕОМЕТРИЯ: МИНДАЛЕВИДНАЯ ФОРМА ГЛАЗА --- float baseWidth = 65.0 * scale; float baseHeight = 24.0 * scale; float xNorm = clamp(p.x / baseWidth, -1.0, 1.0); float curve = baseHeight * cos(xNorm * 1.57079632); // PI/2 float bounce = sin(rawClosed * 3.1415 * 2.5) * 0.05 * sin(rawClosed * 3.1415); float closed = clamp(rawClosed - bounce, 0.0, 1.0); float currentHeight = curve * (1.0 - closed); float eyeSDF = abs(p.y) - currentHeight; float horizontalFade = smoothstep(1.05, 0.95, abs(p.x / baseWidth)); if (horizontalFade == 0.0) return; // --- ВНЕШНИЙ КОНТУР И СВЕЧЕНИЕ С РЕСНИЦАМИ --- if (eyeSDF > 0.0) { float glow = exp(-eyeSDF / (6.0 * scale)) * horizontalFade; if (isLight) { col = mix(col, colorPurple, glow * (isVpn ? 0.6 : 0.25)); } else { col += colorPurple * glow * (isVpn ? 0.7 : 0.4); } // Жесткая подводка float linerMask = smoothstep(2.5 * scale, 0.0, eyeSDF) * horizontalFade; col = mix(col, eyeLinerColor, linerMask); // Тонкий цветной кант float edge = smoothstep(1.5 * scale, 0.0, eyeSDF) * horizontalFade; vec3 edgeColor = isLight ? themeSecondary : themeAccent; col = mix(col, edgeColor, edge * 0.9); // --- АГРЕССИВНЫЕ КИБЕР-РЕСНИЦЫ (THREATENING LASHES) --- float lashFreq = 18.0; // Частота шипов float ySign = sign(p.y); // Смещение фрактала для верхних и нижних ресниц float lashFract = fract(xNorm * lashFreq + ySign * 0.3); // Резкая треугольная форма (шипы/лезвия) float spikeShape = smoothstep(0.2, 0.0, abs(lashFract - 0.5)); // Длина шипа с добавлением хаоса float maxSpikeLen = (10.0 + hash(vec2(floor(xNorm * lashFreq), ySign)) * 14.0) * scale * horizontalFade; if (isVpn) maxSpikeLen *= 1.4; // Угрожающе вытягиваются в VPN режиме float lashMask = smoothstep(1.5 * scale, 0.0, eyeSDF - maxSpikeLen * spikeShape); if (lashMask > 0.0) { // Основание ресницы темное, кончик светится неоном vec3 lashBaseCol = isLight ? themePrimary * 0.8 : vec3(0.02); vec3 lashTipCol = isVpn ? themeAccent : themeSecondary; float tipGradient = smoothstep(0.0, maxSpikeLen, eyeSDF); vec3 finalLashCol = mix(lashBaseCol, lashTipCol, tipGradient * 1.8); col = mix(col, finalLashCol, lashMask); } return; } // --- ВНУТРЕННЕЕ ПРОСТРАНСТВО (SCLERA И IRIS) --- vec3 innerCol = eyeScleraColor; // 1. Synthwave Сетка (Grid) с перспективой float gridSpeed = isVpn ? u_time * 12.0 : u_time * 3.0; float perspY = p.y / max(currentHeight, 0.1 * scale); float gridY = fract((perspY * 8.0 * scale - gridSpeed) / (4.0 * scale)); float gridLines = smoothstep(0.85, 1.0, gridY); float gridX = fract((p.x - (isVpn ? u_time * 15.0 : 0.0)) / (8.0 * scale)); float gridLinesX = smoothstep(0.9, 1.0, gridX); float gridIntensity = gridLines + gridLinesX; vec3 gridActiveColor = isLight ? themePrimary * 0.6 : colorPurple * 0.5; float gridMix = isLight ? (gridIntensity * 0.5) : (gridIntensity * (0.2 + 0.8 * abs(perspY))); innerCol = mix(innerCol, gridActiveColor, gridMix); // 2. Радужка и Зрачок (Векторный ретро-стиль) vec2 gazeOffset = gaze * vec2(15.0, 6.0) * scale; vec2 pupilP = p - gazeOffset; float r = length(pupilP); float irisRadius = 18.0 * scale; float irisMask = smoothstep(irisRadius, irisRadius - 1.5 * scale, r); if (irisMask > 0.0) { innerCol = mix(innerCol, isLight ? themeBgLight : vec3(0.01), irisMask); float stripeDensity = isVpn ? 2.0 : 3.5; float sunStripes = step(0.4, fract((pupilP.y - u_time * 4.0) / (stripeDensity * scale))); vec3 sunGradient = mix(colorCyan, themeAccent, smoothstep(-irisRadius, irisRadius, pupilP.y)); float sunGlow = smoothstep(irisRadius, 0.0, r); vec3 sunBaseColor; if (isLight) { sunBaseColor = sunGradient * (sunStripes * 0.5 + 0.5); } else { sunBaseColor = sunGradient * (sunStripes * 0.7 + 0.3) * sunGlow * 2.2; } innerCol = mix(innerCol, sunBaseColor, irisMask); float rotAngle = u_time * 1.5 * (isRightEye ? 1.0 : -1.0); float s = sin(rotAngle), c = cos(rotAngle); vec2 rotPupil = vec2(pupilP.x * c - pupilP.y * s, pupilP.x * s + pupilP.y * c); float pupilRadius = isVpn ? 7.0 * scale : 4.5 * scale; if (isRightEye) { float dTri = max(abs(rotPupil.x) * 0.866025 + rotPupil.y * 0.5, -rotPupil.y) - pupilRadius; float triMask = smoothstep(1.5*scale, 0.0, dTri); innerCol = mix(innerCol, isLight ? themeBgLight : vec3(0.0), triMask); vec3 triNeon = isLight ? themePrimary : colorCyan * 2.5; innerCol = mix(innerCol, triNeon, smoothstep(2.0*scale, 0.0, abs(dTri)) * triMask); } else { float dRom = abs(rotPupil.x) + abs(rotPupil.y) - pupilRadius * 1.2; float romMask = smoothstep(1.5*scale, 0.0, dRom); innerCol = mix(innerCol, isLight ? themeBgLight : vec3(0.0), romMask); vec3 romNeon = isLight ? themePrimary : themeAccent * 2.5; innerCol = mix(innerCol, romNeon, smoothstep(2.0*scale, 0.0, abs(dRom)) * romMask); } float specH = smoothstep(1.5*scale, 0.0, abs(pupilP.y - 5.0*scale)) * smoothstep(8.0*scale, 0.0, abs(pupilP.x + 4.0*scale)); innerCol = mix(innerCol, vec3(1.0), specH * irisMask * (isLight ? 0.8 : 1.0)); } // 3. Сканер и помехи if (isVpn) { float scanY = sin(u_time * 4.0) * baseHeight; float scanMask = exp(-abs(p.y - scanY) / (1.5 * scale)); innerCol = mix(innerCol, colorCyan, scanMask * (isLight ? 0.4 : 0.6)); } float shadowOcclusion = smoothstep(0.0, 10.0 * scale, currentHeight - abs(p.y)); if (isLight) { innerCol = mix(innerCol, themePrimary * 0.3, (1.0 - shadowOcclusion) * 0.15); } else { innerCol *= (0.2 + 0.8 * shadowOcclusion); } col = innerCol; } void main() { vec2 baseUv = (u_isLowQuality > 0.5) ? v_uv : crt_coords(v_uv, u_distortion); bool isLight = u_isDarkMode < 0.5; float scale = u_resolution.x < 500.0 ? 1.0 : (u_resolution.x < 800.0 ? 1.1 : 1.2); // 1. Преломление линзы (Lens Refraction) - применяется только к фону vec2 lensOffset = vec2(0.0); if (u_eyeActive > 0.5 && u_isLowQuality < 0.5) { vec2 eL = u_eyePosL / u_resolution; eL.y = 1.0 - eL.y; vec2 eR = u_eyePosR / u_resolution; eR.y = 1.0 - eR.y; vec2 pL = baseUv * u_resolution - eL * u_resolution; vec2 pR = baseUv * u_resolution - eR * u_resolution; float rL = length(pL); float rR = length(pR); float lensRad = 32.0 * scale; float refrForce = (u_isVpnOn > 0.5) ? 0.15 : 0.04; if (rL < lensRad) { float profile = smoothstep(lensRad, 0.0, rL); if (u_isVpnOn > 0.5) profile = floor(profile * 4.0) / 4.0; lensOffset -= (pL / u_resolution) * refrForce * profile; } if (rR < lensRad) { float profile = smoothstep(lensRad, 0.0, rR); if (u_isVpnOn > 0.5) profile = floor(profile * 4.0) / 4.0; lensOffset -= (pR / u_resolution) * refrForce * profile; } } // 2. Обычные глитчи и Shockwave vec2 glitchOffset = vec2(0.0); vec2 grid = floor(baseUv * u_resolution / vec2(80.0, 40.0)); float glitchVal = hash(grid + floor(u_time * 12.0)); if (glitchVal > 0.998) glitchOffset.x += (hash(grid) - 0.5) * 0.05; float tearHash = hash(vec2(floor(u_time * 8.0), 13.37)); float isTearing = step(0.97, tearHash); float caOffset = 0.0; if (isTearing > 0.5) { float tearY = hash(vec2(floor(u_time * 8.0), 24.68)); float tearBand = smoothstep(0.04, 0.0, abs(baseUv.y - tearY)); glitchOffset.x += tearBand * (hash(vec2(baseUv.y * 100.0, u_time)) - 0.5) * 0.2; caOffset = tearBand * 0.05; } // --- СИНТВЕЙВ ПОДЕРГИВАНИЕ (Легкое смещение при включении VPN) --- float vpnTear = 0.0; if (u_vpnTimer > 0.0) { float intensity = smoothstep(0.0, 1.0, u_vpnTimer); // Более тонкие блоки float tearBlock = floor(baseUv.y * 20.0 + u_time * 15.0); float blockHash = hash(vec2(tearBlock, floor(u_time * 10.0))); // Смещение слабое и аккуратное if (blockHash > 0.4) { // Множитель снижен с 1.5 до 0.08 (легкое подергивание вместо разрыва) float shift = (hash(vec2(tearBlock, 2.0)) - 0.5) * 0.08 * intensity; glitchOffset.x += shift; vpnTear = intensity; } } float wave = 0.0; vec2 swDir = vec2(0.0); if (u_shockwave.z > 0.01) { vec2 aspect = vec2(u_resolution.x / u_resolution.y, 1.0); float dist = distance((baseUv + glitchOffset) * aspect, u_shockwave.xy * aspect); wave = smoothstep(u_shockwave.z + 0.015, u_shockwave.z, dist) * smoothstep(u_shockwave.z - 0.015, u_shockwave.z, dist); swDir = normalize((baseUv + glitchOffset) - u_shockwave.xy); glitchOffset -= swDir * wave * 0.015; } // Разделяем координаты: vec2 bgUv = baseUv + lensOffset + glitchOffset; vec2 eyeUv = baseUv + glitchOffset; vec3 color = texture(u_mainTex, bgUv).rgb; vec3 themePrimary = vec3(0.545, 0.239, 1.0); vec3 themeSecondary = vec3(0.0, 0.898, 0.949); vec3 themeAccent = vec3(1.0, 0.15, 0.5); // Мягкий глитч-эффект if (vpnTear > 0.0) { // Легкое разъезжание каналов (уменьшено с 0.12 до 0.015) color.r = texture(u_mainTex, bgUv + vec2(0.015 * vpnTear, 0.0)).r; color.g = texture(u_mainTex, bgUv).g; color.b = texture(u_mainTex, bgUv - vec2(0.015 * vpnTear, 0.0)).b; // Синтвейв полосы float colorBand = fract(baseUv.y * 3.0 - u_time * 10.0); vec3 glitchColor = mix(themeSecondary, themeAccent, step(0.33, colorBand)); glitchColor = mix(glitchColor, themePrimary, step(0.66, colorBand)); // Мягкое перекрытие (сильно убавлена непрозрачность) color = mix(color, glitchColor, vpnTear * (isLight ? 0.25 : 0.35)); color += glitchColor * vpnTear * 0.15; } if (caOffset > 0.0) { color.r = texture(u_mainTex, bgUv + vec2(caOffset, 0.0)).r; color.b = texture(u_mainTex, bgUv - vec2(caOffset, 0.0)).b; color += (isLight ? themePrimary : vec3(0.7, 0.2, 1.0)) * caOffset * 10.0; } if (wave > 0.0) { color.r = texture(u_mainTex, bgUv + swDir * wave * 0.005).r; color.b = texture(u_mainTex, bgUv - swDir * wave * 0.005).b; } if (u_eyeActive > 0.5) { vec2 eyeL = u_eyePosL / u_resolution; vec2 eyeR = u_eyePosR / u_resolution; eyeL.y = 1.0 - eyeL.y; eyeR.y = 1.0 - eyeR.y; applyClassicEye(eyeUv, eyeL, u_gaze, u_eyeClosedness, u_isVpnOn > 0.5, false, color); applyClassicEye(eyeUv, eyeR, u_gaze, u_eyeClosedness, u_isVpnOn > 0.5, true, color); } if (wave > 0.0) { color += wave * 0.25 * (isLight ? themePrimary : vec3(0.0, 0.9, 1.0)); } float scanline = sin(v_uv.y * u_resolution.y * 2.2) * 0.02; color -= isLight ? scanline * 0.5 : scanline; float vignette = clamp(pow(16.0 * v_uv.x * v_uv.y * (1.0 - v_uv.x) * (1.0 - v_uv.y), 0.25), 0.0, 1.0); outColor = vec4(color * mix(0.9, 1.0, vignette), 1.0); } `.trim();