VULKAN API — УРОК 41. Uniform buffer

Сейчас необходимо создать буфер, который будет содержать данные UBO для шейдеров. Скопируем новые данные в uniform buffer каждого кадра, так что на данный момент промежуточный буфер должен быть поблизости.

Добавим новый члены класса для uniformStagingBuffer, uniformStagingBufferMemory, uniformBuffer, и uniformBufferMemory:

VDeleter<VkBuffer> indexBuffer{device, vkDestroyBuffer};
VDeleter<VkDeviceMemory> indexBufferMemory{device, vkFreeMemory};

VDeleter<VkBuffer> uniformStagingBuffer{device, vkDestroyBuffer};
VDeleter<VkDeviceMemory> uniformStagingBufferMemory{device, vkFreeMemory};
VDeleter<VkBuffer> uniformBuffer{device, vkDestroyBuffer};
VDeleter<VkDeviceMemory> uniformBufferMemory{device, vkFreeMemory};

Аналогичным образом создадим новую функцию createUniformBuffer, вызываемую после createIndexBuffer, которая и выделит буферы:

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

...

void createUniformBuffer() {
    VkDeviceSize bufferSize = sizeof(UniformBufferObject);

    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformStagingBuffer, uniformStagingBufferMemory);
    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, uniformBuffer, uniformBufferMemory);
}

Далее нужно написать отдельную функцию, которая обновит uniform buffer с учетом новых данных преобразования каждого кадра, так что здесь не будет операций vkMapMemory и copyBuffer.

void mainLoop() {
    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();

        updateUniformBuffer();
        drawFrame();
    }

    vkDeviceWaitIdle(device);
}

...

void updateUniformBuffer() {

}

Теперь опишем, что же должно происходить в updateUniformBuffer. Она будет генерировать новое преобразование каждого кадра для вращения. Нужно добавить новые хедеры, что бы получить этот функционал:

#define GLM_FORCE_RADIANS
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include <chrono> 

glm/gtc/matrix_transform.hpp добавляет функционал, что может быть использован для генерации моделей преобразования, на подобии glm::rotate, отображения преобразования – glm::lookAt и проекций – glm::perspective. GLM_FORCE_RADIANS необходимо для того, что бы убедиться, что функции вроде glm::rotate используют радианты в качестве аргументов, дабы избежать всяческих конфузов.

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

updateUniformBuffer функция начинается с вызова нескольких функций для расчета времени в миллисекундах с начала рендеринга, если понадобится более точная настройка, то можно использовать std::chrono::microseconds, т.е. 1e6f (1000000.0f):

void updateUniformBuffer() {
    static auto startTime = std::chrono::high_resolution_clock::now();

    auto currentTime = std::chrono::high_resolution_clock::now();
    float time = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startTime).count() / 1000.0f;
}

Теперь определим модельное, отображаемое и проекционное преобразования в объекте uniform buffer. Поворот модели будет простым поворотом вокруг оси Z, используя переменную time.

Функция glm::rotate принимает существующее преобразование, угол вращения и ось как параметры. Конструктор glm::mat4() по умолчанию возвращает единичную матрицу. Используя угол вращения, time * glm::radians(90.0f) выполняет вращение на 90 градусов в секунду:

UniformBufferObject ubo = {};
ubo.model = glm::rotate(glm::mat4(), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f));

 Для преобразований отображения давайте посмотрим сверху под углом в 45 градусов. Функция glm::lookAt принимает позицию «глаза», позицию центра и направление вверх:

ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f));

Было выбрано использовать перспективную проекцию с углом 45 градусов по вертикали. Остальные параметры – соотношение сторон, ближние и дальние плоскости. Важно использовать extent текущей swap chain для расчета соотношения сторон для получения новых ширины и высоты окна после изменения размера.

ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f);

GLM был разработан для OpenGL, где координата Y клип координат перевернута, самым простым путем решения данного вопроса будет изменить знак оси Y в матрице проекции:

ubo.proj[1][1] *= -1;

Все преобразования были выполнены, так что можно скопировать данные в uniform buffer. Это делается так же, как и ранее перенос из staging buffer в vertex buffers:

void* data;
vkMapMemory(device, uniformStagingBufferMemory, 0, sizeof(ubo), 0, &data);
    memcpy(data, &ubo, sizeof(ubo));
vkUnmapMemory(device, uniformStagingBufferMemory);

copyBuffer(uniformStagingBuffer, uniformBuffer, sizeof(ubo));

Использование промежуточного буфера и конечного буфера таким образом не является наиболее эффективным способом передачи часто изменяемых переменных в шейдер. Более эффективным способом передачи малых буферов данных в шейдеры являются push constant’ы. К ним скорее всего еще обратимся.

Main Admin

4 Comments

  1. А не могли бы вы выложить список значений в матрице, я работаю в другом языке и библиотеки почему-то по разному перемножают, а может быть и в памяти хранят по другому. Если не сложно, значения float32 как они лежат в памяти, то есть в том же порядке x, y, z.

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

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