lenec ru

← все посты

Shader Graph в Unity 6: что реально стоит делать визуально, а что писать в HLSL

19K

Shader Graph я долго не любил. Пять лет писал шейдеры руками в HLSL, и каждый раз, когда видел граф из 200 нод, думал: «понятный код был бы в три раза короче». Потом доверился, переписал на Shader Graph треть шейдеров одного проекта, и пересмотрел отношение. Не на всё, но во многих случаях это правильный инструмент. Расскажу, где он окупается, где нет, и какие приёмы окажутся полезны.

Опираюсь на Unity 6 (6000.x), Shader Graph 17.x, URP. В HDRP всё похоже, но Master-нода другая.

Что такое Shader Graph и зачем он

Shader Graph — визуальный редактор шейдеров. Соединяешь ноды (Sample Texture, Add, Multiply, Lerp), на выходе получаешь шейдер, совместимый с URP/HDRP. Compile target — HLSL под капотом, Unity сама собирает код.

Главные плюсы:

  • Художники, не пишущие код, могут тыкать материал и сразу видеть результат.
  • Препревью каждой ноды — мгновенная визуализация.
  • SRP Batcher-friendly: граф автоматически использует CBUFFER и совместим с батчингом.
  • Кросс-пайплайн: те же ноды работают в URP и HDRP с минимальными правками мастер-нод.
  • Subgraph'ы для переиспользования. Один раз собрал triplanar mapping — кладёшь в subgraph, используешь везде.

Главные минусы:

  • Сложные графы трудно читать. 200 нод — это лабиринт, в котором без названий и комментариев не разберёшься.
  • Performance не всегда оптимальный. Shader Graph генерирует «честный» код, без хитрых оптимизаций. Опытный человек на HLSL напишет на 10–20% быстрее.
  • Diff'ы в git — бинарные. Merge невозможен, ревью graf'а — только через скриншоты.
  • Не все шейдерные техники доступны через ноды (multi-pass effects, geometry shaders, tessellation — частично или никак).

Когда Shader Graph оправдан

По моему опыту — когда у тебя:

  • Стилизованные эффекты, где итерации художника важнее экономии 0.05 мс на фрейме. Toon-шейдеры, dissolve, силуэты, hologram-эффекты, водопады.
  • Несколько вариаций похожих шейдеров, где Subgraph'ы реально экономят. Тип «у меня 10 разных материалов с одной базовой логикой плюс уникальная фича каждого».
  • Художник в команде хочет править шейдеры сам.
  • Прототип/гейм-джем, где нужна скорость.

Когда я остаюсь на HLSL:

  • Performance-критичные шейдеры на мобиле. Каждый ALU имеет значение, и иметь явный код важно.
  • Сложные lighting-функции (custom BRDF, anisotropic shading). В Shader Graph есть Custom Function ноды, но они становятся костылями.
  • Multi-pass эффекты, где нужно несколько проходов с разными состояниями.
  • Compute shaders, surface shaders, geometry shaders.

Базовый стилизованный шейдер: dissolve

Один из самых частых эффектов — диссоль (растворение объекта по noise-маске). Хорошо ложится на граф.

Шаги:

  1. Master-нода: Universal Lit или Universal Unlit.
  2. Sample Texture 2D — для базовой текстуры.
  3. Sample Texture 2D — для noise (Simplex Noise или Voronoi из коробки тоже работает).
  4. Subtract: noise.r минус параметр _DissolveAmount (Float, expose в Inspector).
  5. Step или smoothstep — порог.
  6. В Alpha Clipping мастер-ноды подаём результат step'а инверсно (1 - step).
  7. Доп: Edge color через cremrer цветной полоски на границе исчезновения.

В коде это 30–40 строк HLSL, с фрагмент-функцией. В графе — 15 нод. Художник может править _DissolveAmount, цвет края и noise-текстуру в инспекторе. Победа.

Custom Function ноды: ваш мостик в HLSL

В графе есть нода Custom Function. Можешь написать кусок HLSL и подключить как ноду.

// SmoothMin.hlsl
void SmoothMin_float(float a, float b, float k, out float result)
{
    float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
    result = lerp(b, a, h) - k * h * (1.0 - h);
}

В графе подключаешь как Custom Function, указываешь файл и имя функции, поля Input/Output. Это спасает, когда ноды для нужной операции нет.

Совет: не пихай в Custom Function огромные блоки. Если у тебя fragment-шейдер целиком в HLSL внутри одной Custom Function — это знак, что Shader Graph не подходит, пиши классический шейдер.

Subgraph'ы: убийцы дублирования

Самая полезная фича для больших проектов. Subgraph — это мини-граф, который можно вставить как ноду в основной.

Что я держу в Subgraph'ах:

  • Triplanar mapping (универсальная штука для скал, террейна).
  • Distance fade (плавное скрытие объекта при приближении камеры).
  • Vertex displacement по noise (трава, листья).
  • UV scrolling с настраиваемой скоростью.
  • Fresnel + цвет — для рим-лайта.

Каждый Subgraph — один файл, переиспользуется во всех материалах. Меняешь логику в Subgraph — обновляются все шейдеры, его использующие.

Производительность: что смотреть

