Розробка шейдерів рослинності та вітру для графіки
Дерева та трава в іграх — це не статичні меші. Це тисячі instances, кожен з яких повинен рухатися переконливо під вітром, правильно приймати освітлення через тонкі листки, не створювати z-fighting на напівпрозорих гранях та не вбивати продуктивність на мобільних пристроях. Зробити це правильно — означає вирішити кілька не пов'язаних між собою технічних проблем одночасно.
Vertex animation для вітру: чому скелетна анімація не підходить
Дерево з тисячею листків не можна анімувати через кості — це сотні skinned mesh renderer вишивів. Стандартне рішення — vertex shader animation, де переміщення вершини закодовано в самому шейдері та керується параметрами вітру через Material Property Block або Global Shader Property.
У ShaderGraph (URP/HDRP) алгоритм будується так: Transform Position нода → World Position → додаємо Sine-based offset по двом осям (X та Z) з Time + Phase Offset. Phase Offset — критично важливий параметр: він кодується в вершинний колір або UV-канал меша (звичайно UV2) на етапі підготовки ассету. Без phase offset всі гілки дерева рухаються синхронно — це виглядає як механічне покачування, а не органічний вітер.
Ієрархічний вітер — справжній differentiator якісного vegetation shader'а. Рух розбивається на три рівні:
- Trunk sway (раскачивание стовбура) — низька частота, велика амплітуда, вся геометрія дерева
- Branch flutter (рух гілок) — середня частота, менша амплітуда, закодована через R-канал Vertex Color
- Leaf shimmer (трепет листків) — висока частота, мінімальна амплітуда, G-канал Vertex Color
У шейдері кожен рівень — окремий Sine з різними frequency та amplitude параметрами. Weight для кожного рівня береться з vertex color каналу. Це означає, що художник повинен запекти vertex colors в дерево перед експортом: у основи стовбура R=0 (немає branch flutter), на кінцях гілок R=1.
Двусторонняя відрисовка та alpha clipping
Листки — це полігональні карточки (billboard quads або meshcard strips) з alpha texture. Проблеми два типи:
Alpha blending vs Alpha Clipping. Blending правильно сортує напівпрозорість, але вимагає правильного depth sorting всіх листків, що неможливо без GPU-side sorting. На практиці для vegetation використовують Alpha Clipping (Alpha Test): жорстка межа по порогу, без blending. Це дає артефакти на краях, але працює правильно з depth buffer та інстансингом. Поріг (Cutoff) звичайно 0.4–0.6 залежно від текстури.
Двусторонняя відрисовка. Lit шейдер по замовчуванню освітлює тільки front face. Для листків потрібен Two Sided матеріал з Flip Normals на back face — інакше задня сторона листка буде чорною при будь-якому освітленні. У ShaderGraph це Two Sided checkbox в Graph Settings + Facing нода для застосування normal flip.
Subsurface scattering для листків. Реальні листки просвічуються на сонці. У URP для vegetation використовують Translucency апроксимацію: беруть dot product між Light Direction та View Direction, додають його до Albedo через Lerp з translucency color (теплий зелений). Це ~4 ноди у ShaderGraph, додає +0.1ms до вартості шейдера, але різниця у візуальній якості очевидна.
Інстансинг та GPU Instancing
Трава та дерева вимагають GPU Instancing — інакше кожен куст це окремий draw call. У Unity для vegetation правильний підхід:
-
Graphics.DrawMeshInstancedабоGraphics.DrawMeshInstancedIndirectдля процедурної рослинності - Unity Terrain Detail Mesh — вбудований інстансинг для деталізації террейну
- SpeedTree integration (вбудований в Unity, але вимагає ліцензію SpeedTree для редагування)
Шейдер повинен підтримувати #pragma instancing_options та використовувати UNITY_SETUP_INSTANCE_ID у вершинному шейдері. У ShaderGraph це автоматично, але при написанні кастомного HLSL через Custom Function ноду потрібно явно додати ці pragma.
Material Property Block дозволяє передавати per-instance параметри (phase offset, wind strength multiplier) без створення окремого Material на кожен instance — критично для performance при сотнях інстансів з різними параметрами.
Настройка вітру через Global Shader Properties
Вітер у сцені — це глобальний параметр. Правильна архітектура: WindController MonoBehaviour встановлює Shader.SetGlobalFloat("_WindStrength", strength) та Shader.SetGlobalVector("_WindDirection", dir) кожен кадр (або по подіям зміни вітру). Всі vegetation шейдери читають ці глобальні параметри через Global ноду в ShaderGraph (Custom Function: UNITY_ACCESS_INSTANCED_PROP для per-instance або просто float з Global для shared).
Це дає можливість робити динамічні ефекти вітру: посилення при шторму, порив при вибуху рядом, зміна напрямку через день-ночний цикл — все через один контролер без змін матеріалів.
| Тип задачі | Строк |
|---|---|
| Шейдер трави (URP, mobile, з вітром) | 2–4 дні |
| Шейдер дерева (URP, ієрархічний вітер, PBR листки) | 4–7 днів |
| Vegetation shader set (трава + кусти + дерева) | 1–2 тижні |
| HDRP з subsurface + translucency | 1–2 тижні |
Вартість розраховується індивідуально за результатами обговорення рендер-пайплайну, цільової платформи та стилю графіки.





