optimization

This commit is contained in:
2026-04-26 19:59:47 +07:00
parent 865f48964a
commit de16e11f0e
+350 -70
View File
@@ -20,6 +20,12 @@ let frameCounter = 0;
let eyeScale = 1.5; let eyeScale = 1.5;
let interactionTimer = 0; let interactionTimer = 0;
let fpsHistory = [];
let lastFrameTime = 0;
let scaleCheckTimer = 0;
let currentPixelScale = 1.0;
let maxPixelScale = 1.0;
let postProgram; let postProgram;
let postFramebuffer, postTexture; let postFramebuffer, postTexture;
@@ -103,6 +109,88 @@ void main() {
} }
`.trim(); `.trim();
const vsGlitch = `#version 300 es
in vec2 a_quad; // 0..1
in vec4 a_glitchData; // x, y, w, h
in float a_colorType; // 0..4
uniform vec2 u_resolution;
out float v_colorType;
void main() {
// a_glitchData: x = pos.x, y = pos.y, z = width, w = height
vec2 pixelPos = vec2(a_glitchData.x, a_glitchData.y) + a_quad * vec2(a_glitchData.z, a_glitchData.w);
// Переводим пиксели (0..ширина) в пространство 0..1
vec2 zeroToOne = pixelPos / u_resolution;
// Переводим 0..1 в пространство -1..1 (Clip Space)
vec2 clipSpace = (zeroToOne * 2.0) - 1.0;
// Переворачиваем Y, так как в WebGL Y идет вверх
gl_Position = vec4(clipSpace * vec2(1.0, -1.0), 0.0, 1.0);
v_colorType = a_colorType;
}
`.trim();
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); // Добавили epsilon для точности
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();
const vsEye = `#version 300 es
in vec2 a_quadCentered; // -0.5 to 0.5
uniform vec2 u_resolution;
uniform vec2 u_eyePos; // Центр глаза
uniform float u_eyeSize;
out vec2 v_uv;
void main() {
vec2 pos = u_eyePos + a_quadCentered * u_eyeSize;
vec2 zeroToOne = pos / u_resolution;
vec2 clipSpace = (zeroToOne * 2.0) - 1.0;
gl_Position = vec4(clipSpace * vec2(1.0, -1.0), 0.0, 1.0);
v_uv = a_quadCentered + 0.5; // Переводим -0.5..0.5 в 0..1 для текстуры
}
`.trim();
const fsEye = `#version 300 es
precision mediump float;
in vec2 v_uv;
uniform sampler2D u_texture;
out vec4 outColor;
void main() {
vec4 color = texture(u_texture, v_uv);
if (color.a < 0.01) discard;
outColor = color;
}
`.trim();
const postProcessSource = `#version 300 es const postProcessSource = `#version 300 es
precision highp float; precision highp float;
@@ -183,7 +271,6 @@ void main() {
} }
`.trim(); `.trim();
// --- WEBGL STATE ---
let matrixProgram, overlayProgram; let matrixProgram, overlayProgram;
let instanceBuffer, quadBuffer, overlayQuadBuffer; let instanceBuffer, quadBuffer, overlayQuadBuffer;
let vaoMatrix, vaoOverlay; let vaoMatrix, vaoOverlay;
@@ -191,6 +278,11 @@ let uResLoc, uFontLoc, uAtlasSizeLoc;
let overlayTexture; let overlayTexture;
let instanceBufferCapacity = 0; let instanceBufferCapacity = 0;
// ДОБАВЛЕННЫЕ ПЕРЕМЕННЫЕ ДЛЯ ГЛИТЧЕЙ И ГЛАЗ
let glitchProgram, vaoGlitch, glitchInstanceBuffer;
let eyeProgram, vaoEye, quadBufferCentered;
let eyeCanvas, eyeCtx, eyeTexture;
// --- ИНИЦИАЛИЗАЦИЯ WEBGL --- // --- ИНИЦИАЛИЗАЦИЯ WEBGL ---
function compileShader(type, source) { function compileShader(type, source) {
const shader = gl.createShader(type); const shader = gl.createShader(type);
@@ -216,12 +308,11 @@ function createProgram(vsSource, fsSource) {
function initWebGL() { function initWebGL() {
matrixProgram = createProgram(vsMatrix, fsMatrix); matrixProgram = createProgram(vsMatrix, fsMatrix);
overlayProgram = createProgram(vsOverlay, fsOverlay); postProgram = createProgram(vsOverlay, postProcessSource);
glitchProgram = createProgram(vsGlitch, fsGlitch);
// --- VAO для Матрицы --- eyeProgram = createProgram(vsEye, fsEye);
vaoMatrix = gl.createVertexArray();
gl.bindVertexArray(vaoMatrix);
// --- БАЗОВЫЕ КВАДРАТЫ ---
quadBuffer = gl.createBuffer(); quadBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer); gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
gl.bufferData( gl.bufferData(
@@ -230,16 +321,28 @@ function initWebGL() {
gl.STATIC_DRAW, gl.STATIC_DRAW,
); );
quadBufferCentered = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, quadBufferCentered);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
-0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5,
]),
gl.STATIC_DRAW,
);
// --- VAO: МАТРИЦА ---
vaoMatrix = gl.createVertexArray();
gl.bindVertexArray(vaoMatrix);
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
const aQuadLoc = gl.getAttribLocation(matrixProgram, "a_quad"); const aQuadLoc = gl.getAttribLocation(matrixProgram, "a_quad");
gl.enableVertexAttribArray(aQuadLoc); gl.enableVertexAttribArray(aQuadLoc);
gl.vertexAttribPointer(aQuadLoc, 2, gl.FLOAT, false, 0, 0); gl.vertexAttribPointer(aQuadLoc, 2, gl.FLOAT, false, 0, 0);
gl.vertexAttribDivisor(aQuadLoc, 0);
instanceBuffer = gl.createBuffer(); instanceBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer); gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
instanceBufferCapacity = 20000; instanceBufferCapacity = 20000;
gl.bufferData(gl.ARRAY_BUFFER, instanceBufferCapacity * 4, gl.DYNAMIC_DRAW); gl.bufferData(gl.ARRAY_BUFFER, instanceBufferCapacity * 4, gl.DYNAMIC_DRAW);
const strideBytes = 5 * 4; const strideBytes = 5 * 4;
const aPosLoc = gl.getAttribLocation(matrixProgram, "a_position"); const aPosLoc = gl.getAttribLocation(matrixProgram, "a_position");
gl.enableVertexAttribArray(aPosLoc); gl.enableVertexAttribArray(aPosLoc);
@@ -261,12 +364,38 @@ function initWebGL() {
gl.vertexAttribPointer(aScaleLoc, 1, gl.FLOAT, false, strideBytes, 4 * 4); gl.vertexAttribPointer(aScaleLoc, 1, gl.FLOAT, false, strideBytes, 4 * 4);
gl.vertexAttribDivisor(aScaleLoc, 1); gl.vertexAttribDivisor(aScaleLoc, 1);
gl.bindVertexArray(null); // --- VAO: ГЛИТЧИ ---
vaoGlitch = gl.createVertexArray();
gl.bindVertexArray(vaoGlitch);
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
const aQuadGlitch = gl.getAttribLocation(glitchProgram, "a_quad");
gl.enableVertexAttribArray(aQuadGlitch);
gl.vertexAttribPointer(aQuadGlitch, 2, gl.FLOAT, false, 0, 0);
// --- VAO для Overlay --- glitchInstanceBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, glitchInstanceBuffer);
const strideG = 5 * 4; // БЫЛО 6, СТАЛО 5 (согласно Rust структуре)
const aDataLoc = gl.getAttribLocation(glitchProgram, "a_glitchData");
gl.enableVertexAttribArray(aDataLoc);
gl.vertexAttribPointer(aDataLoc, 4, gl.FLOAT, false, strideG, 0);
gl.vertexAttribDivisor(aDataLoc, 1);
const aColorLoc = gl.getAttribLocation(glitchProgram, "a_colorType");
gl.enableVertexAttribArray(aColorLoc);
gl.vertexAttribPointer(aColorLoc, 1, gl.FLOAT, false, strideG, 4 * 4);
gl.vertexAttribDivisor(aColorLoc, 1);
// --- VAO: ГЛАЗА (Sprite) ---
vaoEye = gl.createVertexArray();
gl.bindVertexArray(vaoEye);
gl.bindBuffer(gl.ARRAY_BUFFER, quadBufferCentered);
const aQuadEye = gl.getAttribLocation(eyeProgram, "a_quadCentered");
gl.enableVertexAttribArray(aQuadEye);
gl.vertexAttribPointer(aQuadEye, 2, gl.FLOAT, false, 0, 0);
// --- VAO: POST PROCESS (Overlay Quad) ---
vaoOverlay = gl.createVertexArray(); vaoOverlay = gl.createVertexArray();
gl.bindVertexArray(vaoOverlay); gl.bindVertexArray(vaoOverlay);
overlayQuadBuffer = gl.createBuffer(); overlayQuadBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, overlayQuadBuffer); gl.bindBuffer(gl.ARRAY_BUFFER, overlayQuadBuffer);
gl.bufferData( gl.bufferData(
@@ -274,40 +403,115 @@ function initWebGL() {
new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
gl.STATIC_DRAW, gl.STATIC_DRAW,
); );
const aPosLocOver = gl.getAttribLocation(postProgram, "a_position");
const aPosLocOver = gl.getAttribLocation(overlayProgram, "a_position");
gl.enableVertexAttribArray(aPosLocOver); gl.enableVertexAttribArray(aPosLocOver);
gl.vertexAttribPointer(aPosLocOver, 2, gl.FLOAT, false, 0, 0); gl.vertexAttribPointer(aPosLocOver, 2, gl.FLOAT, false, 0, 0);
gl.vertexAttribDivisor(aPosLocOver, 0);
gl.bindVertexArray(null); gl.bindVertexArray(null);
// --- Uniforms & Textures --- // --- ТЕКСТУРЫ И БУФЕРЫ ---
uResLoc = gl.getUniformLocation(matrixProgram, "u_resolution"); uResLoc = gl.getUniformLocation(matrixProgram, "u_resolution");
uFontLoc = gl.getUniformLocation(matrixProgram, "u_fontSize"); uFontLoc = gl.getUniformLocation(matrixProgram, "u_fontSize");
uAtlasSizeLoc = gl.getUniformLocation(matrixProgram, "u_atlasSize"); uAtlasSizeLoc = gl.getUniformLocation(matrixProgram, "u_atlasSize");
atlasTexture = gl.createTexture(); atlasTexture = gl.createTexture();
overlayTexture = gl.createTexture();
// ИНИЦИАЛИЗАЦИЯ ПОСТОБРАБОТКИ // Инициализация маленького канваса для глаз (300x300 пикселей)
postProgram = createProgram(vsOverlay, postProcessSource); eyeCanvas = new OffscreenCanvas(300, 300);
eyeCtx = eyeCanvas.getContext("2d", { alpha: true });
eyeTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, eyeTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
postTexture = gl.createTexture(); postTexture = gl.createTexture();
postFramebuffer = gl.createFramebuffer(); postFramebuffer = gl.createFramebuffer();
// Настройка текстуры постобработки (размер установим в resize)
gl.bindTexture(gl.TEXTURE_2D, postTexture); gl.bindTexture(gl.TEXTURE_2D, postTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// Смена настроек WebGL
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
gl.enable(gl.BLEND); gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
} }
function updateDynamicScaling(now) {
if (lastFrameTime === 0) lastFrameTime = now;
const dt = now - lastFrameTime;
lastFrameTime = now;
// Собираем историю последних 60 кадров
fpsHistory.push(dt);
if (fpsHistory.length > 60) fpsHistory.shift();
scaleCheckTimer++;
// Проверяем производительность каждую секунду (60 кадров)
if (scaleCheckTimer >= 60) {
const avgDt = fpsHistory.reduce((a, b) => a + b, 0) / fpsHistory.length;
let needsResize = false;
// Если FPS падает ниже ~45-50 (кадр дольше 22 мс)
if (avgDt > 22.0 && currentPixelScale > 0.5) {
currentPixelScale = Math.max(0.5, currentPixelScale - 0.25);
needsResize = true;
}
// Если летит стабильно 60+ FPS (кадр меньше 15 мс) и мы не на максимуме
else if (avgDt < 15.0 && currentPixelScale < maxPixelScale) {
currentPixelScale = Math.min(maxPixelScale, currentPixelScale + 0.25);
needsResize = true;
}
if (needsResize) {
console.log(`[AutoScaler] Adjusting pixelScale to: ${currentPixelScale}`);
// Применяем новый скейл к буферам WebGL
gl.canvas.width = width * currentPixelScale;
gl.canvas.height = height * currentPixelScale;
gl.bindTexture(gl.TEXTURE_2D, postTexture);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
width * currentPixelScale,
height * currentPixelScale,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
null,
);
// Если используешь overlayCanvas для глаз (см. ниже), его тоже ресайзим
}
scaleCheckTimer = 0;
}
}
function renderSingleEyeToTexture(gazeX, gazeY, dir, isClosed) {
// Очищаем маленький канвас
eyeCtx.clearRect(0, 0, 300, 300);
// Передаем cx = 150, cy = 150 (центр 300x300)
drawEye(eyeCtx, 150, 150, gazeX, gazeY, dir, isClosed);
// Загружаем результат в текстуру
gl.bindTexture(gl.TEXTURE_2D, eyeTexture);
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
// ИСПРАВЛЕНИЕ: Ставим false, чтобы текстура не переворачивалась дважды
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
gl.RGBA,
gl.UNSIGNED_BYTE,
eyeCanvas,
);
}
function buildAtlas() { function buildAtlas() {
atlasCanvas = new OffscreenCanvas(chars.length * fontSize, fontSize * 11); atlasCanvas = new OffscreenCanvas(chars.length * fontSize, fontSize * 11);
atlasCtx = atlasCanvas.getContext("2d", { alpha: true }); atlasCtx = atlasCanvas.getContext("2d", { alpha: true });
@@ -627,26 +831,30 @@ function render(currentTime) {
if (now - lastDrawTime < 16) return; if (now - lastDrawTime < 16) return;
lastDrawTime = now; lastDrawTime = now;
// 1. Физика капель (WASM) // 1. Авто-Скейлер
// Для слабых устройств можно включить троттлинг обратно, если будет греться updateDynamicScaling(now);
// 2. Физика
engine.tick(); engine.tick();
// 3. Подготовка буферов
const targetBuffer = isLowQuality ? null : postFramebuffer; const targetBuffer = isLowQuality ? null : postFramebuffer;
gl.bindFramebuffer(gl.FRAMEBUFFER, targetBuffer); gl.bindFramebuffer(gl.FRAMEBUFFER, targetBuffer);
gl.viewport(0, 0, width * pixelScale, height * pixelScale); gl.viewport(0, 0, width * currentPixelScale, height * currentPixelScale);
const bgR = isDarkMode ? 10 / 255 : 250 / 255; const bgR = isDarkMode ? 10 / 255 : 250 / 255;
const bgG = isDarkMode ? 10 / 255 : 250 / 255; const bgG = isDarkMode ? 10 / 255 : 250 / 255;
const bgB = isDarkMode ? 12 / 255 : 252 / 255; const bgB = isDarkMode ? 12 / 255 : 252 / 255;
// Всегда очищаем реальным цветом фона, а не прозрачностью!
// Это гарантирует, что даже если шейдер заглючит, под ним будет верный цвет.
gl.clearColor(bgR, bgG, bgB, 1.0); gl.clearColor(bgR, bgG, bgB, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT); gl.clear(gl.COLOR_BUFFER_BIT);
// 1.1. РЕНДЕР МАТРИЦЫ (Instanced Rendering)
// ==========================================
// ЭТАП 1: МАТРИЦА (Zero-Copy Instancing)
// ==========================================
const renderLen = engine.render_len(); const renderLen = engine.render_len();
const instanceCount = renderLen / 5; if (renderLen > 0) {
if (instanceCount > 0) { const instanceCount = renderLen / 5;
gl.useProgram(matrixProgram); gl.useProgram(matrixProgram);
gl.bindVertexArray(vaoMatrix); gl.bindVertexArray(vaoMatrix);
@@ -675,45 +883,122 @@ function render(currentTime) {
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount); gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
} }
// 1.2. РЕНДЕР ОВЕРЛЕЯ (Глаза и эффекты) // ==========================================
const needsOverlayUpdate = drawOverlayElements(); // ЭТАП 2: ГЛИТЧИ (Pure WebGL Instancing)
// ==========================================
const glLen = engine.glitch_len();
if (glLen > 0) {
const glitchInstanceCount = glLen / 5;
gl.useProgram(glitchProgram);
gl.bindVertexArray(vaoGlitch);
// КРИТИЧЕСКАЯ ОПТИМИЗАЦИЯ ДЛЯ СЛАБЫХ ПК: // Определяем цвета темы
// Не вызываем рендер оверлея, если он статичен (interactionTimer вышел) let tr = 2 / 255,
const skipOverlay = tg = 132 / 255,
isLowQuality && interactionTimer <= 0 && engine.glitch_len() === 0; tb = 199 / 255; // Blue
if (isVpnOn && isDarkMode) {
if (!skipOverlay) { tr = 139 / 255;
gl.useProgram(overlayProgram); tg = 61 / 255;
gl.bindVertexArray(vaoOverlay); tb = 255 / 255;
gl.activeTexture(gl.TEXTURE1); } else if (!isVpnOn && isDarkMode) {
gl.bindTexture(gl.TEXTURE_2D, overlayTexture); tr = 0;
tg = 229 / 255;
if (needsOverlayUpdate) { tb = 242 / 255;
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); } else if (isVpnOn && !isDarkMode) {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); tr = 109 / 255;
gl.texImage2D( tg = 40 / 255;
gl.TEXTURE_2D, tb = 217 / 255;
0,
gl.RGBA,
gl.RGBA,
gl.UNSIGNED_BYTE,
overlayCanvas,
);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
} }
gl.uniform1i(gl.getUniformLocation(overlayProgram, "u_texture"), 1); gl.uniform3f(
gl.drawArrays(gl.TRIANGLES, 0, 6); gl.getUniformLocation(glitchProgram, "u_themeMain"),
tr,
tg,
tb,
);
gl.uniform3f(
gl.getUniformLocation(glitchProgram, "u_themeBg"),
bgR,
bgG,
bgB,
);
gl.uniform2f(
gl.getUniformLocation(glitchProgram, "u_resolution"),
width,
height,
);
const glDataView = new Float32Array(
wasm.memory.buffer,
engine.glitch_ptr(),
glLen,
);
gl.bindBuffer(gl.ARRAY_BUFFER, glitchInstanceBuffer);
gl.bufferData(gl.ARRAY_BUFFER, glDataView, gl.DYNAMIC_DRAW);
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, glitchInstanceCount);
} }
// 3. ФИНАЛЬНЫЙ ЭТАП (ПОСТОБРАБОТКА) // ==========================================
// Если мы на слабом устройстве — работа закончена, выходим! // ЭТАП 3: ГЛАЗА (Sprite Batching)
// ==========================================
const eyeLen = engine.eye_len();
if (eyeLen >= 6) {
interactionTimer = 120;
const eyeData = new Float32Array(
wasm.memory.buffer,
engine.eye_ptr(),
eyeLen,
);
const eyeBaseX = eyeData[0];
const eyeBaseY = eyeData[1];
// Обновляем текстуру глаз ТОЛЬКО если мы не пропускаем кадр из-за lowQuality
if (!isLowQuality || frameCounter % 2 === 0) {
renderSingleEyeToTexture(eyeData[2], eyeData[3], eyeData[4], eyeData[5]);
}
gl.useProgram(eyeProgram);
gl.bindVertexArray(vaoEye);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, eyeTexture);
gl.uniform1i(gl.getUniformLocation(eyeProgram, "u_texture"), 0);
gl.uniform2f(
gl.getUniformLocation(eyeProgram, "u_resolution"),
width,
height,
);
gl.uniform1f(gl.getUniformLocation(eyeProgram, "u_eyeSize"), 300.0);
const gap = (isMobile ? 75.0 : 80.0) * eyeScale;
// Рисуем ЛЕВЫЙ глаз
gl.uniform2f(
gl.getUniformLocation(eyeProgram, "u_eyePos"),
eyeBaseX - gap,
eyeBaseY,
);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// Рисуем ПРАВЫЙ глаз
gl.uniform2f(
gl.getUniformLocation(eyeProgram, "u_eyePos"),
eyeBaseX + gap,
eyeBaseY,
);
gl.drawArrays(gl.TRIANGLES, 0, 6);
} else if (interactionTimer > 0) {
interactionTimer--;
}
frameCounter++;
// ==========================================
// ЭТАП 4: ПОСТ-ПРОЦЕССИНГ (Для мощных ПК)
// ==========================================
if (isLowQuality) return; if (isLowQuality) return;
// Для мощных ПК переносим результат из скрытого буфера на экран с эффектами
gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.useProgram(postProgram); gl.useProgram(postProgram);
gl.bindVertexArray(vaoOverlay); gl.bindVertexArray(vaoOverlay);
@@ -725,11 +1010,9 @@ function render(currentTime) {
gl.uniform1f(gl.getUniformLocation(postProgram, "u_time"), now / 1000); gl.uniform1f(gl.getUniformLocation(postProgram, "u_time"), now / 1000);
gl.uniform2f( gl.uniform2f(
gl.getUniformLocation(postProgram, "u_resolution"), gl.getUniformLocation(postProgram, "u_resolution"),
width * pixelScale, width * currentPixelScale,
height * pixelScale, height * currentPixelScale,
); );
// Параметры "Киберпанк 1984"
gl.uniform1f(gl.getUniformLocation(postProgram, "u_distortion"), 18.0); gl.uniform1f(gl.getUniformLocation(postProgram, "u_distortion"), 18.0);
gl.uniform1f(gl.getUniformLocation(postProgram, "u_aberration"), 0.008); gl.uniform1f(gl.getUniformLocation(postProgram, "u_aberration"), 0.008);
gl.uniform1f(gl.getUniformLocation(postProgram, "u_scanlines"), 0.15); gl.uniform1f(gl.getUniformLocation(postProgram, "u_scanlines"), 0.15);
@@ -745,7 +1028,6 @@ function render(currentTime) {
gl.drawArrays(gl.TRIANGLES, 0, 6); gl.drawArrays(gl.TRIANGLES, 0, 6);
} }
// --- ОБРАБОТЧИК СООБЩЕНИЙ ОТ REACT --- // --- ОБРАБОТЧИК СООБЩЕНИЙ ОТ REACT ---
self.onmessage = async (e) => { self.onmessage = async (e) => {
const { type, payload } = e.data; const { type, payload } = e.data;
@@ -877,13 +1159,11 @@ self.onmessage = async (e) => {
// А вот в движок передаем только если он уже готов // А вот в движок передаем только если он уже готов
if (engine) { if (engine) {
if (typeof engine.set_vpn_status === "function") if (typeof engine.set_vpn_status === "function") {
engine.set_vpn_status(isVpnOn); engine.set_vpn_status(isVpnOn);
}
buildAtlas(); buildAtlas();
} }
if (typeof engine.set_vpn_status === "function")
engine.set_vpn_status(isVpnOn);
buildAtlas();
} else if (type === "MOUSE_MOVE") { } else if (type === "MOUSE_MOVE") {
if (engine) engine.update_mouse(payload.x, payload.y); if (engine) engine.update_mouse(payload.x, payload.y);
} else if (type === "DRAW_START") { } else if (type === "DRAW_START") {