Vulkan. Руководство разработчика. Вершинные буферы

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.


Я начинаю перевод нового раздела Vulkan Tutorial (vulkan-tutorial.com), который называется Vertex buffers.
Сегодняшняя публикация посвящена первой главе раздела — Vertex input description.

Содержание
1. Вступление

2. Краткий обзор

3. Настройка окружения

4. Рисуем треугольник

  1. Подготовка к работе
    • Базовый код
    • Экземпляр (instance)
    • Слои валидации
    • Физические устройства и семейства очередей
    • Логическое устройство и очереди
  2. Отображение на экране
    • Window surface
    • Swap chain
    • Image views
  3. Графический конвейер (pipeline)
    • Вступление
    • Шейдерные модули
    • Непрограммируемые этапы
    • Проходы рендера
    • Заключение
  4. Отрисовка
    • Фреймбуферы
    • Буферы команд
    • Рендеринг и отображение на экране
  5. Пересоздание swap chain

5. Буферы вершин

  1. Описание
  2. Создание буфера вершин
  3. Staging буфер
  4. Буфер индексов

6. Uniform-буферы

  1. Дескриптор layout и буфера
  2. Дескриптор пула и sets

7. Текстурирование

  1. Изображения
  2. Image view и image sampler
  3. Комбинированный image sampler

8. Буфер глубины

9. Загрузка моделей

10. Создание мип-карт

11. Multisampling

FAQ

Политика конфиденциальности


Описание входных данных вершин


  • Вступление
  • Вершинный шейдер
  • Данные вершин
  • Описание привязки (binding)
  • Описание атрибутов
  • Входные данные пайплайна


Вступление


В следующих главах мы заменим данные вершин, встроенные в вершинный шейдер, на вершинный буфер в памяти. Начнем с самого простого — создадим буфер видимый для CPU и используем memcpy, чтобы напрямую копировать в него данные вершин. Затем рассмотрим, как использовать промежуточный буфер для копирования данных вершин в высокопроизводительную память.

Вершинный шейдер


Сначала изменим код вершинного шейдера и уберем из него данные вершин. Используем ключевое слово in, чтобы шейдер принимал данные из вершинного буфера.

#version 450

layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec3 inColor;

layout(location = 0) out vec3 fragColor;

void main() {
    gl_Position = vec4(inPosition, 0.0, 1.0);
    fragColor = inColor;
}

Переменные inPosition и inColor — атрибуты вершин. Это свойства, которые указываются для каждой вершины в буфере точно так же, как мы указывали координаты и цвет с помощью массива в шейдере. Не забудьте заново скомпилировать вершинный шейдер!

Точно так же, как это было у fragColor, layout (location = x) присваивает индекс каждому атрибуту, чтобы в дальнейшем можно было ссылаться на них. Важно знать, что некоторые типы, например 64-битные векторы dvec3, используют несколько слотов. Это значит, что следующий индекс после него должен быть как минимум на 2 больше:

layout(location = 0) in dvec3 inPosition;
layout(location = 2) in vec3 inColor;

Дополнительную информацию о layout квалификаторе можно найти в OpenGL wiki.

Данные вершин


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

#include <glm/glm.hpp>

Создадим новую структуру Vertex с двумя атрибутами, которые соответствуют входным данным вершинного шейдера:

struct Vertex {
    glm::vec2 pos;
    glm::vec3 color;
};

GLM очень кстати предоставляет типы C ++, которые в точности соответствуют векторным типам, используемым в языке шейдеров.

const std::vector<Vertex> vertices = {
    {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}},
    {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},
    {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}
};

Используя структуру Vertex, укажем массив данных вершин. Мы используем те же значения координат и цвета, что и раньше, но теперь они объединены в один массив вершин.

Описание привязки (binding)


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

Первая структура — VkVertexInputBindingDescription. Добавим метод в структуру Vertex, чтобы заполнить ее нужными данными.

struct Vertex {
    glm::vec2 pos;
    glm::vec3 color;

    static VkVertexInputBindingDescription getBindingDescription() {
        VkVertexInputBindingDescription bindingDescription{};

        return bindingDescription;
    }
};

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

VkVertexInputBindingDescription bindingDescription{};
bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

