Блог xyz school

Оптимизация: почему время важнее полигонов

Многие начинающие 3D-художники и разработчики игр знают, что существует «оптимизация» — нечто, что позволяет повысить частоту кадров. Но зачастую они не разбираются в том, как она работает — и это заставляет их принимать неверные решения.

В этом материале преподаватель курса OutBlock и левел-дизайнер VOID Interactive Денис Куандыков объясняет, как устроена оптимизация, и почему количество полигонов тут — не ключевой фактор.
В чём проблема

Работая с геометрией, новичок может потратить уйму времени впустую, пытаясь сократить количество полигонов. Даже если лимит по техническому заданию позволяет определённому LOD (уровню детализации) держать в себе 10-15 тысяч трисов (полигонов с тремя сторонами), моделер может решить, что если он всё уместит в 5 тысяч треугольников, то работа станет более «оптимизированной». Это не так.
Оценить абстрактную «стоимость» рендера одного кадра невозможно — нужно знать, под какие устройства создаётся игра, и что именно в ней отрисовывается.
Если 3D-артист со стороны попытается самостоятельно оценить сложность кадра, то может получиться недоразумение. Художник не знает, какое количество треугольников будет оптимальным, если нет чёткого технического задания.

Если оно есть, это решает проблему. Но иногда техническое задание бывает нечётким не по вине заказчика: например, если вы сами разрабатываете собственную игру, или же если в проекте всё стандартно и нет ничего специфичного.
Как устроена отрисовка кадра

Процесс отрисовки кадра — это та самая «магия под капотом движка». У отрисовки, как и у работы художника, есть стоимость, и рассчитывается она исходя из необходимого времени.

Для того, чтобы оценить время рендера, в редакторах и движках есть отдельные инструменты. Одного лишь значения кадров в секунду в любом случае недостаточно.
Во всех движках есть вывод статистики. Самое важное в нашем случае — это значение времени одного кадра.
«Время кадра» — это время в милисекундах, за которое может быть построен и отрисован один кадр. Чтобы получить 60 кадров в секунду, один кадр должен быть обсчитан за 16 милисекунд или еще быстрее.

Draw Call (вызов отрисовки) — это одна графическая команда, которая должна что то отрисовать.

Сложность кадра можно оценивать именно с помощью вызовов отрисовки. Чем их больше, тем дольше они обсчитываются, тем медленнее выполняется 1 кадр, тем меньше кадров в одной секунде и тем меньше итоговый FPS.

И наоборот — чем меньше вызовов отрисовки, тем быстрее считается кадр, и тем выше FPS.
Frame Debugger — встроенная утилита Unity, позволяющая прямо в редакторе разобрать один кадр на все составляющие. Этот кадр занял 96 отрисовок.
Примерный разброс лимитов на Draw Call можно описать так:

— Мобильные игры — примерно 100 на средне-высоких настройках, на топовых устройствах можно и больше.

— Игры для ПК и консолей — до 4000.

Например, Battelfield и третий «Ведьмак» в среднем работают на 1000-2000 вызовах отрисовки.
А у PUBG этот показатель скачет от адекватных цифр (до 5 тысяч) вплоть до безумных 50 тысяч Draw Call. Такой разброс — это плохо.
В то же время, некоторые внутренние движки Ubisoft спокойно работают на значениях вплоть до 20 тысяч. Дело в том, что Draw Call — это тоже не идеальное мерило производительности. Вызовы отрисовки бывают разные, и тут всё зависит от того как работает конкретный рендер-пайплайн в конкретном проекте. Подробно разобраться в том, как это устроено, можно здесь.

Рендер-пайплайн — это очень сложная система, и во многих крупных проектах она уникальна. Из-за этого оценить оптимизацию порой практически невозможно.
Однако вы можете самостоятельно проанализировать отрисовку кадра любой ПК игры, используя специальные инструменты:

— RenderDoc

— Nvidia Nsight

