[[MaterialPropertyBlock (без использования GPU Instancing) может решить проблему с тем, что из-за использования VAT-анимации количество SetPass растёт пропорционально количеству объектов?]] Суть MaterialPropertyBlock (MPB) — «подменить» отдельные параметры материала на уровне конкретного `Renderer`, не создавая копию материала. Правильно применённый MPB экономит память, убирает лишние [[SetPass Calls]] **MaterialPropertyBlock (MPB)** — это специальная структура-контейнер, в которую Вы записываете значения шейдерных свойств (_цвет, текстуру, float, Vector4, матрицу и т. д._). Затем блок «прикрепляется» к конкретному `Renderer` через `renderer.SetPropertyBlock(block)`. Unity передаёт эти значения на GPU **только для данного объекта**, не создавая новый материал и не изменяя `.mat`-asset. **MaterialPropertyBlock (MPB)** не совместим с SRP Batcher. - Почему MPB «ломает» SRP Batcher - **SRP Batcher** кеширует материал в GPU-буфере один раз и потом только меняет матрицы. Когда Вы вызываете `renderer.SetPropertyBlock()`, Unity вынужден заново выкладывать **модифицированный** буфер для каждого объекта, и рендер переходит на старый код-путь. ### Обязательно ли использовать GPU Instancing, чтобы благодаря MaterialPropertyBlock сократить SetPass? Нет, GPU Instancing не обязателен, чтобы `MaterialPropertyBlock` (MPB) «удержал» счётчик **SetPass Calls**. Все рендереры ссылаются на один и тот же `.mat`-asset. MPB меняет только содержимое константного буфера, а не сам материал, поэтому для всех объектов остаётся тот же вариант шейдера. → Unity не выполняет `SetPass` повторно, и счётчик SetPass Calls остаётся 1. ### MaterialPropertyBlock умеет работать с динамическими данными? `MaterialPropertyBlock` как раз создан для **динамических** данных: Вы можете в каждом кадре менять числовые поля, текстуры, массивы — и Unity подтянет их без создания копий материала. ### Какие ограничения у MaterialPropertyBlock? У `MaterialPropertyBlock` есть две жёсткие планки: 1. **Массивы** (SetFloatArray/SetVectorArray/…): Unity передаёт их как instanced-массы и обрезает всё, что превышает **1023-й элемент** ([Unity Documentation](https://docs.unity3d.com/ScriptReference/MaterialPropertyBlock.CopySHCoefficientArraysFrom.html?utm_source=chatgpt.com "Scripting API: MaterialPropertyBlock.CopySHCoefficientArraysFrom")). 2. **Обычные поля** (одиночные float, Vector4, цвета, матрицы) сводятся к per-object constant buffer’у; суммарно он не должен превышать **≈ 4 КиБ** (≈ 256 float4). Если записать больше, движок выводит предупреждение «MaterialPropertyBlock is too large, max is 4 kilobytes» и части данных просто не попадут на GPU. То есть MPB отлично подходит для десятков параметров или для массива до 1023 элементов; при большей нагрузке переходят на [[Instanced-буфер (StructuredBuffer или ComputeBuffer)|StructuredBuffer/ComputeBuffer]] и `DrawMeshInstanced(Indirect)`. ### Как «включить» MaterialPropertyBlock (MPB) Никакого переключателя нет — MPB активируется автоматически, как только Вы создаёте блок, заполняете его значениями и передаёте в `Renderer.SetPropertyBlock`. Ни в Project Settings, ни в манифесте пакетов ничего включать не нужно. ### MaterialPropertyBlock совместим с GPU Resident Drawer? Нет. Использование `MaterialPropertyBlock` (MPB) делает объект _не-совместимым_ с GPU Resident Drawer (GRD). Если рендерер хоть раз получает MPB, движок исключает его из групп GRD и возвращается к SRP Batcher/обычным вызовам Draw, тем самым теряя выигрыш в SetPass/CPU-времени. --- #### Зачем он нужен | Проблема | Как решает MPB | | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | **`renderer.material` клонирует материал** → лишняя память, ломается батчинг | MPB хранит только изменённые свойства; ссылку на материал не трогает | | **Нужно менять параметры у сотен объектов** | Один и тот же `.mat` остаётся общим, а индивидуальные различия задаём блоками | --- #### Ключевые особенности производительности - **Память**: блок весит ~ 160 байт + данные свойств, против десятков-сотен КБ у полноценного материала. - **CPU**: добавляет микро-затрату при установке, но **экономит** время на рендер-потоке за счёт уменьшения количества `SetPass Calls`. - **GC**: создавайте один `MaterialPropertyBlock` и переиспользуйте его — новые экземпляры каждый кадр будут мусорить. --- #### Мини-пример (C#) ```csharp using UnityEngine; [RequireComponent(typeof(MeshRenderer))] public class MpbColorBlink : MonoBehaviour { // Кэшируем ID свойства «_BaseColor» (URP/HDRP) или «_Color» (Built-in). private static readonly int BaseColorId = Shader.PropertyToID("_BaseColor"); private MeshRenderer _renderer; private MaterialPropertyBlock _mpb; void Awake() { _renderer = GetComponent<MeshRenderer>(); // Создаём блок один раз и храним – без аллокаций в Update. _mpb = new MaterialPropertyBlock(); } void Update() { // 1. Читаем текущий блок (важно, если уже были свойства). _renderer.GetPropertyBlock(_mpb); // 2. Модифицируем нужные параметры. Color c = Color.Lerp(Color.red, Color.blue, Mathf.PingPong(Time.time, 1)); _mpb.SetColor(BaseColorId, c); // 3. Применяем изменения. _renderer.SetPropertyBlock(_mpb); } } ``` **Пояснения к коду:** - `Shader.PropertyToID` ускоряет доступ к свойству и избегает строковых аллокаций. - `GetPropertyBlock` безопасен: если блок ещё не назначен, Unity вернёт пустой. - Тот же `_mpb` объект используется каждый кадр → ноль GC-давления. --- #### Когда MPB не подойдёт - Нужно **разные шейдеры** или **разные ключевые слова (`shader keywords`)** — придётся иметь отдельные материалы. - Хотите сохранить значение на диск — MPB живёт только в памяти. - Меняете материал в **Shader Graph** с **пер-рендерерными bool-свичами**: чаще всего такие свичи требуют собственного материала.