~5 мин чтения

Рендеринг, материалы и шейдеры

URP, Material vs Shader, Shader Graph и почему "разноцветные кубики" дорого.

Чем вообще рисуется кадр

Unity отдаёт сцену в GPU через рендер-пайплайн — последовательность проходов (passes): тени, opaque, transparent, postprocess. Конкретные шаги зависят от выбранного пайплайна (см. главу про стек):

  • Built-in RP — старый, монолитный. Forward или Deferred рендеринг по выбору.
  • URP — Forward+ по умолчанию, кросс-платформа. Это рекомендованный выбор сейчас.
  • HDRP — Deferred + Forward, физически-корректный, для High-end ПК и консолей.
Веб

WebGL/WebGPU draw call — это вызов отрисовки буфера вершин с шейдером. Браузер за вас не делает почти ничего: вы сами собираете батчи, посылаете uniforms.

Unity

Unity за вас собирает draw call’ы из сцены, сортирует, объединяет (batching), вызывает шейдеры. Вы видите в Frame Debugger список вызовов и можете оптимизировать.

Mesh, Material, Shader — три кита

  • Mesh — геометрия: массив вершин, нормалей, UV-координат, индексов. Импортируется из FBX/OBJ/glTF.
  • Shader — программа на GPU, описывающая, как из вершин и текстур получаются пиксели.
  • Material — экземпляр Shader с конкретными параметрами (текстуры, цвета, числа).

Один Mesh + один Material = один draw call (упрощённо). Если у вас 100 объектов с одинаковым материалом, Unity может слить их в один draw call (Static / Dynamic / GPU Instancing). Если у каждого свой Material — 100 draw call’ов.

Атлас вместо 50 текстур

Стандартный приём оптимизации: объединить текстуры в один атлас, чтобы все объекты использовали один материал. Это даёт автоматический batching и снижает overhead.

Material Inspector — что вы крутите

Откройте материал — увидите параметры активного шейдера. Для URP/Lit (стандарт):

  • Surface Type — Opaque или Transparent. Transparent дороже и требует сортировки.
  • Workflow — Metallic или Specular (физически-корректные PBR).
  • Base Map — основная цветная текстура (albedo).
  • Metallic Map + Smoothness — насколько объект металлический и гладкий.
  • Normal Map — карта нормалей для имитации мелкого рельефа без увеличения полигонов.
  • Emission — самосветящиеся участки.
  • Tiling / Offset — повтор и сдвиг UV.
// Поменять цвет материала из кода (Built-in RP / Standard shader: свойство _Color)
var renderer = GetComponent<Renderer>();
renderer.material.color = Color.red;          // создаст instance!

// Для URP/Lit основное свойство называется _BaseColor — .color может не работать ожидаемо:
renderer.material.SetColor("_BaseColor", Color.red);

// Через property block — без создания нового материала, без потери batching
var block = new MaterialPropertyBlock();
renderer.GetPropertyBlock(block);
block.SetColor("_BaseColor", Color.red);
renderer.SetPropertyBlock(block);
renderer.material vs renderer.sharedMaterial

Обращение к renderer.material создаёт уникальную копию материала для этого объекта. Это ломает batching и течёт памятью, если делается часто. Если меняете часто — используйте MaterialPropertyBlock (как в примере выше) — он не создаёт новый материал.

Shader Graph — визуальный шейдер

Если HLSL вам пока неудобно, в URP/HDRP есть Shader Graph — визуальный редактор шейдеров. В Project: Create → Shader Graph → URP → Lit Shader Graph. Открывается граф, где вы соединяете ноды: текстуры, операторы, output на Surface.

Shader Graph под капотом генерирует HLSL и подходит для большинства gameplay-эффектов: пульсирующее свечение, hologram, dissolve, water surface. Сложные постпроцессы тоже можно собирать.

Render Graph (URP 17, Unity 6)

В Unity 6 / URP 17 рендерер URP по умолчанию работает через Render Graph — декларативный API, где каждый pass описывает свои inputs/outputs (textures, buffers), а движок сам строит граф зависимостей и оптимизирует execution (merge passes, reuse render targets, parallel where possible).

