пятница, 19 января 2024 г.

UMaterial, UMaterialInstanceDynamic, UMaterialInstanceConstant и UpdateStaticPermutation в UE5.2+

    Недавно столкнулся со странностями в архитектуре UE 5.2+. В данном случае – с устройством материалов в движке. Материал, как уже известно, в анриле может быт много чем, но меня интересовала первичное его назначение – когда он работает как эдакий шейдер+ с биндингами переменных. Основной проблемой было то, что материалы странно вели себя при рендеринге из коммандлета, который запускался из консоли – в одну из веток поступали какие-то некорректные данные и, увы, это пока не удалось отладить. Частично из-за того, что нет простой прямой связи между объектами рендеринга и материалами. А ещё RenderDoc упорно не хотел показывать никаких вызовов графического API, хотя рендеринг явно происходил. В итоге принял решение сбросить определённый флажок, который имел вид Static Parameter Switch в наследуемом инстансе материала  и должен был решить проблему за счёт убирания глючащей ветки.

    И вот здесь позволю себе отступления, что вообще во время исполнения EG рекомендуют использовать UMaterialInstanceDynamic, который по идее позволяет избежать лишней компиляции материалов и вообще весь шустрый. В принципе, почему бы и нет? Все нужные методы у него есть, хорошо же? Я говорю, само собой, об условном UpdateStaticPermutation, аж в трёх вариантах, который по идее должен давать возможность переопределять статические параметры.

    А вот нет, ни разу. Перегрузка может не триггерить  повторную компиляцию материала. Беглый поиск по редактору и движку, показал, что там чаще используется вот эта перегрузка. Но самое смешное – это если дёрнуть метод на UMaterialInstanceDynamic, то оно счастливо упадёт с ассертом о том, что лишь UMaterialInstanceConstant можно менять такие флаги. В документации ограничения нет, отсюда и пост.

    То есть мы можем использовать в таком случае только UMaterialInstanceConstant. Замечу, что я здесь рассматриваю код, исполняемый, по сути, на этапе ещё не собранной игры, в конечном билде это может не работать.

    Справедливости ради создать UMaterialInstanceConstant в рантайме, вполне можно есть вот такой пример. Однако здесь его кидают в ассет, а это не обязательно всё же делать. Зато, кроме переопределения статических параметров, было нужно ещё и копирование настроек из определённого «предыдущего» материала.

    В итоге получилось что-то такое. Это  далеко не оптимальный код, есть ощущение, что если покопаться ещё, то можно ещё отрезать ненужные действия.


UMaterialInstanceConstantFactoryNew* MaterialFactory = 
	NewObject<UMaterialInstanceConstantFactoryNew>();
// Это совсем базовый материал, а вот Material – это инстанс, который хочется копировать. 
// Замечу, что BaseMaterial должен по идее быть UMaterial
UMaterialInterface* BaseMaterial = Material->GetBaseMaterial();
MaterialFactory->InitialParent = BaseMaterial;
const FName MatName = FName(TEXT("REPLACE_WITH_OWN_MAT_NAME"));
// В данном случае нам не интересен пакет, т.к. мы не планируем из этого делать ассет
UMaterialInstanceConstant* Instance = CastChecked<UMaterialInstanceConstant>(
	MaterialFactory->FactoryCreateNew(
    	UMaterialInstanceConstant::StaticClass(), 
        this, 
        MatName, 
        RF_Standalone | RF_Public, 
        nullptr, 
        GWarn
   )
);

// Это обычно делают перед обновлением свойств в редакторе, не стал менять
Instance->PreEditChange(nullptr); 

// Копирование исходных параметров. Замечу, что если родителем Material является 
// другой инстанс, то его тоже стоит скопировать 
Instance->CopyMaterialUniformParametersEditorOnly(Material, true);

// Далее меняем Static Parameter Switch
FStaticParameterSet Set;
// Важно использовать именно эту перегрузку, другая не возвращает базовые свитчи
Instance->GetStaticParameterValues(Set); 
for (auto& Switch : Set.StaticSwitchParameters)
{
     if (Switch.ParameterInfo.Name.ToString() == TEXT("SWITCH_NAME"))
	{
		Switch.Value = <новое значение>;
		Switch.bOverride = true;
	}
}
Instance->UpdateStaticPermutation(Set, Instance->BasePropertyOverrides, true);

// Тут документация недоговаривает, эта принудительная компиляция прекрасно работает 
// и вне FMaterialUpdateContext. 
// Возможно, в будущем всё изменится, но пока это так.
Instance->InitStaticPermutation();  

    Дополнительно можно подёргать всякие сбросы кешей, чтобы сработало, но, по идее, это не обязательно:


Instance->RecacheUniformExpressions(true);
// UPD: 02.03.2024 Вот это вообще на 5.3.2+ начало крашить при рендеринге - причины непонятны, увы
// Впрочем код выше работает и без этого.
Instance->RecacheAllMaterialUniformExpressions(true);
FPropertyChangedEvent Evt(nullptr, EPropertyChangeType::ValueSet);
Instance->PostEditChangeProperty(Evt);
Instance->PostEditChange();

    Как-то так. Это опять же, способ, который я отыскал сам ковырянием в движке и редакторе, но если у кого-то есть более простой и красивый вариант – я буду рад узнать о нём.

Комментариев нет:

Отправить комментарий