Comments 21
Большое спасибо за статью, прочитал с удовольствием! К сожалению, на хабре, да и в интернетах в целом, на русском языке крайне мало информации по движку, особенно различных советов и лайфхаков от профессиональных разработчиков. В этой связи хотелось бы видеть больше статей, например о сетевом взаимодействии, подобных этой или прошлой, о сборке мусора.
Большое спасибо за комментарий, рад что вам понравилось. В целом то, что вы сказали и побудило меня начать писать эти статьи. Когда я начинал свое становление, было весьма сложно искать информацию в принципе, не важно каким языком обладаешь. И поиск информации сжирал очень много времени и моральных ресурсов.
Сейчас на многие фундаментальные вопросы до сих пор нет четких ответов и я стараюсь это исправить. О сети очень советую network compendium, он правда не потерял актуальность и несет в себе все нужные знания, что бы делать сетевые игры. Но возможно и сам что нибудь напишу про сеть. :-)
> То есть если у вас текстура например 1000х100 то в памяти она будет занимать столько же сколько и 1024х1024.
Не буду говорить за мобильные платформы, т.к. не знаю как там дела обстоят, но на десктопах это не так. Память под текстуры выделяется не квадратами (ну там есть заморочки со свиззлом и с блочной компрессией, но обычно эти блоки небольшие, не больше 4х4). Проверить можно легко, завести такую текстуру и поглядеть, что скажет мемрепорт.
> Если материал используется в двух местах - будет его две копии в памяти и он будет 2 раза загружаться
Если это один и тот же материал (всмысле один uobj) - то не будет дублирования. Если там 2 разных материала с копипастнутыми графами, то тогда да, будут две шейдермапы
> Во первых, если у вас есть меши - у них должны быть лоды
В рамках разумного. Ниже определенного количества вертексов лоды не имеют смысла, если меш не инстанцируется/не батчится, потому что накладные расходы на сам дроколл начинают перевешивать все остальное. На десктопах этот порог относительно большой (несколько лет назад было несколько сотен поликов, с современными картами думаю побольше, но точно не замерял. Вангую ~тысячу). С тяжелыми материалами тоже вертексная нагрузка может быть незаметна на общем фоне. Ну и нанитные меши в пятерке лод чейна тоже не требуют (хотя в целом для них тоже можно их делать, чтобы использовать на платформах без поддержки нанита)
Про размер текстур: Это на самом деле капец глубокий вопрос!
Во первых я говорю о видео памяти (VRAM), а не о оперативной памяти (RAM).
Однако, я изучил вопрос глубже: действительно в нынешней реальности текстуры не обязаны быть квадратными, однако они все еще должны быть стонрой в степени 2-х.
на это завязаны алгоритмы сжатия, выборки, интерполяции и генерации mip-карт. Да и многие другие на самом деле.
Однако сейчас есть алгоритмы, например в DirectX 11 которые относительно спокойно работают с текстурами не в степени 2-х, но для некоторых операций/оптимизаций, на сколько мне известно, они все равно расширяют текстуру до степени 2-х.
Память под текстуры так же аллоцируется блоками кратными 2-м и на современных видео картах. Возможно на данный момент это частично обусловлено и обратной совместимостью, так как не все используемые на данный момент платформы умеют работать с текстурами не в степени 2-х.
По поводу сжатия текстур. Почти все форматы сжатия используют тексели 4х4 пикселя, по этому что бы на текстуре нормально отработало сжатие ее стороны должны быть кратны - 4м. Это точно актуально для форматов: BC1-BC5
Вот несколько статей, которые вы можете почитать что бы лучше понимать о чем я говорю, но конкретно интересующей нас инфы там мало:
https://docs.microsoft.com/ru-ru/windows/win32/direct3d11/overviews-direct3d-11-devices-downlevel-intro?redirectedfrom=MSDN
https://www.katsbits.com/tutorials/textures/make-better-textures-correct-size-and-power-of-two.php
https://docs.microsoft.com/en-us/windows/win32/direct3d10/d3d10-graphics-programming-guide-resources-block-compression#virtual-size-versus-physical-size
Про материалы и инстансы, опять же я говорю о видео памяти. Шейдерам для того что бы исполнятся на видеядрах нужно быть загруженным и вычисленным. Я сознательно опустил момент того, что шейдер нужно еще и вычислить перед использованием, заменив это просто термином "загрузка".
Работает это (загрузка и вычисление 1 раз) только со статическими инстансами .В видео память шейдер с 2-х одинаковых материалов или с динамических инстансов будет загружен и вычислен 2 раза.
Но в оперативной памяти будет висеть 1 объект материала.
Кажется UE уже сам инстансит материалы при постановке их в слот в редакторе, но я нигде не нашел информации про это.
Это обусловлено тем, что при изменении параметров материала шейдер должен быть так же изменен и как следствие вычислен заново. Про это можно почитать вот по этой ссылке: https://docs.unrealengine.com/5.0/en-US/instanced-materials-in-unreal-engine/ в разделе "Constant and Dynamic Instances"
Про лоды отвечу так: А вы много видели в относительно современных играх мешей манее нескольких тысяч полигонов?
Да и в целом в вопросе про лодирование я больше акцентирую внимание на снижение сложности шейдера, чем на количество полигонов.
Однако! Основная задача лодирования мешей - решение проблемы перерисовок - quad overdraw. Пережевать же сколь угодно большое число вертексов геометрическим шейдером - фигня вопрос. Вот есть статья на эту тему: https://unrealartoptimization.github.io/book/pipelines/pixel/
К примеру: если у вас будет стоять меш у которого мы видим 100 полигонов и на экране он будет занимать 2х2 пикселя - мы эти 4 пикселя будем перерисовывать столько раз, сколько полигонов мы видим, то есть - 100 раз. Это все будет в рамках одного дроукола, но все равно - 100 перерисовок, это оверкилл.
Это обусловлено тем, что при изменении параметров материала шейдер должен быть так же изменен и как следствие вычислен заново.
Не совсем понимаю о чем вы.
Как вы в материале измените параметры? Параметры вы можете менять только в инстансе. Соответственно даже если вы 10 раз используете материал в разных объектах - он будет загружен и скомпилирован один раз.
Изменение обычных параметров не ведет к перекомпиляции шейдеров. То есть если мы сделали инстанс и у него меняем параметры - это всё тот же шейдер. Обычные параметры имеют тип uniform и это просто переменные которые выставляются при биндинге шейдера. На содержимое кода шейдера они не влияют. Поэтому перекомпиляции не будет. Но будут проблемы с батчингом. Потому что в рамках одного шейдера с одним набором параметров мы можем забиндить шейдер и за один DrawCall нарисовать сразу много объектов использующих этот материал. А если у нас инстансы с измененными параметрами - каждый объект придется отдельно рисовать, меняя значения uniform переменных.
Абсолютно безболезненно можно использовать один и тот же материал(не инстанс) в разных объектах. Он загрузится и скомпилируется один раз.
Ваша ссылка про фичелевел явно показывает, что начиная с 10 фичелевела карты спокойно поддерживают non-power-of-2. (что в общем-то покрывает буквально все десктоп карты, которые сейчас выпускают, потому что с 2008-2009 года карты с 10 фичелевелом массово пошли примерно)
Про сжатие я в курсе, я поэтому и написал что "кратно 4х4 - это не совсем степень двойки". С мипами повеселее, потому что каждый мип надо сжимать bcшкой, что ведет к тому, что вроде как обе стороны текстуры должны быть степенями двойки, чтобы не напороться на кривой некратный двум ресемплинг. Но это не догма, никакого выделения "как 1024x1024" для текстуры 1000x100 не будет, у вас просто мипы начиная с какого-то индекса будут немножко кривоваты (т.к. ресемплинг их немножко размоет), но с памятью ничего плохого не случится. В целом это никак не мешает делать текстуры 2:1 (и в общем-то иногда и делают)
С материалами не так, если у вас один uobj с шейдермапой, то он один раз будет скомпилирован и собран в соответствующие psoшки. Динамик инстансы да, рвут батчинг, но никаких дополнительных шейдеров не создают (хотя тут есть подводные камни в виде того, что вендоры в лице nvidia/amd могут для некоторых uniform параметров сами делать несколько специализаций и выбирать нужные шейдера в рантайме, но это настолько под капотом, что повлиять на это толком нельзя и увидеть без профилировщиков тоже)
А вот обычные material instance кстати внезапно могут добавлять новые шейдера в шейдермапу (через статик свитчи в материалах)
Про лоды и треугольники - мелкие пропсы регулярно могут быть меньше косаря треугольников. Ну т.е. возьмите какую-нибудь бочку / биллборд на заднем плане, вы при всем желании не сделаете ее больше тысячи треугольников (не, ну конечно есть умельцы, но в нормальной игре у вас такого не будет). Ну и всякая трава, но она как раз не в счет, т.к. инстансится обычно.
С quad overdraw все тоже сложнее. Во первых есть подходы на visibility buffer, которые полностью игнорят этот ваш quad overdraw (привет, нанит, но нанит не единственный такой подход, есть еще call of duty, deus ex, horizon forbidden west с похожим подходом). Во вторых, если у вас 100 полигонов покрывают квад в 2х2 пикселя, то совершенно не факт что у вас будет 100 quad overdraw. Если треугольник настолько маленький, что не покрывает центр ни одного из 4 пикселей внутри квада, растеризатор не сгенерит для этого треугольника квад, и соответственно x100 овердро не будет. Будет сильно меньше (по кваду за каждый треугольник, покрывающий центр хотя бы одного пикселя в кваде). Хотя за вертексные преобразования и растеризацию вы все равно заплатите, поэтому делать так не надо конечно.
Пожалуй я скажу что не так хорошо разбираюсь у вопросе на столь глубоком уровне, однако все равно убежден:
Текстуры стоит держать стороной в степень 2ки, да не обязательно квадраты
Лоды нужны, даже если объекты легковесны. Ну или их можно просто калить на самом деле
Правило про степень двойки оно как про испльзование пробелов при нейминге чего либо в ОС:
Давно не актуально, но стоит придерживаться чтобы не отгрести в каком-нибудь эдж кейсе.
У нас, например, пробелмы с переходом из одной VCS на другую, потому что кто-то бранч назвал используя пробел. Да, так было можно, но не нужно.
Вот и с текстурами таже история:
Используй степени двойки и точно ничего не проиграешь. Зато, условно, при порте на какое-нибудь внезапное железо можно НЕ получить артефакты или потери производительности на ровном месте.
Как по мне самая большая проблема УйЁ - система сборки, которая несовместима ни с чем принятом в остальном мире.
Проблемы начинаются с подключения сторонних библиотек(это больно)
Особенно остро проблема подключения библиотек встает когда вы разрабатываете свою для интеграции со своими сервисами.
Если бы УйЁ использовал для сборки CMake, то как по мне было бы не так больно...
Мне кажется, что ее основная проблема - отсутствие документации, ее правда до сих пор очень мало. Однако если потратить приличное количество времени и разобраться - то она становится весьма удобной и гибкой, как и остальные системы. Возможно стоит и про систему сборки анриала написать статейку.
Нам нужны "странные" вещи а-ля nDisplay+SplitScreen по которым доков нет =\
CMake Это кроссплатформенность и независимость от компилятора по умолчанию. А UE привязан к компиляторам, на винде это MSVC, на линуксе Clang, на маке вроде тоже Clang либо XCode, но не суть. У движка очень много зависимостей, причем все из них качаются бинарниками. Бинарники от компилятора к компилятору не совместимы, если только речь не про dll (так как это PE). То тут получается 2 выхода, либо качать бинарники под все компиляторы (они и так уже качаются под все ос и весят на 4.27 38гб, а в 5 уже 60гб, лишние варианты еще раздуют объем исходников, почему не качать только то что нужно?) либо собирать все зависимости перед сборкой движка (зависимостей очень много и некоторые будут собираться дольше самого движка, у меня движок на не самом слабом компе собирается 1.5-2 часа. Все собирать это еще лишние проблемы, могут быть ошибки при сборке либо несовместимости с другими компиляторами).
@SmallSnowballи @AllexInблагодарю вас, что помогли сделать статью лучше, я исправил части, на которые вы указали (про степень 2-х и материалы) и немного дополнил сверху.
Старайтесь не использовать
BeginPlay
для инициализации - вместо него есть много всего.
А можно подробнее, почему?
На первый взгляд, выглядит как место для инициализации по умолчанию.
Идеологически BeginPlay - начало игрового процесса (что твой актер делает, когда игра началась для него). В идеале оно происходит после инициализации всего от чего актер зависит. Основная именно проблемная проблема - в мультиплеере бегин плей не всегда вызывается на клиентах.
Так же актеры мог спавниться с отложенным бегин плеем (SpawnActorDeffered).
Например при загрузке уровня стримингом актеры и компоненты создаются и инитятся группами, но бегин плей вызывают вместе.
Что может нам говорить о том, что бегин плей должен быть легковесным.
Функции же приведенные как альтернативы - вызываются всегда при создании объектов и предназначены для инициализации объектов.
Если очень хочется то можно, конечно, но я бы не советовал.
Советы для начинающих (и не очень) разработчиков на Unreal Engine