Все наши данные собраны в один массив, поэтому у нас будет только одна привязка. Параметр binding указывает номер привязки в массиве. Параметр stride указывает расстояние между элементами данных. А параметр inputRate может иметь одно из следующих значений:

  • VK_VERTEX_INPUT_RATE_VERTEX: переход к следующему элементу данных происходит после каждой вершины
  • VK_VERTEX_INPUT_RATE_INSTANCE: переход к следующему элементу данных происходит после каждого экземпляра (instance)

Мы будем использовать VK_VERTEX_INPUT_RATE_VERTEX.

Описание атрибутов


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

#include <array>

...

static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescriptions() {
    std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};

    return attributeDescriptions;
}

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

attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Vertex, pos);

Параметр binding сообщает Vulkan, от какой привязки поступают данные для вершины. Параметр location ссылается на директиву location в вершинном шейдере. В нашем шейдере значение location = 0 соответствует координатам вершины. Координаты представлены двумя 32-разрядными числами типа float.

Параметр format описывает тип данных для атрибута. Может показаться странным, что для format используется такое же перечисление, что и для форматов цвета. Обычно используют такие соответствия типа и формата:

  • floatVK_FORMAT_R32_SFLOAT
  • vec2VK_FORMAT_R32G32_SFLOAT
  • vec3VK_FORMAT_R32G32B32_SFLOAT
  • vec4VK_FORMAT_R32G32B32A32_SFLOAT

Как вы видите, необходимо использовать формат, в котором количество цветовых каналов совпадает с количеством компонентов в типе данных шейдера. Можно использовать больше каналов, чем количество компонентов в шейдере, но они будут автоматически отброшены. Если количество каналов меньше количества компонентов, компоненты GBA будут использовать значения по умолчанию (0, 0, 1). Тип цвета (SFLOAT, UINT, SINT) и разрядность также должны соответствовать типу входных данных шейдера. Ниже представлены примеры:

  • ivec2VK_FORMAT_R32G32_SINT, двухкомпонентный вектор 32-битных целых чисел со знаком
  • uvec4VK_FORMAT_R32G32B32A32_UINT, 4-компонентный вектор 32-битных целых чисел без знака
  • doubleVK_FORMAT_R64_SFLOAT, число с плавающей запятой двойной точности (64-битное)

Параметр format неявно определяет размер данных атрибута в байтах, а параметр offset указывает смещение данных атрибута от начала считанного для вершины куска. Данные для каждой из вершин загружаются в виде структуры Vertex, а атрибут pos смещен на 0 байт от начала этой структуры. Можно использовать макрос offsetof, чтобы делать эти расчеты автоматически.

attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, color);

Аналогично описывается атрибут цвета.

Входные данные пайплайна


Теперь изменим метод createGraphicsPipeline, добавив ссылки на наши структуры, чтобы пайплайн принимал данные вершин в нужном формате. Найдем vertexInputInfo и добавим туда ссылки:

auto bindingDescription = Vertex::getBindingDescription();
auto attributeDescriptions = Vertex::getAttributeDescriptions();

vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();

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

Код C++ / Вершинный шейдер / Фрагментный шейдер
Источник: https://habr.com/ru/post/571944/


Интересные статьи

Интересные статьи

В этой статье я попыталась собрать несколько своих техник тестирования на Python. Не стоит воспринимать их как догму, поскольку, думаю, со временем я обновлю свои практик...
Я переводчик из ижевской компании CG Tribe, и я продолжаю выкладывать перевод руководства к Vulkan API. Ссылка на источник — vulkan-tutorial.com. В этой публикации представлен перевод...
Чуть больше года назад я столкнулся с тем, что на внутреннем проекте совсем не айтишной компании вырос целый отдел веб-разработки, которым мне и довелось руководить. Рабочий процесс вроде...
Введение Данная статья предназначена вниманию системных администраторов, которые подготавливают типовые рабочие места на компьютерах под управлением ОС Windows 10, в том числе дл...
Одной из «киллер-фич» 12й версии Битрикса была объявлена возможность отдавать статические файлы из CDN, тем самым увеличивая скорость работы сайта. Попробуем оценить практический выигрыш от использова...