Vulkan API — урок 59. Модель в действии

Теперь загрузим вершины и индексы из файла модели. Перед этим удалим глобальные массивы vertices и indices. Заменим их не константными членами класса:

std::vector<Vertex> vertices;
std::vector<uint32_t> indices;
VDeleter<VkBuffer> vertexBuffer{device, vkDestroyBuffer};
VDeleter<VkDeviceMemory> vertexBufferMemory{device, vkFreeMemory};

Тип индексов теперь uint32_t, потому как вершин будет гораздо больше, чем 65535. Так же изменим параметр vkCmdBindIndexBuffer:

vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT32);

Библиотека tinyobjloader включает построена по тому же принципу, что и STB библиотека. Определим TINYOBJLOADER_IMPLEMENTATION и подключим файл tiny_obj_loader.h:

#define TINYOBJLOADER_IMPLEMENTATION
#include <tiny_obj_loader.h>

Теперь напишем функцию loadModel, использующую эту библиотеку для заполнения vertices и indices данными. Они должна вызываться для создания буферов, вершин и индексов:

void initVulkan() {
    ...
    loadModel();
    createVertexBuffer();
    createIndexBuffer();
    ...
}

...

void loadModel() {

}

Модель загружается в структуры данных библиотеки при помощи вызова функции tinyobj::LoadObj.

Файл OBJ содержит позиции, координаты текстур и грани. Грани состоят из произвольного количества вершин, где каждая вершина ссылается к позиции, нормали и/или координате текстуры по индексу. Это делает возможным не только повторное использование вершины целиком, но и отдельные атрибуты.

attrib хранит все позиции, нормали и координаты текстур в attrib.vertices, attrib.normals и attrib.texcoords  соответственно.

shapes содержит отдельные объекты и их грани. Каждая грань состоит из массива вершин, а каждая вершина содержит индексы позиции, нормали и координаты текстуры. OBJ модели могут так же определять материал и текстуры по граням, но в данный момент это использовано не будет.

Строка err содержит ошибки и предупреждения, произошедшие во время загрузки, к примеру отсутствующее определение материала. Если загрузка завершилась с действительно серьезной ошибкой, то функция LoadObj вернет false. Как упоминалось ранее, грани в файле OBJ могут содержать произвольное количество вершин, в то время, как наше приложение рендерит только треугольники. Но вот имеет опциональный параметр для автоматической триангуляции подобных граней, параметр включен по умолчанию.

void loadModel() {
    tinyobj::attrib_t attrib;
    std::vector shapes;
    std::vector materials;
    std::string err;

    if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, MODEL_PATH.c_str())) {
        throw std::runtime_error(err);
    }
}

Объединим все грани в файле в одну модель, просто пройдем по ним всем, объединяя в наш вектор vertices:

for (const auto& shape : shapes) {
    for (const auto& index : shape.mesh.indices) {
        Vertex vertex = {};

        vertices.push_back(vertex);
        indices.push_back(indices.size());
    }
}

Для простоты будем считать, что каждая вершина сейчас уникальна, следовательно просто добавляем индексы. Переменная index имеет тип tinyobj::index_t, который содержит vertex_index, normal_index и texcoord_index. Нам нужно использовать все это, что бы просматривать фактические атрибуты вершин в массивах атрибут. К сожалению массив attrib.vertices является массивом значений типа float, взамен чего-то вроде glm::vec3, так что вам нужно умножить индекс на 3, а затем использовать смещение. Аналогичным образом поступаем с координатами текстур:

vertex.pos = {
    attrib.vertices[3 * index.vertex_index + 0],
    attrib.vertices[3 * index.vertex_index + 1],
    attrib.vertices[3 * index.vertex_index + 2]
};

vertex.texCoord = {
    attrib.texcoords[2 * index.texcoord_index + 0],
    attrib.texcoords[2 * index.texcoord_index + 1]
};

vertex.color = {1.0f, 1.0f, 1.0f};

Теперь можно запустить приложение (используйте Release мод в Visual Studio, а не отладочный, так программа будет загружать модель в разы быстрее (-O3 флаг для GCC)). После этого увидите что-то вроде:

model1

