Туторіал · Мобільні · Продуктивність · WebGL
📅 Липень 2026 ⏱ ≈ 25 хв 🎯 Середній рівень

Мобільна оптимізація WebGL: кроки та підводні камені

Симуляція, що видає 60fps на десктопній RTX-карті, може впасти до одноцифрових значень — або сама себе термально притормозити до слайдшоу за дві хвилини — на телефоні середнього класу. Мобільні GPU — не менші десктопні GPU; це справді інша архітектура з іншими вузькими місцями. Цей туторіал охоплює конкретні кроки, які дійсно дають ефект, і підводні камені, що проявляються лише коли реальні користувачі відкривають вашу симуляцію на реальних телефонах.

1. Інший режим, а не менший десктопний GPU

Майже кожен мобільний GPU (Apple, Arm Mali, Qualcomm Adreno, Imagination PowerVR) використовує плиткове відкладене рендерингу (TBDR): екран розбивається на маленькі плитки, і геометрія та шейдинг кожної плитки повністю обробляються у швидкій пам'яті на чіпі перед одноразовим записом у основну пам'ять. Ось чому деякі звички, вироблені на десктопі, активно шкодять продуктивності на мобільних:

Наслідок для дизайну шейдерів: все, що виграє від «рендер у текстуру, потім семплінг в іншому проході» (bloom, розмиття, більшість пост-обробки) — це реальна робота на TBDR-апаратурі, а не майже безкоштовна операція — свідомо розподіляйте бюджет render target і проходів, а не нашаровуйте ефекти, як на десктопі.

2. Обмежте devicePixelRatio

Сучасний телефон повідомляє devicePixelRatio 2.6-3+. Рендеринг canvas WebGL з таким повним співвідношенням означає, що кожен фрагментний шейдер виконується приблизно в 9 разів частіше, ніж при 1x — заради покращення чіткості, яке ледь помітне на маленькому екрані, розташованому на відстані витягнутої руки.

const pixelRatio = Math.min(window.devicePixelRatio, 1.5); // 1.5-2 — хороше значення для мобільних за замовчуванням
renderer.setPixelRatio(pixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);

Для симуляцій, важких за fill-rate (системи частинок, симуляції рідин, будь-що з великими повноекранними проходами шейдерів), розгляньте ще агресивніший підхід: рендерити внутрішньо у частці від CSS-розміру і дозволити canvas масштабуватися через CSS, що коштує майже нічого порівняно з шейдингом кожного пікселя в нативному розширенні.

// Внутрішнє розширення рендеру 0.75x, масштабоване браузером через CSS
const renderScale = 0.75;
renderer.setSize(
  window.innerWidth * renderScale,
  window.innerHeight * renderScale,
  false // false = не чіпати розмір CSS, лише буфер малювання
);
renderer.domElement.style.width = '100%';
renderer.domElement.style.height = '100%';

3. Точність шейдера: уникайте переповнення highp

Кваліфікатори точності GLSL ES (lowp, mediump, highp) на мобільних — не просто рекомендації: багато мобільних GPU виконують математику фрагментного шейдера mediump на дійсно вужчих апаратних шляхах, ніж highp, а деякі старіші мобільні GPU взагалі не підтримують highp у фрагментному шейдері (специфікація дозволяє тихо переходити на mediump).

Кваліфікатор Типовий діапазон Використовувати для
lowp ~256 окремих значень, ±2 Нормалізовані кольори, прості коефіцієнти 0-1
mediump ~16-бітний діапазон float UV-координати, більшість математики освітлення, точність фрагментного шейдера за замовчуванням на багатьох GPU
highp ~32-бітний діапазон float Позиції у світовому просторі далеко від початку координат, точне накопичення часу

Найпоширеніший тихий баг, специфічний лише для мобільних: симуляція, що накопичує пройдений час в uniform-змінній mediump (або з відкатом від highp), через кілька хвилин починає показувати помітне тремтіння або смуги, коли накопичене значення перевищує точність mediump при такій величині — на десктопі з повною підтримкою highp все працює добре, і ламається лише на телефоні в руках ваших користувачів.

// Періодично скидайте uniform-змінні на основі часу замість вічного накопичення
const t = (performance.now() % 100000) / 1000; // обгортається кожні ~100с
material.uniforms.uTime.value = t;
Перевірте фактичний відкат: запитайте gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT) — якщо .precision дорівнює 0, GPU не має справжньої підтримки highp у фрагментному шейдері й тихо виконує вашу «highp» математику як mediump.

4. Бюджети draw call і текстурної пам'яті

Орієнтовні, консервативні початкові бюджети для телефону середнього класу (Android 2-3-річної давності або старіший iPhone) порівняно з типовим десктопом:

Бюджет Десктоп (типово) Мобільний середнього класу
Draw call / кадр при 60fps ~1,000-3,000+ ~150-400
Трикутники / кадр Кілька мільйонів ~200 тис.-500 тис.
Загальна текстурна пам'ять Кілька ГБ ~150-400 МБ (спільно з ОС і браузером)

5. Термальний тротлінг