— Intel Graphics Perfomance Analisys
RenderDoc
Подробнее обо всех этих инструментах можно узнать здесь.

Самое важное: нужно оценивать стоимость кадра. А первичное мерило стоимости — это время. Уже затем можно всё раскладывать на процессы, то есть, на те самые вызовы отрисовки.

Когда обсуждают оптимизацию проекта и специфику его контента, в первую очередь речь заходит не о количестве полигонов, а о том, сколько примерно можно позволить себе вызовов отрисовки, — учитывая железо и технологии.
Вызовы отрисовки

Теперь разберёмся, как именно можно подсчитать количество этих вызовов.

Простой пример: у нас есть 10 ящиков и одно дерево. Все десять ящиков имеют один и тот же шейдер, материал и количество полигонов. Логично предположить, что у нас будет 11 вызовов, ведь в кадре 11 объектов.

Всё почти так и есть. Программа рендерит так: «ящик, ящик, ящик, ящик, ящик... и потом дерево. Кадр построен».

Однако, судя по статистике, у нас всего три батча (вызова отрисовки):
Дело в том, что движок сам понимает, что перед ним десять одинаковых ящиков. Он сам «сшивает» ящики в один объект, и теперь процессор отдает нашей видеопамяти на отрисовку один кусок меша, который мы видим как 10 отдельных ящиков.

Этот процесс называется «батчинг». Он бывает динамическим и статическим.
Как устроен батчинг

Динамический батчинг работает, если он включен в движке. Движок сам анализирует геометрию и сшивает те объекты, которые подходят под лимиты.

Обычно у динамического батчинга очень жёсткие лимиты; он не позволяет сшивать тяжёлую геометрию и занимается в основном мелкими деталями в кадре. Например, если от стены динамически отделились мелкие осколки, то они они, скорее всего, будут сшиты динамическим батчингом в каждом отдельном кадре.
Как устроен батчинг

Динамический батчинг работает, если он включен в движке. Движок сам анализирует геометрию и сшивает те объекты, которые подходят под лимиты.

Обычно у динамического батчинга очень жёсткие лимиты; он не позволяет сшивать тяжёлую геометрию и занимается в основном мелкими деталями в кадре. Например, если от стены динамически отделились мелкие осколки, то они они, скорее всего, будут сшиты динамическим батчингом в каждом отдельном кадре.
Партикли (система частиц) умеют «сшиваться» сами по себе, поэтому их стоит использовать, если нужно нарисовать много одинаковых мелких объектов.

Например, тысячи рыб в кадре в Abzu — это на самом деле партикли, геометрия которых дополнительно анимирована шейдером с вертексной анимацией.
Creating the Art of ABZU
Статический же батчинг происходит «заранее». Для этого нужно указать что этот объект статичен, и никогда не будет изменен или сдвинут.
Отличие от динамического батчинга в том, что он позволяет перерабатывать огромное количество объектов. За один батч Unity может сшить объектов общим количеством до 64 000 треугольников.

Кроме того, динамический батчинг будет пытаться пересобрать геометрию почти в каждом новом кадре, — а значит будет постоянно увеличивать время расчёта.
Именно батчинг виноват в том, что в огромном количестве игр до сих пор нельзя разрушить любую стену или сдвинуть стул с места.
Чтобы батчинг сработал, объекты должны быть «одинаковы». Объекты должны иметь один и тот же шейдер, материал на этом шейдере (в Unreal это инстанс материала), текстуру и остальные параметры объекта, а также не должны иметь Non Uniform Scale и не должны быть разбиты светом.

Одна из причин того, почему от художников требуют не текстурить объекты уникальными сетами отдельных текстур, а паковать их в огромные текстурные атласы — как раз в этом. Это позволяет отдать батчингу на обработку сотни разных визуально разных объектов, — ведь они будут отрисованы не отдельно, а группами.
Пример очень простого атласа для тайловых поверхностей.
Как уже было сказано, важнейшее мерило оптимизации — время. Иногда один раз загрузить в память один огромный текстурный атлас гораздо выгоднее, чем постоянно гонять туда-сюда отдельные мелкие текстуры — даже если в сумме они будут занимать в памяти меньше места, чем атлас целиком. Загрузка и выгрузка — не бесплатный процесс.