С геометрией все хорошо, но с текстурой наблюдаем проблемы. Суть в том, что начало координат Vulkan – верхний левый угол, в то время, как OBJ предполагает левый нижний угол. Эту проблему следует исправить:

vertex.texCoord = {
    attrib.texcoords[2 * index.texcoord_index + 0],
    1.0f - attrib.texcoords[2 * index.texcoord_index + 1]
};

Теперь все должно стать хорошо:

model2

Вот по сути и все. Основные элементы Vulkan’а были описаны, конечно это далеко не все его возможности, но их полное описание вы найдете на сайте производителя в документации 🙂 . Примеры использования всего остального вполне можно посмотреть и в расширенных уроках про другие API, основные концепции работают аналогично, основные отличия Вам уже знакомы. А примеры реализации этого функционала можно поглядеть в уже готовых движках, лично я собираюсь пощупать Unreal Engine 4 (не рекомендация, сам только в день написания урока его впервые качаю).

Как говорится: «Спасибо за внимание!»

Послесловие

Это не означает, что сайт будет заброшен!

Вы можете оставлять ссылки на различные статьи и страницы, перевод (с английского) которых хотели бы увидеть. Основное условие – они должны быть связаны с геймдевом/рендером/расчетами на видеокартах, не важно статьи про Vulkan, OpenGL, DirectX, либо же не касаются работы видеокарт напрямую, но связаны с указанными ранее отраслями. В дальнейшем возможно и вернусь к полноценным урокам, но если это и свершится, то не в ближайший квартал точно.

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

Main Admin

13 Comments

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

  2. Касательно желаемых материалов для перевода хоть и хотелось бы видеть перевод примеров вулкана SaschaWillems, но к сожалению у него переводить нечего. 😀 Если увижу что интересное для перевода обязательно отпишусь тут.

    • Спасибо за данный отзыв! Выставил нормальный порядок всех уроков на соответствующей странице, нужно еще будет найти время и пройтись по тексту, исправить очепятки.

      Как упоминалось выше, на данный момент на «чистый» Vulkan «подзабил», и решил попробовать его, и не только, в Unreal. Через пару месяцев сформирую собственное мнение о UE4 и напишу здесь.

      Написать свой движек под конкретную цель, без всей этой лишней нагрузки, конечно всегда лучше, но иногда осознаешь — времени на это нет(

  3. Экспериментировал с полноэкранным режимом в этом конечном примере. Имеется кратковременный графический артефакт при выходе и альт-табе (выглядит как не знаю как описать, как мерцание наверное). Этот эффект при выходе удалось пофиксить с помощью glfwDestroyWindow(window), если кто знает как пофиксить при альт-табе отпишитесь пожалуйста.

    • «Заявку» оставил, но вот тишина, видимо русскоязычное комьюнити им не интересно. Оно в принципе и понятно — заморачиваться и переводить для проверки слишком сложно, а аудитория слишком слабо развита, фокусироваться на ней смысла не видят. У нас разве что готовые движки используют (за крайне и крайне редким исключением, и это самое исключение и так английский знает)

  4. «Заявка» на перевод — спецификация GLSL 4.5
    Хотя там 200 страниц, многовато, но если вдруг 🙂

  5. Глянул внимательней — похоже, ее все же в оригинале лучше читать.

    • Все же да, лучше оригинал, и спецификацию целиком — долго будет. Но если какой-то раздел, или, допустим, главу из указанной ранее книги (Jason Gregory — Game Engine Architecture) — это могу.

  6. Тогда есть пожелание перевести разделы из OpenGL Book 4.3, посвященные GLSL. Так как актуальной литературы по шейдерам на русском в принципе нет: https://www.google.ru/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=0ahUKEwijvorYkNzRAhUhG5oKHfTLAA8QFggfMAA&url=https%3A%2F%2Fwww.ics.uci.edu%2F~gopi%2FCS211B%2Fopengl_programming_guide_8th_edition.pdf&usg=AFQjCNHXH6NOglyb0Xk6ynDE8R8HwJq9bA&sig2=NtdnE6thwUrYYWZT1ols7g&cad=rjt

  7. Спасибо. А возможно ли загружать модели из Blender в Vulkan? И как это сделать? И как сразу загрузить несколько моделей?

    • Можно, но нужно найти библиотеку которая будет это делать). Ну либо написать самостоятельно

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *