Оптимізація рендеринга мобільної гри
На Samsung Galaxy A52 (Adreno 618) гра тримає 28–32 FPS при цільових 60. На Xiaomi Redmi Note 11 з Helio G96 та Mali-G57 — стабільні 55–60 FPS. Різна продуктивність на подібних за ціною пристроях — типова ситуація для мобільного геймдева. Причина: оптимізація проводилася на флагмані, а mid-range GPU з іншою архітектурою дає зовсім інший результат.
Діагностика: що тормозить
CPU-bound vs GPU-bound
Перш ніж оптимізувати — зрозуміти, який є bottleneck. У Unity: Frame Debugger + Profiler. Включуємо Profile GPU в Android Player Settings та дивимось Profiler → GPU. Якщо GPU-час на кадр близько до 16ms (60 FPS), а CPU-час значно менше — GPU-bound. Якщо навпаки — CPU-bound.
На Unreal Engine: stat GPU в консолі, ProfileGPU команда, RenderDoc для захоплення кадра. r.ScreenPercentage 50 — швидкий тест: якщо FPS різко виріс при зниженні розрішення вдвоє — точно GPU-bound.
// Unreal консольні команди для діагностики
stat unit // загальний breakdown CPU/GPU/frame time
stat drawcalls // кількість draw calls цього кадра
r.ShowFlag.Rendering 1 // деталізовані rendering stats
Draw calls
На мобільних GPU draw call overhead вище ніж на консолях/PC. 500+ draw calls у кадрі — червона зона для mid-range Android. Кожен унікальний матеріал = окремий draw call. Кожний MeshRenderer з унікальним матеріалом = ще один.
Static batching (Unity): об'єкти з однаковим матеріалом об'єднуються в один mesh. Вимога — однаковий Material asset (не просто однакові настройки). Mark as Static в Inspector. Працює автоматично при сборці.
GPU Instancing: для повторюючихся об'єктів (трава, дерева, враги одного типу):
// Матеріал повинен підтримувати instancing
material.enableInstancing = true;
// Рисуємо 1000 екземплярів одним draw call
Graphics.DrawMeshInstanced(mesh, 0, material, matrices, 1000);
SRP Batcher (Unity URP/HDRP): автоматично батчит об'єкти з різними матеріалами але однаковим шейдером. Включається в URP Asset → SRP Batcher = enabled. Найпростіший спосіб скоротити draw calls без ручного batching.
Шейдери для мобільних GPU
Tile-Based Rendering (TBIMR)
Мобільні GPU (Adreno, Mali, PowerVR, Apple) використовують Tile-Based Immediate Mode Rendering. Екран ділиться на тайли, кожен рендериться цілком у швидкій on-chip пам'яті. Це означає:
-
Framebuffer fetch— читання з поточного framebuffer всередині тайла — практично безплатно. Використовуємо для deferred lighting:gl_LastFragDataу GLSL (розширенняEXT_shader_framebuffer_fetch). -
Depth pre-passна мобілі — часто зайвий overhead, TBIMR й так ефективно працює з depth test всередині тайла. -
Discardу фрагментному шейдері (alpha-test, clip) — на tile-based GPU вбиває ранній depth test для цілого тайла. Замінюємо alpha-blend або alpha-to-coverage там де можливо.
Precision qualifiers у GLSL/Metal
// ПОВІЛЬНО — highp скрізь за замовчуванням
uniform highp mat4 ModelMatrix;
varying highp vec2 TexCoord;
// ШВИДКО — мінімально необхідна точність
uniform highp mat4 ModelMatrix; // матриці — highp обов'язково
varying mediump vec2 TexCoord; // UV-координати — mediump достатньо
varying lowp vec4 VertexColor; // колір — lowp
На Mali GPU переход з highp на mediump для texture samplers — від 10 до 25% прирост продуктивності фрагментного шейдера.
ALU vs Texture Fetch
На більшості мобільних GPU texture fetch дешевше ніж важкі ALU-вычисління (sin, pow, sqrt). Попередньо запечені lookup таблиці в текстурі швидше ніж вычисління в шейдері:
// Повільно: вичисляємо fresnel у шейдері
float fresnel = pow(1.0 - dot(viewDir, normal), 5.0);
// Швидко: lookup texture
float fresnel = texture2D(fresnelLUT, vec2(dot(viewDir, normal), roughness)).r;
Розрішення та Dynamic Resolution Scaling
Рендерити у нативному розрішенні iPhone 15 Pro (2556×1179) — надмірно для мобільної гри з інтенсивним рендерингом. Стандартна практика — render scale 0.7–0.85 від нативного з наступним upscale.
Unity URP Dynamic Resolution:
ScalableBufferManager.ResizeBuffers(0.75f, 0.75f); // 75% від нативного
Unreal Mobile Super Resolution (MSR) — вбудований temporal upscaler для мобільних платформ з Unreal 5.1+. r.Mobile.TemporalAA 1. Дає якість близьку до нативної при значно меншій GPU-навантаженню.
Adaptive Performance (Samsung Game SDK + Unity): автоматично знижує навантаження при перегріванні:
using UnityEngine.AdaptivePerformance;
var ap = Holder.Instance;
ap.ThermalStatus.ThermalMetrics // поточна температура
ap.PerformanceStatus.PerformanceMetrics // bottleneck інформація
Кейс: 40 → 58 FPS на Adreno 618
Runner-гра: на Galaxy A52 — 40 FPS. Профілювання через AGI показало: Fragment ALU 87%, fragment bandwidth — перегружен. Три зміни:
- Шейдер води: заменили
pow(fresnel, 5.0)на LUT-текстуру → -8ms GPU -
highp→mediumpдля всіх texture samplers → -4ms GPU - Dynamic resolution 0.80 замість нативного → -6ms GPU
Результат: з 40 до 58 FPS без зміни візуального стилю. На Pro-пристроях — без змін, вони тримали 60 FPS з запасом.
Терміни
Профілювання та аналіз рендеринга — 2–3 дні. Оптимізація шейдерів, batching, dynamic resolution — 1–3 тижні залежно від стану проекту.







