У Java Edition есть несколько основных движков — графический, световой, клиентский и серверный. Название известно только для графического движка — Blaze3D. Остальные не имеют названия и просто существуют (либо я просто за все годы не слышал об их названиях). Они создавались по ходу дела в процессе разработки игры.
Крупнейший из перечисленных — серверный, он включает в себя обработку всего, что происходит с игроком и с игровым миром в принципе с точки зрения логики и событий. В свою очередь включает в себя отдельные модули, которые тоже можно рассматривать как отдельные движки.
Клиентский отвечает за обработку серверных данных и взаимодействие с ними посредством действий игрока.
Графический движок отвечает за визуализацию всего, что мы видим на экране. При этом, его работа тесно связана со световым движком.
Световой движок работает независимо как со стороны сервера (расчёт освещения при обновлении блоков, загрузке/генерации чанков), так и со стороны клиента (обработка серверных данных освещения и передача сведений графическому движку).

Как работает графика Minecraft? Движок майнкрафта

Важно не путать понятие движка и ЯП. Java — это не движок, это язык, на котором написана бо́льшая часть Java Edition.

Источник: minecraftru.net

Создание Minecraft за одну неделю на C++ и Vulkan

Я поставил перед собой задачу воссоздания с нуля Minecraft за одну неделю с помощью собственного движка на C++ и Vulkan. Меня вдохновил на это Hopson, который сделал то же самое при помощи C++ и OpenGL. В свою очередь, его вдохновил Шейн Бек, которого вдохновила Minecraft, источником вдохновения для которой была Infiniminer, при создании которой, предположительно, вдохновлялись реальными горными промыслами.

Репозиторий GitHub этого проекта находится здесь. У каждого дня есть своя git-метка.

Разумеется, я не планировал в буквальном смысле воссоздавать Minecraft. Этот проект должен был стать обучающим. Я хотел изучить использование Vulkan в чём-то более сложном, чем vulkan-tutorial.com или демо Саши Виллема. Поэтому основной упор сделан на проектирование Vulkan-движка, а не на дизайн игры.

Задачи

Разработка на Vulkan намного медленнее, чем на OpenGL, поэтому я не смог включить в игру многие функции настоящей Minecraft. Нет ни мобов, ни крафта, ни красного камня, ни физики блоков, и т.п. С самого начала цели проекта были следующими:

  • Создание системы рендеринга рельефа
  • Мешинг
  • Освещение
  • Рельеф
  • Деревья
  • Биомы

Библиотеки

Разумеется, я не собирался писать Vulkan-приложение с нуля. Для ускорения процесса разработки я по возможности буду использовать готовые библиотеки. А именно:

  • VulkanWrapper — моя собственная обёртка на C++ для Vulkan API
  • GLFW — для окон и ввода пользователя
  • VulkanMemoryAllocator — для распределения памяти Vulkan
  • GLM — для математики векторов и матриц
  • entt — для сигналов/слотов и ECS
  • stb — для утилит загрузки изображений
  • FastNoise — для генерации 3D-шума

День 1

В первый день я подготовил бойлерплейт Vulkan и скелет движка. Большая часть кода была бойлерплейтом и я смог просто скопипастить его с vulkan-tutorial.com. В него вошёл и трюк с хранением данных вершин как части вершинного шейдера. Это означало, что мне даже не придётся настраивать распределение памяти. Всего лишь простой конвейер, который способен делать только одно: отрисовывать треугольник.

Движок достаточно прост, чтобы поддерживать рендерер треугольников. Он имеет одно окно и игровой цикл, к которому можно подключать системы. GUI ограничен частотой кадров, выводимой в заголовок окна.

Проект разделён на две части: VoxelEngine и VoxelGame .

День 2

Я интегрировал библиотеку Vulkan Memory Allocator. Эта библиотека берёт на себя большую часть бойлерплейта, связанного с распределением памяти Vulkan: типы памяти, кучи памяти устройств и вторичное распределение.

Читайте также:  Как работает удача в майнкрафт

Теперь, когда у меня было распределение памяти, я создал классы для мешей и буферов вершин. Я изменил рендерер треугольников так, чтобы он использовал класс мешей, а не встроенные в шейдер массивы. На данный момент передача данных мешей в GPU выполняется рендерером треугольников вручную.

Изменилось немногое

День 3

Я добавил систему графов рендеринга. Для создания этого класса взят за основу данный пост, но класс очень сильно упрощён. Мой граф рендеринга содержит только самое необходимое для обработки синхронизации с Vulkan.

Граф рендеринга позволяет мне задавать узлы и рёбра. Узлы представляют собой выполняемую GPU работу. Рёбра — это зависимости данных между узлами. Каждый узел получает собственный буфер команд, в который выполняет запись. Граф занимается двойной буферизацией буферов команд и синхронизацией их с предыдущими кадрами.

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