Однако батчинг тоже не «бесплатен» — даже если он статический и обсчитан заранее. Сцены в играх нужно освещать, и даже в случае статичного освещения это будет влиять на батчинг.
Так например, два одинаковых ящика, которые в обычной ситуации превратились бы в один батч, разделят на два батча, так как они привязаны к разным лайт-пробам (Light Probe).

Освещение в реальном времени тоже будет влиять на то, какие объекты сбатчатся, а какие — нет.
Световые зонды (Light Probes) нужны, чтобы запечь в себя шейдинг в статичном освещении. Они позволяют затенить динамический объект в сцене с Light Map.
Кроме того, нужно учитывать где будет храниться полученный материал. Ведь сбатченная геометрия — это отдельный уникальный меш, который не заменяет ваши ящики на локации. То есть, батчинг требует свободной памяти.

Поэтому иногда применяют «ручной» батчинг, он же просто «мердж» (merge). Берём геометрию, и в любой программе сшиваем так, как нужно под конкретную сцену.
На финальных итерациях, когда весь левел-дизайн и левел-арт готовы, многие мобильные проекты проходят этап ручного сшивания всей локации. А модульные объекты, из которых состояла локация, и вовсе не идут в билд так как заменяются смерженным мешем.

Иногда гораздо выгоднее скормить движку огромный меш в 64 тысячи треугольников, чтобы отрисовать всю локацию целиком. Так поступили, например, разработчики Guns Of Boom.
Дмитрий Гладилин - Проблемы и решения при создании графики для мобильного шутера Guns of Boom
Подобный подход практически полностью исключает использование Occlusion Culling. Эта техника позволяет не рисовать те объекты, которые не видны камере. То есть, когда ящик перекрыт другим более крупным ящиком — мы его не видим, а значит можем не рисовать. В случае со «склеенными» мешами такое разделение невозможно.
Уровни детализации

Level of Detail (LOD, или уровень детализации) — простая техника, которая позволяет уместить огромное количество объектов в кадре, не отнимая время на обработку лишней геометрии.

LOD может разбить объекты на разные батчи: и в этом нет ничего страшного, ведь зачастую несколько сотен лишних DrawCall будет быстрее обсчитаны на лодированых мешах чем рисовать их оригиналы — но сбатченные.
Сейчас есть неплохие инструменты автоматической генерации LOD-мешей, и это облегчает работу. При нужной настройке можно выдать результат не сильно хуже ручной ретопологии. Но даже используя автоматику, не стоит делать тысячи разных уровней детализации: все уровни LOD тоже нужно хранить в памяти, а её не стоит забивать просто так.

Обычно используют четыре уровня детализации. LOD-0 — это оригинальная модель, а LOD-3 — дальняя. LOD4 может быть просто 2D спрайтом который всегда смотрит на игрока.
Кроме LOD-0 порой нужно делать отдельную модель для кат-сцен. В ней может быть куда больше полигонов, чем у LOD-0, потому что она появляется лишь в срежиссированных сценах, и, как правило, находится очень близко к камере — поэтому для неё лучше использовать крайне высокую детализацию.

Это касается даже мелких объектов: например, у обычной кружки в одной из стартовых сцен Dead Space 2 было достаточно полигонов, чтобы она не казалась «квадратной», в то время как в остальной игре в LOD-0 у аналогичных объектов не было такого сглаживания.
LOD-ы позволяют не считать лишние сотни полигонов в вашей модели. В первую очередь важно понимать роль модели в сцене и прикидывать, насколько детально игрок сможет её разглядеть.

Если модель, например, можно разглядеть вблизи с максимальным приближением, то просто сделайте LOD-0 достаточно детализированным, — а в самой сцене основным рабочим уровнем будет LOD-1.

