Vulkan API — урок 23. Фиксированные функции (ч1)

В старых API большинство шагов конвейера устанавливается по умолчанию. В Vulkan’е же так нельзя, и вам придется все указывать конкретно, начиная от размера окна и до операций функции смешения цветов.

Получение вершин (Vertex input)

Структура VkPipelineVertexInputStateCreateInfo определяет формат данных вершин, которые будут переданы в vertex shader. Определение можно разделить на две основных части:

  • Связь: расстояние между данными и данные по вершинам (per-vertex) или по образцам (per-instance, читайте тут).
  • Описание атрибутов: тип атрибутов, переданных в vertex shader, который определяет откуда их загружать и в куда.

Т.к. мы пока жестко в коде задаем данные вершин непосредственно в vertex shader, мы будем заполнять эту структуру с учетом отсутствия необходимости в загрузке вершин. Ранее упоминалось про буфер вершин, после создания оного мы вернемся к редактированию этой части.

VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 0;
vertexInputInfo.pVertexBindingDescriptions = nullptr; // Optional
vertexInputInfo.vertexAttributeDescriptionCount = 0;
vertexInputInfo.pVertexAttributeDescriptions = nullptr; // Optional

Элементы pVertexBindingDescriptions и pVertexAttributeDescriptions указывают на массив структур, описывающих вышеупомянутые детали для загрузки данных вершин. Добавьте эту структуру в функцию createGraphicsPipeline после массива shaderStages.

Input assembly

Структура VkPipelineInputAssemblyStateCreateInfo описывает две вещи: какой тип геометрии будет отрисовываться на основе вершин и условия, когда должна быть включена перезагрузка примитивов. Шаблон определяется в элементе topology  и имеет одно из значений:

  • VK_PRIMITIVE_TOPOLOGY_POINT_LIST: точки на основе вершин.
  • VK_PRIMITIVE_TOPOLOGY_LINE_LIST: линии на основе двух вершин, без повторного использования вершин.
  • VK_PRIMITIVE_TOPOLOGY_LINE_STRIP: каждая вторая вершина используется как начало новой линии.
  • VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST: треугольник на основе трех вершин,  без повторного использования вершин.
  • VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP: каждая третья вершина как начало нового треугольника.

Как правило, вершины загружаются из буфера по индексу по порядку, но в буфере элементов вы можете определять индексы нужным вам образом. Это позволяет произвести оптимизировать, на подобии повторного использования вершин. Если установить primitiveRestartEnable  на значение VK_TRUE, то будет возможно разбить линии и треугольники в режиме _STRIP  используя специальные индексы 0xFFFF или 0xFFFFFFFF.

Для создания обычного треугольника зададим следующие значения:

VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;

Область просмотра и обрезка (Viewports and scissors)

Viewport определяет размеры фреймбуфера для которого и будет производиться рендеринг. Почти всегда это будет (0, 0) на (width, height):

VkViewport viewport = {};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float) swapChainExtent.width;
viewport.height = (float) swapChainExtent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;

Следует помнить, что размер swap chain и его изображений может отличаться от WIDTH и HEIGHT окна. Изображения swap chain будут использованы как фреймбуферы позже, так что мы будем придерживаться их размеров.

Значения minDepth и maxDepth определяют диапазон значений глубины, используемых фреймбуфером. Диапазон должен быть в пределах [0.0f, 1.0f], но minDepth может быть больше maxDepth. Если вы не собираетесь делать что-то особенное, то ставьте эти значения.

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

В данном туториале мы хотим заполнить весь фреймбуфер, так что пусть scissor покрывает его целиком:

VkRect2D scissor = {};
scissor.offset = {0, 0};
scissor.extent = swapChainExtent;

Теперь viewport и scissor должны быть объеденены и использованы в качестве состоянии окна просмотра, для чего используется структура VkPipelineViewportStateCreateInfo. Можно использовать несколько viewports и scissor на разных видеокартах, для этого потребуется включение функций GPU (искать в создании логического устройства «Настройка используемых функций устройства»).

VkPipelineViewportStateCreateInfo viewportState = {};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;

Rasterization

Растеризатор принимает геометрию, которая формируется на основе вершин, полученных из vertex shader, и разбивает её на фрагменты, которые будут окрашены  fragment shader. Он так же проверяет глубину, отбрасывает грани и проверяет scissor, может выводить фрагменты, заполняющие как фигуры целиком, так и только лишь ребра (wireframe rendering – рендеринг каркаса). Все эти настройки указываются в структуре VkPipelineRasterizationStateCreateInfo.

VkPipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;

Если depthClampEnable установлен в значение VK_TRUE, тогда фрагменты, находящиеся за ближней и дальней плоскостью, прикрепляются к ним, а не отбрасываются. Это бывает полезно в ряде случаев, например для карты теней. Для использования необходимо включить функцию GPU.

rasterizer.rasterizerDiscardEnable = VK_FALSE;

Если rasterizerDiscardEnable установлен в значение VK_TRUE, тогда геометрия не проходит через растеризатор. По сути это отключает любой вывод в фреймбуфер.

rasterizer.polygonMode = VK_POLYGON_MODE_FILL;

polygonMode  определяет, как фрагменты генерируются для геометрии. Доступны следующие режимы:

  • VK_POLYGON_MODE_FILL: заполнить площадь многоугольника фрагментами;
  • VK_POLYGON_MODE_LINE: края многоугольников изображаются в виде линий;
  • VK_POLYGON_MODE_POINT: вершины многоугольников в виде точек;

Только использование заливки не требует включения функции GPU.

rasterizer.lineWidth = 1.0f;

Параметр lineWidth достаточно прост, он описывает толщину линий с точки зрения числа фрагментов. Максимальная ширина линии зависит от железа и значение более 1.0f требует включения функции wideLines GPU.

rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;

cullMode определяет тип граней для отсеивания. Вы можете отключить эту возможность, можете отсекать передние или задние грани, или все вместе. frontFace определяет последовательность вершин для граней как начиная с передних и по часовой стрелке.

rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.depthBiasConstantFactor = 0.0f; // Optional
rasterizer.depthBiasClamp = 0.0f; // Optional
rasterizer.depthBiasSlopeFactor = 0.0f; // Optional

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

Поспешил и выложил черновой вариант урока, сейчас в тексте был исправлен ряд недосказанностей и опечаток. Код, как и ранее, в полном порядке.

Main Admin

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

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