Vulkan API — урок 44. Текстурирование (texture mapping), предисловие

До этого момента геометрию просто окрашивали по вершинам, что является очень ограниченным подходом, теперь же перейдем к отображению текстурам. Это так же позволит загружать 3D модели, что так же будет описано.

Нужно добавить текстуру в приложение, для этого выполним следующие шаги:

  • Создать объект изображения в памяти устройства
  • Заполнить его пикселями из файла изображения
  • Создать шаблон  (sampler) изображения
  • Добавить комбинированный дескриптор (descriptor) сэмплера изображения для получения цветов из текстуры

Ранее с объектами изображений уже сталкивались, но то были автоматически  создаваемые расширения swap chain. На этот раз все нужно делать самостоятельно. Создание изображения и его заполнение очень похоже на создание буфера вершин. Создается VkImage, запрашиваются требования к памяти, выделяется память устройства, память связывается с изображением, и наконец размечается (map) память для выгрузки пиксельных данных. Будет снова использоваться промежуточное и конечное изображение, что бы текстура изображения находилась в быстрой памяти устройства.

Однако изображения могут иметь различные форматы (layouts), что повлияет на то, как пиксели будут размещены в памяти. Из-за особенностей работы железа, построчное хранение пикселей может показать далеко не лучшую производительность. При выполнении различных операций над изображением, нужно убедиться, что изображение имеет наиболее подходящий формат для текущей операции. На самом деле некоторые форматы (layouts) Вы уже видели, это было во время прохода рендера:

  • VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: Оптимальное решение для представления.
  • VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: Оптимален как вложение для написания цветов из fragment shader.
  • VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: Для операций передачи в качестве источника, как то vkCmdCopyImage
  • VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: Для операций передачи в качестве получателя, как то vkCmdCopyImage
  • VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: Для дискретизации (sampling) из шейдера

Одним из наиболее распространенных способов изменения формата (transition layouts) изображения является барьер конвейера (pipeline barrier). Барьеры конвейера в основном используют для синхронизации доступа к ресурсам, например что бы убедиться, что изображение было написано для того, как начнется чтение, но они так же могут использоваться  для изменения форматов.

Библиотека изображений

На просторах всея интернета можно найти множество библиотек для загрузки изображений, или же даже написать свою. В данных уроках будет использоваться stb_image из stb collection. Её преимущество в том, что весь код находится в одном файле, потому конфигурация крайне проста. Качаем stb_image.h и кладем (или ложим) его в папку, где хранятся  другие скачанные библиотеки (GLFW и GLM).

Если используете Visual studio, то должно получиться примерно так (теоретически можно подключить просто ссылку, не скачивая файл, но у меня что-то пошло не так и ничего из этого не вышло, если у кого получилось – пишите в комментах):

stb

Загрузка изображения

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

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

Создадим функцию createTextureImage, в которой будем загружать изображение и передавать его в объект изображения Vulkan’а.  Будет использоваться буфер команд, так что её вызов должен идти после createCommandPool.

void initVulkan() {
    ...
    createCommandPool();
    createTextureImage();
    createVertexBuffer();
    ...
}

...

void createTextureImage() {

}

Создадим директорию для хранения текстур, можно назвать её просто «textures». Для примера была использована следующая картинка с разрешением 512 х 512, но можете использовать то, что вам больше понравится:

Загрузка изображения при помощи уже написанной библиотеки очень проста:

void createTextureImage() {
    int texWidth, texHeight, texChannels;
    stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
    VkDeviceSize imageSize = texWidth * texHeight * 4;

    if (!pixels) {
        throw std::runtime_error("failed to load texture image!");
    }
}

Функция stbi_load принимает путь к файлу и количество каналов. Значение STBI_rgb_alpha заставляет изображение быть загруженным с альфа каналом, даже если его не имеет, что окажется полезным при согласовании с другими текстурами. Три параметра по середине предназначены для вывода ширины, высоты и фактического количества каналов изображения. Возвращается указатель на первый элемент в массиве пикселей. Пиксели располагаются по строкам, 4 байта на пиксель в случае STBI_rgba_alpha, объем занимаемой памяти соответственно занимает texWidth * texHeight * 4.

Main Admin

8 Comments

  1. Подскажите пожалуйста, stb_image умеет работать с атласом текстур?

    • Извиняюсь, не понял вопрос. Возможно Вас смутил момент в листинге, он выдал ошибку. Была опечатка, заместо «stb_image» я напечатал «std_image», в остальных частях текста было указано верное наименование (как и в итоговом листинге).

  2. вместо угловых кавычек поставьте верхние двойные штрихи (ваш редактор их заменил на << ). Угловые кавычки ссылаются на системные хедеры, а " " у меня нашел в директории программы.

    • Вообще да, «This method is normally used to include standard library header files.», так говорится об угловых ковычках. В данном случае я не просто добавляю библиотеку в папку с кодом, а создаю отдельно папку, и использую её как «системную». Для этого в Визуал Студии и производились указанные действия, что бы система понимала что я запрашиваю. Если же Вы не пользуетесь студией (и вполне могу это понять), то для GCC в makefile добавляете это:

      VULKAN_SDK_PATH = /home/user/VulkanSDK/x.x.x.x/x86_64
      STB_INCLUDE_PATH = /home/user/libraries/stb
      
      ...
      
      CFLAGS = -std=c++11 -I$(VULKAN_SDK_PATH)/include -I$(STB_INCLUDE_PATH)
  3. На мой взгляд очень неудачный перевод некоторых терминов. Sampler это сущность, представляющая настройки sampling’а, то есть загрузки данных из текстуры с обработкой текстурных координат и фильтрацией данных, это даже противоположно дискретизации. Ближе по значению слова загрузчик/загрузка или фильтр/фильтрация. Наиболее корректно, конечно, было бы сохранение английских слов или транслитерация — сэмплер и сэмплинг

    • Потому во многом термины оставлял на английском, или как здесь в скобках оставил оригинал (тут отчасти подходит термин дискретизации, но согласен, что он не совсем корректен). Если найдете еще подобные проблемы — оставляйте комментарии, думаю читателям будет полезно.

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

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