Разработка шейдеров растительности и ветра для графики
Деревья и трава в играх — это не статические меши. Это тысячи 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 недели |
Стоимость рассчитывается индивидуально по результатам обсуждения рендер-пайплайна, целевой платформы и стиля графики.