У телефона немає активного охолодження. Тривале високе навантаження на GPU нагріває SoC, доки термальне керування ОС не знизить частоту GPU (і часто CPU) — іноді на 30-50% нижче пікової — щоб залишитися в безпечних температурах. Це означає:

Наслідок для дизайну: орієнтуйтеся й тестуйте очікувану стійку частоту кадрів після кількох хвилин гри, а не частоту кадрів у перші секунди. Регулятор адаптивної якості, що знижує кількість частинок, розширення тіней чи якість пост-обробки, коли дельта-час між кадрами зростає, — надійніше рішення, ніж припущення про фіксований рівень пристрою з одноразової перевірки можливостей.
// Мінімальний регулятор адаптивної якості
let quality = 1.0;
function animate(now) {
  const dt = (now - lastTime) / 1000;
  lastTime = now;
  if (dt > 1 / 45) quality = Math.max(0.5, quality - 0.02); // відстаємо → знижуємо
  else if (dt < 1 / 58) quality = Math.min(1.0, quality + 0.005); // є запас → повільно піднімаємо
  particleSystem.setActiveCount(Math.floor(maxParticles * quality));
  requestAnimationFrame(animate);
}

6. Тач-введення замість миші/клавіатури

Кожен десктоп-орієнтований патерн взаємодії потребує тач-дружного еквіваленту — немає hover, немає правого кліку, і часто взагалі немає клавіатури:

Десктопний патерн Мобільно-дружний еквівалент
Hover для превью/підсвічування Тап для вибору з видимим станом «обрано» (hover не існує)
Контекстне меню правого кліку Жест довгого натискання або постійна екранна кнопка
Перетягування мишею для обертання камери Перетягування одним пальцем для обертання, щипок двома пальцями для масштабування, перетягування двома пальцями для панорамування
Колесо миші для масштабування Жест щипка (touchmove з двома активними точками дотику)
Клавіатурне керування WASD/стрілками Екранний віртуальний джойстик або кнопки напрямку
// Мінімальне відстеження відстані для щипка-масштабування
let lastPinchDist = null;
canvas.addEventListener('touchmove', (e) => {
  if (e.touches.length === 2) {
    const [a, b] = e.touches;
    const dist = Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY);
    if (lastPinchDist !== null) {
      const delta = dist - lastPinchDist;
      camera.position.multiplyScalar(1 - delta * 0.003); // масштаб через зміну відстані
    }
    lastPinchDist = dist;
    e.preventDefault(); // зупиняє прокрутку/масштабування сторінки
  }
}, { passive: false });

canvas.addEventListener('touchend', () => { lastPinchDist = null; });
Встановіть touch-action: none на елементі canvas у CSS разом зі слухачами { passive: false } — інакше власні жести щипка-масштабування/прокрутки браузера конфліктуватимуть з вашою обробкою дотику.

7. Тестування на реальних пристроях

Емульований «мобільний» режим Chrome DevTools (панель пристрою) лише симулює розмір екрана й тач-події — шейдери все одно виконуються на драйвері десктопного GPU, що не дає корисного сигналу про реальну продуктивність мобільного GPU, відкати точності чи термальну поведінку.

  1. Підключіть Android-телефон через USB з увімкненим USB-налагодженням, потім відкрийте chrome://inspect на десктопі — він перелічує відкриті вкладки телефону з живим посиланням «inspect» у справжні DevTools, що працюють проти реального пристрою.
  2. На iOS підключіть iPhone/iPad через кабель, увімкніть Web Inspector у Налаштування → Safari → Додатково, потім відкрийте меню Develop у Safari на Mac, щоб приєднатися до сторінки, що виконується на пристрої.
  3. Тестуйте на найстарішому пристрої, з якого ваша аналітика показує значущий трафік, а не на власному найновішому телефоні — результат 60fps на актуальному флагмані нічого не говорить про 3-річні Android-пристрої середнього класу, які зазвичай є справжнім довгим хвостом.
Далі: поєднайте це з туторіалом Debugging WebGL для читання chrome://gpu на самому підключеному пристрої, або з туторіалом з 3D-оптимізації для технік LOD і відсікання, що скорочують ті самі бюджети draw call і трикутників, обговорені в розділі 4.

Часті запитання

Чого я навчуся в цьому уроці?

Випустіть WebGL-симуляцію, яка дійсно добре працює на телефонах: обмеження pixel ratio, точність шейдерів, бюджети draw call і текстурної пам'яті, термальний тротлінг, тач-керування та мобільні баги, яких немає на десктопі.

Які теми розглядаються в цьому уроці?

Цей урок охоплює такі теми: Інший режим, а не менший десктопний GPU, Обмежте devicePixelRatio, Точність шейдера: уникайте переповнення highp, Бюджети draw call і текстурної пам'яті, Термальний тротлінг, Тач-введення замість миші/клавіатури, Тестування на реальних пристроях.

Скільки часу займає цей урок?

Цей урок займає приблизно 25 хв.

Які попередні знання потрібні?

Це урок рівня «Середній рівень» — окрема попередня підготовка, крім базового JavaScript, не потрібна.