[[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-свичами**: чаще всего такие свичи требуют собственного материала.