Shader Graph не магически дороже, но и не дешевле руки. Что я мониторю:

Number of instructions

В Frame Debugger или через Show Generated Code (правый клик на мастер-ноде) видно итоговый HLSL. Можешь прикинуть количество ALU и текстурных сэмплов. Для мобильного fragment-шейдера норма — до 50–60 ALU и 4–5 текстур.

Complex Lit vs Lit

В URP в Shader Graph есть Lit и Complex Lit. Complex поддерживает clear coat, anisotropic — больше фич, но дороже. Для мобилки бери Lit.

Динамические сэмплы

Если в графе ты сэмплишь текстуру по координатам, рассчитанным во фрагмент-шейдере (с зависимостями), GPU не может предсказать LOD, и качество фильтрации страдает. Где можно — переноси сэмплинг в вершинный шейдер.

Boolean keywords

В Blackboard'е графа можно объявить Boolean или Enum Keyword. Они компилируются в варианты шейдера через #pragma multi_compile. Полезно для опциональных фич, но количество keywords умножается комбинаторно. Если у тебя 5 keywords, это 32 варианта шейдера. Размер билда и compile time страдают.

SRP Batcher friendliness

SRP Batcher объединяет draw call'ы одинаковых шейдеров с разными материалами. Условие — все uniform'ы материала в одном CBUFFER.

Shader Graph сам генерит правильный CBUFFER. Custom HLSL шейдеры могут это сломать, если не следить. Проверка в инспекторе шейдера — поле SRP Batcher: compatible. Должно быть «yes».

Прозрачность и сортировка

Самый частый затык — прозрачные материалы. В мастер-ноде ставишь Surface: Transparent, и тут начинается:

  • По умолчанию depth write выключен — объект пишется в depth, но не сортируется. Получаешь визуальные баги «один прозрачный объект перед/за другим случайно».
  • В URP ты часто хочешь прозрачные объекты с включённым depth write для эффектов вроде glass-orb с преломлением.
  • Внутри одного меша несколько прозрачных submesh'ей рендерятся в порядке индексов, а не глубины.

Решения зависят от случая, но я почти всегда:

  • Стараюсь использовать Alpha Clipping (cutout) вместо Transparent, где можно. Cutout сортируется правильно и быстрее.
  • Для дыма/частиц — soft particles, специальный transparent setup.
  • Для стёкол — отдельный пасс с depth-prepass'ом.

Vertex displacement

Shader Graph поддерживает выход в Position (мастер-нода) — это вершинный шейдер. Базовое смещение вершин по noise или sin'у — несколько нод.

Что важно:

  • Если двигаешь вершины, не забудь нормали пересчитать. Иначе lighting будет неправильным. Это или вычисление в Subgraph'е (производные), или передача через UV2 готовых нормалей.
  • Для травы/листьев — World Position должен влиять на смещение, не Local. Иначе все травинки качаются в одной фазе.
  • На мобилке движение вершин дешевле, чем фрагмент-эффекты, но не бесплатно. На дальних LOD'ах отключай.

Workflow в команде

Несколько практик, которые сэкономили мне время:

  • Naming convention. Каждая нода с осмысленным именем (правый клик → rename). Не «Multiply (3)», а «Mask × Power».
  • Группы. Граф разбивай на логические группы через GroupNode. Каждая группа — одна функция (например, «Diss UV», «Edge Color», «Fresnel»).
  • Comments. Sticky Notes — добавляй пояснения, особенно для нестандартных решений.
  • Versioning. Сохраняй стабильные версии материала. Когда художник экспериментирует, пусть это делает в дубликате.
  • Code review. Перед merge'ом graf'а — скриншот в PR. Без визуала ревьюверу не понять, что произошло.

Hybrid подход

На больших проектах я работаю гибридно. Базовый шейдер — на Shader Graph (визуально, художник правит). Сложная математика (например, custom lighting) — в HLSL, подключается через Custom Function.

Так получается: художник управляет визуальными параметрами, программист — производительностью и алгоритмами. Никто не блокирует никого.

Что не делать

  • Не пиши скрипты, которые в рантайме модифицируют граф. Это оверкомпликация. Если нужны параметры — expose их в Blackboard и меняй SetFloat/SetVector на материале.
  • Не используй Shader Graph для compute shader'ов. Не его задача.
  • Не меняй мастер-ноду «по ходу проекта». Если начал на Lit, и потом перешёл на Unlit — все настройки слетят. Решай в начале.
  • Не делай 200 keywords. Помни про комбинаторный взрыв вариантов.

Что почитать и посмотреть

Cyanilux на YouTube — лучший источник по Shader Graph: и базовые туториалы, и сложные эффекты. Daniel Ilett — реалистичные техники (rim light, tessellation, ray marching) в графе и HLSL. Ben Cloward — старше, но фундаментальные основы шейдеров.

Из репозиториев — официальный Shader Graph Examples от Unity и URP Code Templates на GitHub. Там примеры кастомных Renderer Features в связке с графами.

Главное правило, которое сэкономит время: сначала пробуй на Shader Graph. Если упёрся в производительность или фичи — переходи на HLSL для конкретного шейдера. Не переделывай весь проект разом.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.