optimization and stylizing

This commit is contained in:
2026-04-26 19:29:34 +07:00
parent 2664f00741
commit 865f48964a
3 changed files with 78 additions and 58 deletions
+2 -2
View File
@@ -74,7 +74,7 @@ export const NetrunnerMatrix: React.FC<NetrunnerMatrixProps> = ({
const isLowQuality = isMobile || isWeakHardware; const isLowQuality = isMobile || isWeakHardware;
// На слабом ПК и телефонах рендерим 75% от разрешения экрана // На слабом ПК и телефонах рендерим 75% от разрешения экрана
const pixelScale = isLowQuality ? 0.75 : 1.0; const pixelScale = 1;
// ---------------------------------------------- // ----------------------------------------------
const logicalWidth = container.clientWidth; const logicalWidth = container.clientWidth;
@@ -127,7 +127,7 @@ export const NetrunnerMatrix: React.FC<NetrunnerMatrixProps> = ({
const isMob = width <= 768; const isMob = width <= 768;
const lowQual = isMob || isWeakHardware; const lowQual = isMob || isWeakHardware;
const pScale = lowQual ? 0.75 : 1.0; const pScale = 1;
workerRef.current?.postMessage({ workerRef.current?.postMessage({
type: "RESIZE", type: "RESIZE",
+61 -41
View File
@@ -18,6 +18,7 @@ let pixelScale = 1.0;
let isLowQuality = false; let isLowQuality = false;
let frameCounter = 0; let frameCounter = 0;
let eyeScale = 1.5; let eyeScale = 1.5;
let interactionTimer = 0;
let postProgram; let postProgram;
let postFramebuffer, postTexture; let postFramebuffer, postTexture;
@@ -133,9 +134,12 @@ float hash21(vec2 p) {
void main() { void main() {
vec2 crt_uv = crt_coords(v_uv, u_distortion); vec2 crt_uv = crt_coords(v_uv, u_distortion);
// Цвет фона: жестко привязываем к теме
vec3 current_bg = u_isDarkMode > 0.5 ? vec3(10.0/255.0, 10.0/255.0, 12.0/255.0) : vec3(250.0/255.0, 250.0/255.0, 252.0/255.0);
// Если вышли за границы "пузатого" монитора
if (crt_uv.x < 0.0 || crt_uv.x > 1.0 || crt_uv.y < 0.0 || crt_uv.y > 1.0) { if (crt_uv.x < 0.0 || crt_uv.x > 1.0 || crt_uv.y < 0.0 || crt_uv.y > 1.0) {
float bg = u_isDarkMode > 0.5 ? 0.0 : 0.98; outColor = vec4(current_bg, 1.0); // Теперь тут будет белый в светлой теме
outColor = vec4(bg, bg, bg, 1.0);
return; return;
} }
@@ -307,6 +311,8 @@ function initWebGL() {
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 });
// 1. Полная очистка
atlasCtx.clearRect(0, 0, atlasCanvas.width, atlasCanvas.height); atlasCtx.clearRect(0, 0, atlasCanvas.width, atlasCanvas.height);
let r, g, b; let r, g, b;
@@ -333,11 +339,10 @@ function buildAtlas() {
atlasCtx.textAlign = "center"; atlasCtx.textAlign = "center";
const halfFs = fontSize / 2; const halfFs = fontSize / 2;
// 2. Рисуем ТОЛЬКО текст. Никаких fillRect под ним!
for (let row = 0; row <= 9; row++) { for (let row = 0; row <= 9; row++) {
const alpha = (row + 1) / 10; const alpha = isLowQuality ? 1.0 : (row + 1) / 10;
const yOffset = row * fontSize; const yOffset = row * fontSize;
// УБРАЛИ fillRect с серым фоном. Оставляем ТОЛЬКО цветной текст!
atlasCtx.fillStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`; atlasCtx.fillStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`;
for (let i = 0; i < chars.length; i++) { for (let i = 0; i < chars.length; i++) {
atlasCtx.fillText(chars[i], i * fontSize + halfFs, yOffset); atlasCtx.fillText(chars[i], i * fontSize + halfFs, yOffset);
@@ -345,7 +350,6 @@ function buildAtlas() {
} }
const headY = 10 * fontSize; const headY = 10 * fontSize;
// УБРАЛИ fillRect для "головы"
atlasCtx.fillStyle = "rgb(255, 255, 255)"; atlasCtx.fillStyle = "rgb(255, 255, 255)";
for (let i = 0; i < chars.length; i++) { for (let i = 0; i < chars.length; i++) {
atlasCtx.fillText(chars[i], i * fontSize + halfFs, headY); atlasCtx.fillText(chars[i], i * fontSize + halfFs, headY);
@@ -363,11 +367,8 @@ function buildAtlas() {
); );
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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);
} }
} }
// --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ОТРИСОВКИ --- // --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ОТРИСОВКИ ---
function getGlitchColor(type) { function getGlitchColor(type) {
let r, g, b; let r, g, b;
@@ -573,13 +574,26 @@ function drawOverlayElements() {
const eyeLen = engine.eye_len(); const eyeLen = engine.eye_len();
if (eyeLen >= 6) { if (eyeLen >= 6) {
hasContent = true; hasContent = true;
interactionTimer = 120;
// ОПТИМИЗАЦИЯ ДЛЯ СЛАБЫХ УСТРОЙСТВ
// На старом ноуте/Xiaomi обновляем тяжелую текстуру глаз в 2 раза реже
if (isLowQuality && frameCounter % 2 !== 0) {
// Пропускаем очистку и отрисовку в этом кадре,
// WebGL просто выведет старую текстуру из кэша
return true;
}
overlayCtx.globalAlpha = 1.0; overlayCtx.globalAlpha = 1.0;
const eyeData = new Float32Array( const eyeData = new Float32Array(
wasm.memory.buffer, wasm.memory.buffer,
engine.eye_ptr(), engine.eye_ptr(),
eyeLen, eyeLen,
); );
const gap = isMobile ? 85 * eyeScale : 85 * eyeScale;
// Исправленный gap: на мобилках глаза ближе друг к другу (60 вместо 85)
const gap = (isMobile ? 75.0 : 80.0) * eyeScale;
// Левый глаз
drawEye( drawEye(
overlayCtx, overlayCtx,
eyeData[0] - gap, eyeData[0] - gap,
@@ -589,6 +603,8 @@ function drawOverlayElements() {
eyeData[4], eyeData[4],
eyeData[5], eyeData[5],
); );
// Правый глаз
drawEye( drawEye(
overlayCtx, overlayCtx,
eyeData[0] + gap, eyeData[0] + gap,
@@ -598,11 +614,11 @@ function drawOverlayElements() {
eyeData[4], eyeData[4],
eyeData[5], eyeData[5],
); );
} else if (interactionTimer > 0) {
interactionTimer--;
} }
return hasContent; return hasContent;
} }
// --- ГЛАВНЫЙ ЦИКЛ ОТРИСОВКИ ---
function render(currentTime) { function render(currentTime) {
requestAnimationFrame(render); requestAnimationFrame(render);
if (!engine || !wasm || !gl) return; if (!engine || !wasm || !gl) return;
@@ -611,27 +627,23 @@ function render(currentTime) {
if (now - lastDrawTime < 16) return; if (now - lastDrawTime < 16) return;
lastDrawTime = now; lastDrawTime = now;
if (isLowQuality) { // 1. Физика капель (WASM)
frameCounter++; // Для слабых устройств можно включить троттлинг обратно, если будет греться
if (frameCounter % 2 === 0) {
engine.tick(); engine.tick();
}
} else {
engine.tick();
}
// --- ШАГ 1: РЕНДЕРИМ ВСЁ В ТЕКСТУРУ (FRAMEBUFFER) --- const targetBuffer = isLowQuality ? null : postFramebuffer;
// Направляем вывод WebGL в буфер постобработки, а не на экран gl.bindFramebuffer(gl.FRAMEBUFFER, targetBuffer);
gl.bindFramebuffer(gl.FRAMEBUFFER, postFramebuffer);
gl.viewport(0, 0, width * pixelScale, height * pixelScale); gl.viewport(0, 0, width * pixelScale, height * pixelScale);
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.1. РЕНДЕР МАТРИЦЫ
const renderLen = engine.render_len(); const renderLen = engine.render_len();
const instanceCount = renderLen / 5; const instanceCount = renderLen / 5;
if (instanceCount > 0) { if (instanceCount > 0) {
@@ -663,12 +675,21 @@ function render(currentTime) {
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount); gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
} }
// 1.2. РЕНДЕР ОВЕРЛЕЯ (Глаза, глитчи) // 1.2. РЕНДЕР ОВЕРЛЕЯ (Глаза и эффекты)
if (drawOverlayElements()) { const needsOverlayUpdate = drawOverlayElements();
// КРИТИЧЕСКАЯ ОПТИМИЗАЦИЯ ДЛЯ СЛАБЫХ ПК:
// Не вызываем рендер оверлея, если он статичен (interactionTimer вышел)
const skipOverlay =
isLowQuality && interactionTimer <= 0 && engine.glitch_len() === 0;
if (!skipOverlay) {
gl.useProgram(overlayProgram); gl.useProgram(overlayProgram);
gl.bindVertexArray(vaoOverlay); gl.bindVertexArray(vaoOverlay);
gl.activeTexture(gl.TEXTURE1); gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, overlayTexture); gl.bindTexture(gl.TEXTURE_2D, overlayTexture);
if (needsOverlayUpdate) {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D( gl.texImage2D(
@@ -682,23 +703,25 @@ function render(currentTime) {
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_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);
}
gl.uniform1i(gl.getUniformLocation(overlayProgram, "u_texture"), 1); gl.uniform1i(gl.getUniformLocation(overlayProgram, "u_texture"), 1);
gl.drawArrays(gl.TRIANGLES, 0, 6); gl.drawArrays(gl.TRIANGLES, 0, 6);
} }
// --- ШАГ 2: ПОСТОБРАБОТКА (ВЫВОД НА ЭКРАН) --- // 3. ФИНАЛЬНЫЙ ЭТАП (ПОСТОБРАБОТКА)
// Возвращаемся к стандартному буферу (экран) // Если мы на слабом устройстве — работа закончена, выходим!
if (isLowQuality) return;
// Для мощных ПК переносим результат из скрытого буфера на экран с эффектами
gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.useProgram(postProgram);
gl.bindVertexArray(vaoOverlay);
gl.useProgram(postProgram); // <--- ИСПОЛЬЗУЕМ ВАШ НОВЫЙ ШЕЙДЕР
gl.bindVertexArray(vaoOverlay); // Переиспользуем квадрат на весь экран
// Передаем текстуру, которую только что нарисовали (Шаг 1)
gl.activeTexture(gl.TEXTURE2); gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, postTexture); gl.bindTexture(gl.TEXTURE_2D, postTexture);
gl.uniform1i(gl.getUniformLocation(postProgram, "u_mainTex"), 2); gl.uniform1i(gl.getUniformLocation(postProgram, "u_mainTex"), 2);
// Передаем параметры эффектов
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"),
@@ -706,23 +729,20 @@ function render(currentTime) {
height * pixelScale, height * pixelScale,
); );
// Значения: Сбалансированный киберпанк // Параметры "Киберпанк 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);
gl.uniform1f(gl.getUniformLocation(postProgram, "u_noise"), 0.05); // Чуть убавили цифровой шум gl.uniform1f(gl.getUniformLocation(postProgram, "u_noise"), 0.05);
gl.uniform1f( gl.uniform1f(
gl.getUniformLocation(postProgram, "u_glitch_intensity"), gl.getUniformLocation(postProgram, "u_glitch_intensity"),
isVpnOn ? 0.3 : 0.0, isVpnOn ? 0.3 : 0.0,
); );
gl.uniform1f( gl.uniform1f(
gl.getUniformLocation(postProgram, "u_isDarkMode"), gl.getUniformLocation(postProgram, "u_isDarkMode"),
isDarkMode ? 1.0 : 0.0, isDarkMode ? 1.0 : 0.0,
); );
// Рисуем финальный прямоугольник с эффектами
gl.drawArrays(gl.TRIANGLES, 0, 6); gl.drawArrays(gl.TRIANGLES, 0, 6);
} }
+1 -1
View File
@@ -162,7 +162,7 @@ impl MatrixEngine {
#[wasm_bindgen(constructor)] #[wasm_bindgen(constructor)]
pub fn new(width: u32, height: u32, font_size: u32) -> MatrixEngine { pub fn new(width: u32, height: u32, font_size: u32) -> MatrixEngine {
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
let max_drops = 10_000; let max_drops = 250;
let shockwaves = (0..5) let shockwaves = (0..5)
.map(|_| Shockwave { .map(|_| Shockwave {
x: 0.0, x: 0.0,