Узлы и рёбра образуют ориентированный ациклический граф. Затем граф рендеринга выполняет топологическую сортировку узлов, что приводит к созданию плоского списка узлов, отсортированных таким образом, что каждый узел идёт после всех узлов, от которых он зависит.

Движок имеет три типа узлов. AcquireNode получает образ из цепочки буферов (swapchain), TransferNode передаёт данные от CPU к GPU, а PresentNode предоставляет изображение цепочки буферов, которое нужно отобразить.

Каждый узел может реализовать preRender , render и postRender , которые выполняются в каждом кадре. AcquireNode получает изображение цепочки буферов во время preRender . PresentNode предоставляет это изображение во время postRender .

Я отрефакторил рендерер треугольников, чтобы он использовал систему графов рендеринга, а не обрабатывал всё самостоятельно. Существует ребро между AcquireNode и TriangleRenderer , а также между TriangleRenderer и PresentNode . Это гарантирует, что изображение цепочки буферов правильно синхронизировано в процессе его использования во время кадра.

Клянусь, внутри движок поменялся

День 4

Я создал камеру и систему 3D-рендеринга. Пока камера получает собственный постоянный буфер и пул дескрипторов.

В этот день я замедлился, потому что пытался найти подходящую конфигурацию для рендеринга 3D с помощью Vulkan. Большинство материалов онлайн посвящено рендерингу в помощью OpenGL, в котором используются немного отличающиеся от Vulkan системы координат. В OpenGL ось Z пространства усечения (clip space) задаётся как [-1, 1] , а верхний край экрана находится в Y = 1 . В Vulkan ось Z задаётся как [0, 1] , а верхний край экрана находится в Y = -1 . Из-за этих небольших отличий стандартные матрицы проецирования GLM не работают правильно, потому что они предназначены для OpenGL.

В GLM есть опция GLM_FORCE_DEPTH_ZERO_TO_ONE , устраняющая проблему с осью Z. После чего проблему с осью Y можно устранить простой сменой знака элемента (1, 1) матрицы проецирования (в GLM используется индексация от 0).

Если мы переворачиваем ось Y, то нужно перевернуть и данные вершин, потому что до этого отрицательное направление оси Y указывало вверх.

Теперь в 3D!

День 5

Я добавил ввод пользователя и возможность перемещения камеры при помощи мыши. Система ввода слишком переусложнена, но она устраняет странности ввода GLFW. В частности, у меня возникала проблема изменения позиции мыши при её блокировании.

Ввод с клавиатуры и кнопок мыши по сути является тонкой обёрткой поверх GLFW, открытой через обработчики сигналов entt .

Читайте также:  Как выглядит морковь в Майнкрафте

Просто для сравнения — примерно то же самое Hopson сделал в день 1 своего проекта.

Your browser does not support HTML5 video.

День 6

Я начал добавлять код для генерации и рендеринга блоков вокселей. Писать код мешинга было просто, потому что я делал это раньше и знал абстракции, позволяющие совершать меньше ошибок.

Одной из абстракций был шаблонный класс ChunkData , определяющий куб типа T размером chunkSize по каждой из сторон. Этот класс хранит данные в 1D-массиве и обрабатывает индексирование данных с 3D-координатой. Размер каждого блока составляет 16 x 16 x 16, поэтому внутренние данные представляют собой простой массив длиной 4096.

Ещё одна абстракция заключается в создании итератора позиций, генерирующего координаты от (0, 0, 0) до (15, 15, 15) . Эти два класса гарантируют, что итерации с данными блоков выполняются в линейном порядке для повышения локальности кэша. 3D-координата по-прежнему доступна для других операций, которым она нужна. Например:

for (glm::ivec3 pos : Chunk::Positions())

У меня есть несколько статических массивов, задающих смещения, которые обычно используются в игре. Например, Neighbors6 задаёт 6 соседей, с которыми куб имеет общие грани.

static constexpr std::array Neighbors6 = < glm::ivec3(1, 0, 0), //right glm::ivec3(-1, 0, 0), //left glm::ivec3(0, 1, 0), //top glm::ivec3(0, -1, 0), //bottom glm::ivec3(0, 0, 1), //front glm::ivec3(0, 0, -1) //back >;

Neighbors26 — это все соседи, с которыми у куба общая грань, ребро или вершина. То есть это сетка 3x3x3 без центрального куба. Также есть подобные массивы для других наборов соседей и для 2D-наборов соседей.

Существует массив, определяющий данные, необходимые для создания одной грани куба. Направления каждой грани в этом массиве соответствуют направлениям в массиве Neighbors6 .

static constexpr std::array NeighborFaces = < //right face FaceArray < glm::ivec3(1, 1, 1), glm::ivec3(1, 1, 0), glm::ivec3(1, 0, 1), glm::ivec3(1, 0, 0), >, . >;

Благодаря этому код создания мешей очень прост. Он просто обходит данные блоков и добавляет грань, когда блок сплошной, а его сосед — нет. Код просто проверяет каждую грань каждого куба в блоке. Это аналогично «наивному» методу, описанному здесь.