Подход, основанный на роли модели в кадре полностью защищает вас от критики сторонников «идеальной сетки», которые без понимания контекста — просто по скриншоту, — заявят, что вы сделали 10 лишних треугольников.
Зачем тратить время и доказывать кому либо в комментариях на ArtStation, что у вас не завышенный полигонаж? Время — куда более важный показатель оптимизации. И ваше время — в том числе.
Alpha overdraw

Рендер-пайплайн должен адекватно рассортировать и нарисовать сцену — обработать шейдеры, геометрию, текстуры и пост-процессы. К тому же он должен учитывать, что объекты могут быть прозрачными.

Чем больше площадь прозрачности в кадре, и чем больше прозрачные объекты наслаиваются друг на друга — тем дольше рендер-пайплайн будет рисовать итоговый пиксель, ведь ему придётся каждый раз его перерисовывать. Это называется Alpha overdraw.

Чем меньше альфы, тем быстрее будут обсчитаны пиксели. По сути, шейдер и сам рендер — это процесс/программа, которая рисует конкретный цвет конкретного пикселя на экране. И в этом случае пара десятков лишних треугольников, наоборот, ускорит рендер.
Cлева — обычный «плейн» с текстурой ветки. Справа — он же, но силуэт ветки обрезан по контуру, чтобы срезать лишнюю прозрачную площадь.
Z-Fight — артефакт, который образуется, если поставить несколько полигонов в одной плоскости. Рендеру не хватает точности глубины, чтобы отсортировать отрисовку этих полигонов в правильном порядке.

Но кроме визуального артефакта, который заметит игрок, это повлияет и на время рендера — ведь пиксели в этом месте будут постоянно «спорить» друг с другом об очереди отрисовки.
Два плейна на одной оси. Смешение цветов по центру и есть Z-Fight.
Итог

Итак, мы выяснили, что практически ничего «бесплатного» в случае с отрисовкой кадра не бывает: технологии, которые позволяют что-то оптимизировать, не работают по нажатию нужной галочки. Всегда нужно понимать, для чего мы используем тот или иной инструмент.

Нынешнее железо — даже мобильное, — может обрабатывать огромное количество треугольников. Объёмы памяти и скорость растут. Чипы на мобильных устройствах и их батареи, которые перегреваются при высоком FPS, тоже становятся всё совершеннее.

Взять и назвать точные диапазоны количества полигонов попросту невозможно — да и бессмысленно. Всё слишком сильно зависит от того для чего используется модель, и как ей видит игрок.
Дальше уже можно углубляться в процесс оптимизации в движке и расчёт лимитов под среднестатистическое железо — но если не осознавать, каким образом игрок видит сцену в вашей игре, то все эти расчёты — лишь потеря времени.
Время — крайне важный ресурс не только для рендера, но и для вас как для специалиста.
Если вы пытаетесь сэкономить лишнюю тысячу полигонов, потратив на это несколько лишних часов, а на частоту кадров эта экономия никак не повлияет, то вы просто попусту потратили рабочие часы студии или своё личное время. Особенно обидно, если вы фрилансер, — ведь вместо двух моделей за пять часов вы сделали всего лишь одну.

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

Ваша задача — сделать классную модель, классный арт, классную игру; а не заниматься «преждевременной оптимизацией».
***
Денис Куандыков — один из преподавателей курса по левел-дизайну OutBlock, который стартует 15 апреля.

Среди других преподавателей — Михаил Кадиков (Crytek), Макс Пирс (CD Projekt Red), Елена Кондрашина (Mundfish) и Илья Иванов (CD Projekt Red).

Узнать дополнительную информацию и записаться на курс можно здесь.
Понравилась статья?
Хочешь получать лучшие статьи
от XyZ раз в неделю?
Подпишись на рассылку XyZ
Нажимая на кнопку, вы соглашаетесь с условиями обработки данных