Это влияет на вас, если:

  • Пишете custom Renderer Features в URP — старое API (ScriptableRenderPass.Execute) deprecated; нужно перейти на новый RecordRenderGraph().
  • Используете built-in passes — ничего не делаете, Render Graph под капотом.
  • Работаете с HDRP — там Render Graph был с 2021, но в Unity 6 единая модель.

Старый non-Render-Graph путь остаётся как “Compatibility Mode” в URP Asset, но deprecated.

Batching и draw call optimization

Чтобы уменьшить количество draw call’ов, Unity предлагает несколько механизмов в порядке исторической давности:

  1. Static Batching — для не-движущихся объектов. Их меши при загрузке сцены сливаются в один большой меш. Включается флажком “Static” в Inspector.
  2. Dynamic Batching — Unity на лету сливает маленькие меши (до ~300 вершин) с одинаковым материалом. Дорогой CPU-проход, в URP отключён по умолчанию.
  3. GPU Instancing — одним draw call’ом рисуется N экземпляров одного меша с разными матрицами. Включается в материале флажком “Enable GPU Instancing”.
  4. SRP Batcher — для URP/HDRP. Объединяет рендер объектов с шейдерами, совместимыми с SRP Batcher (большинство URP-шейдеров такие). По умолчанию включён.
  5. GPU Resident Drawer (Unity 6, главная новинка) — следующий шаг после SRP Batcher. На базе BatchRendererGroup автоматически батчит тысячи повторяющихся мешей (декорации, скалы, листва) без ручной настройки MultiMesh. Значительно снижает CPU-нагрузку на сценах с большим количеством объектов.

Как включить GPU Resident Drawer

Требования жёсткие — пропуск любого пункта приводит к silent fail:

  1. URP Asset → Rendering → Rendering Path = Forward+ (обязательно; Forward / Deferred не поддерживаются).
  2. URP Asset → Rendering → GPU Resident Drawer = Instanced Drawing.
  3. SRP Batcher включён в URP Renderer (включён по умолчанию).
  4. Project Settings → Graphics → Shader Stripping → BatchRendererGroup Variants = Keep All. Иначе шейдеры урезаются в билде, и GRD молча не работает.
  5. Шейдеры объектов должны быть DOTS Instancing-совместимы: #pragma target 4.5 + объявленный DOTS_INSTANCING_ON keyword. Стандартные URP/Lit/Unlit удовлетворяют.

BatchRendererGroup — низкоуровневый Unity API, на котором GRD построен. GRD прозрачно работает и со статичной геометрией (Static-флажок), и с динамическими MeshRenderer-инстансами — главное, чтобы шейдер поддерживал DOTS Instancing.

GPU Resident Drawer + GPU Occlusion Culling = главный buff Unity 6

Вместе с GPU Occlusion Culling (тоже Unity 6) даёт огромный прирост в open-world сценах: скалы за горизонтом не идут в draw call’ы вообще, видимые батчатся в десятки call’ов вместо тысяч.

Frame Debugger

Window → Analysis → Frame Debugger. Останавливает кадр и показывает все draw call’ы пошагово. Понимать, что туда упёрлось — половина успеха в оптимизации рендера.

Post-processing (Volume Framework)

В URP/HDRP вместо отдельного компонента используется Volume. Это GameObject с компонентом Volume и привязанным Volume Profile — ассетом с настройками эффектов.

Эффекты:

  • Bloom — свечение от ярких пикселей.
  • Tonemapping — преобразование HDR в LDR (ACES, Neutral, …).
  • Color Adjustments — экспозиция, контраст.
  • Vignette — затемнение по краям.
  • Depth of Field — размытие вне фокуса.
  • Motion Blur — размытие при быстром движении.
  • Chromatic Aberration — разделение цветовых каналов по краям.

Volume бывает Global (применяется везде) или Local (с триггер-зоной — эффект только в этой области). Удобно: вход в пещеру — Local Volume с другим tonemapping и vignette.

Не злоупотребляйте постпроцессом

Bloom + Motion Blur + Vignette + Depth of Field — типичный набор инди-разработчика, который “хочет как в фильме”. На мобильных это легко съест половину бюджета кадра. Включайте только то, что работает на ваш визуальный язык.

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