for (glm::ivec3 pos : Chunk::Positions()) < Block block = chunk.blocks()[pos]; if (block.type == 0) continue; for (size_t i = 0; i < Chunk::Neighbors6.size(); i++) < glm::ivec3 offset = Chunk::Neighbors6[i]; glm::ivec3 neighborPos = pos + offset; //NOTE: bounds checking omitted if (chunk.blocks()[neighborPos].type == 0) < Chunk::FaceArray for (size_t j = 0; j < faceArray.size(); j++) < m_vertexData.push_back(pos + faceArray[j]); m_colorData.push_back(glm::i8vec4(pos.x * 16, pos.y * 16, pos.z * 16, 0)); >> > >

Я заменил TriangleRenderer на ChunkRenderer . Также я добавил буфер глубин, чтобы меш блока мог рендериться правильно. Нужно было добавить ещё одно ребро в граф рендеринга между TransferNode и ChunkRenderer . Это ребро передаёт владение ресурсами семейства очередей между очередью передачи и очередью графики.

Затем я изменил движок так, чтобы он мог правильно обрабатывать события изменения окна. В OpenGL это делается просто, но довольно запутанно в Vulkan. Так как цепочка буферов должна создаваться явным образом и иметь постоянный размер, при изменении размера окна её нужно создавать заново. Необходимо создавать заново все ресурсы, зависящие от цепочки буферов.

Все команды, зависящие от цепочки буферов (а сейчас это все команды отрисовки), должны завершить выполнение перед уничтожением старой цепочки буферов. Это означает, что весь GPU будет простаивать.

Нужно изменить графический конвейер, чтобы обеспечить динамическое окно обзора и изменение размеров.

Цепочку буферов невозможно создать, если размер окна равен 0 по оси X или Y. В том числе, когда окно свёрнуто. То есть когда такое происходит, вся игра ставится на паузу и продолжается, только когда окно разворачивается.

Читайте также:  Как нарисовать шляпу из Майнкрафта

Сейчас меш является простой трёхмерной шахматной доской. Цвета RGB меша устанавливаются в соответствии с его позицией по XYZ, умноженной на 16.

Your browser does not support HTML5 video.

День 7

Я сделал так, чтобы игра обрабатывала за раз не один, а несколько блоков. Множественные блоки и их меши управляются ECS библиотеки entt . Затем я отрефакторил ренедрер блоков так, чтобы он рендерил все блоки, находящиеся в ECS. У меня по-прежнему только один блок, но я мог бы при необходимости добавить новые.

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

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

Предыдущий буфер вершин невозможно удалить сразу же. Могут существовать буферы команд, выполняемые из предыдущих кадров, которые зависят от конкретного объекта VkBuffer . Движок должен хранить буфер, пока эти буферы команд не завершатся. То есть, если мы рисуем меш в кадре i , GPU может использовать этот буфер до начала кадра i + 2 . Буфер нельзя удалять из ЦП, пока GPU не завершил его использовать. Поэтому я изменил граф рендеринга так, чтобы он отслеживал срок жизни ресурсов.

Если узел графа рендеринга хочет использовать ресурс (буфер или изображение), то он должен вызвать метод sync внутри метода preRender . Этот метод получает указатель shared_ptr на ресурс. Этот shared_ptr гарантирует, что ресурс не будет удалён, пока выполняются буферы команд. (С точки зрения производительности, такое решение не очень хорошее. Подробнее об этом позже.)

Теперь меш блока регенерируется в каждом кадре.

Your browser does not support HTML5 video.

Заключение

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

Источник: habr.com

Minecraft крупно обновится: новый движок и поддержка NVIDIA RTX

Студия Mojang рассказала, что ждать в обновлении Minecraft для Windows 10. Новый движок и трассировка лучей в реальном времени от NVIDIA RTX.

Что такое трассировка лучей?

Трассировка лучей (рейтрейсинг) — метод построения трёхмерных моделей, в котором используется принцип, аналогичный реальным физическим процессам. То есть для построения объекта система отслеживает траекторию виртуального луча от экрана к этому самому объекту.

416BD2A8-6C1C-4FCB-83E6-A662D765C487

1BF2B7FD-7B85-47BD-A74F-89D2A3D2819C

Мы будем видеть не объекты сами по себе, а отражённый от них свет.

Так что там за обновление такое?

Благодаря поддержке NVIDIA RTX, вода будет выглядеть как настоящая, блоки из разных материалов будут буквально сиять, разноцветные ковры будут освещать окружение. Ну и всё это в красивых лучах солнца.

Также игра получит новый движок Render Dragon: он улучшит освещение и производительность, а также повысит качество графики. С ним игра выглядит абсолютно иначе, но многое будет зависеть и от мощности ПК, конечно же.

Когда можно попробовать?

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

Признавайтесь, играете в Minecraft? Может, стримы смотрите?

Источник: wylsa.com