В данном задании вам предстоит оптимизировать отрисовку 3D-моделей в случае когда на сцене много-много одинаковых объектов.
- В идеале, скопируйте ваше решение model_bakery в эту папку. Если его у вас нет, скопируйте заготовку-рендерилку из model_bakery. Не забудьте поменять название таргета в CMake.
- Поменяйте загружаемую сцену на Avocado, предварительно прогнав её через печку.
- Запустите рендерилку. Что с производительностью? Посмотрите в профайлер.
Также наведём немного терминологии.
RenderElement— группа треугольников с фиксированным материалом.- Модель — набор
RenderElement. Например, фонарь. - Инстанс модели — пара (модель, матрица трансформации). Обратите внимание, что не возможно отрисовать на экран просто модель, так как мы не знаем где конкретно на сцене мы хотим её нарисовать, отрисовать можно только инстанс модели. Если модель — фонарь, то на сцене конечно же будет далеко не один фонарь, а геометрию фонаря мы хотим хранить ровно один раз.
- Инстанс
RenderElement— пара (RenderElement, матрица трансформации). Если посмотреть на инстанс модели с другой стороны, то он по факту состоит из инстансовRenderElement. Каждый инстансRenderElementв наивной реализации соответствует ровно одном вызовуvkCmdDrawIndexedс корректно выставленными биндингами (параметрами матриала и матрицами трансформации).
Поменяйте отрисовку в вашем приложении так, чтобы как можно больше инстансов RenderElement рисовалось за один дроу-колл.
Для этого используйте функцию вулкана vkCmdDrawIndexed с параметром instanceCount > 1.
При желании можно использовать vkCmdDrawIndexedIndirect и в ручную загружать параметры вызова отрисовки на GPU, тем самым рисуя несколько разных RenderElement за один дроу-колл.
При таком подходе нам придётся заранее загрузить матрицы трансформации каждого инстанса в единый буфер на GPU и читать их в вершинном шейдере по индексу инстанса. Будьте аккуратны: второй шаг задания может повлиять на то, в какой момент вам нужно загружать эти матрицы на GPU. В обратную сторону может повлиять бонусный уровень. Ознакомьтесь с ними перед тем как бежать писать код.
Если вы выполнили бонус в прошлой домашке, то объединить в один дроу-колл отрисовку инстансов RenderElement с разными материалами во всяком случае не получится.
Чтобы побороть эту проблему, вам придётся отсортировать инстансы RenderElement по используемому материалу и аналогично отсортировать матрицы трансформации, делать один дроу-колл на каждый материал, а нужный "сдвиг" в массив матриц можно реализовать через параметр firstInstance.
Добавьте в приложение фрустум куллинг на CPU. Иными словами,
- вычислите bounding box для каждого
RenderElement; - преобразуйте все вершины bounding box кажого
RenderElementматрицами MVP каждого инстанса модели чтобы получить bounding box инстансовRenderElement; - выберите только те инстансы
RenderElement, у которых хотя бы одна вершина bounding box попала внутри единичного куба в пространстве экрана; - на отрисовку передавайте только выбранные инстансы
RenderElement.
Проверьте через профайлер что отрисовка стала быстрее на GPU когда на экране видно только одну авокадину.
Реализуйте фрустум куллинг на GPU. Для этого загрузите заранее все данные сцены на GPU и напишите компьют-шейдер который будет выполнять действия аналогичные шагу 2 основного задания, составляя на выходе буферы с количеством вызовов отрисовки и самими вызовами отрисовки для vkCmdDrawIndexedIndirectCount.
Вновь не забудьте, что если у вас прикручены материалы, то вам придётся по-отдельности совершать эти действия для каждого материала.
В будущем мы решим эту проблему и будем рисовать всю сцену за один дроу-колл vkCmdDrawIndexedIndirectCount, а пока предлагается костылять, либо вообще временно открутить материалы.
- https://docs.vulkan.org/spec/latest/chapters/drawing.html — справка по разным командам отрисовки
- https://vkguide.dev/docs/gpudriven/gpu_driven_engines/ — про GPU-driven rendering в целом (код там устаревший и не работает, но объяснения и ссылки полезные!)