Разработка шейдеров для мобильной игры
Мобильный GPU — это не десктопный GPU с урезанным частотами. Архитектура tile-based deferred rendering (TBDR) у Apple GPU (PowerVR-наследие) и Arm Mali принципиально отличается от immediate mode rendering в десктопных Nvidia/AMD. Шейдер, написанный без понимания этой разницы, на мобиле работает в 3-5 раз медленнее, чем мог бы.
Архитектурные ограничения, которые меняют подход к шейдерам
Fillrate и overdraw. Мобильные GPU чувствительны к overdraw — когда один пиксель закрашивается несколько раз. Каждый лишний draw call поверх уже нарисованного стоит дорого. Для Unity: Sorting Layer + правильный Queue в шейдере (Geometry перед Transparent) позволяют рендеру использовать early-z rejection. Прозрачные объекты убивают early-z — их количество в мобильной игре должно быть минимальным.
Precision. На мобиле разница между float (32 бит), half (16 бит) и fixed (10 бит, фактически устарело) ощутима. Arm Mali и Adreno поддерживают нативную 16-битную арифметику, и на них half работает вдвое быстрее float. Позиции вершин — float, цвета и UV — half. В GLSL ES 3.0 это mediump vs highp.
Sampling textures. Mip-mapping обязателен. Без него texture sampler работает с полным разрешением текстуры даже для мелких объектов на экране — это bandwidth-убийца на мобильных SoC с разделяемой памятью CPU/GPU.
Шейдеры в Unity: URP vs Built-in
Universal Render Pipeline — стандарт для мобильных Unity-игр. Shader Graph позволяет строить шейдеры визуально, но узкие места всё равно нужно дописывать в HLSL. Кастомный SubGraph в Shader Graph = переиспользуемый блок, который компилируется один раз.
Пример простого шейдера растворения (dissolve) на URP HLSL:
half4 frag(Varyings input) : SV_Target {
half4 baseColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv);
half noiseValue = SAMPLE_TEXTURE2D(_NoiseMap, sampler_NoiseMap, input.uv).r;
// cutout по порогу с мягкой границей
half edge = smoothstep(_Threshold - _EdgeWidth, _Threshold + _EdgeWidth, noiseValue);
clip(noiseValue - _Threshold); // discard пикселей
half3 edgeColor = lerp(baseColor.rgb, _EdgeColor.rgb, edge);
return half4(edgeColor, baseColor.a);
}
clip() на мобиле работает быстро — TBDR архитектура ещё до растеризации отбрасывает tile, если все пиксели заклипаны.
Постэффекты: что допустимо на мобиле
Полноэкранные постэффекты (bloom, depth of field, motion blur) — дорого. Стратегия:
-
Bloom — через
Kawase blurвместо Gaussian. Kawase делает меньше семплов за сопоставимое визуальное качество. В URPBloomoverride сHigh Quality Filtering= off для мобильного пресета. -
Цветокоррекция —
Tonemapping+Color Adjustmentsпочти бесплатно, делается в одном проходе. - DOF — только для кинематических сцен, не в геймплее. Bokeh DOF на мобиле — это ~3 мс дополнительно на Snapdragon 778G.
-
Motion blur — лучше имитировать через
Trail Rendererна конкретных объектах, чем полноэкранный velocity buffer.
Particle шейдеры и VFX
VFX Graph в Unity требует Compute Shader — поддерживается на Vulkan (Android 7+) и Metal (iOS 11+). Старый Particle System работает через CPU, что хуже при большом количестве частиц. Для мобильных таргетов: VFX Graph на современных устройствах, CPU particles как fallback для бюджетников.
Шейдер для частиц должен быть максимально простым: Unlit, Additive blending, half precision, без Lighting. Lit-шейдер на 500 частицах — распространённая ошибка, которая даёт -10 FPS.
Godot и GLSL ES
В Godot 4 шейдеры пишутся на диалекте GLSL с собственными расширениями (uniform, varying заменены на uniform и out). Шейдерный язык Godot компилируется в SPIR-V (Vulkan) или GLSL ES (совместимость). Для мобильного экспорта Godot использует Compatibility renderer на основе OpenGL ES 3.0 — он поддерживается шире, чем Vulkan на старых Android.
Оценка работ
Объём шейдерного кода сильно зависит от визуального стиля игры: стилизованная 2D-игра с cel-shading — 1-2 кастомных шейдера, реалистичная 3D с PBR, водой и атмосферой — 10+. Сроки от 3 дней до 4-6 недель. Стоимость рассчитывается индивидуально.







