操作系統(tǒng):Windows8.1

顯卡:Nivida GTX965M

開發(fā)工具:Visual Studio 2017


Introduction

到目前為止,幾何圖形使用每個頂點顏色進行著色處理,這是一個局限性比較大的方式。在本教程的一部分內(nèi)容中,我們實現(xiàn)紋理映射,使得幾何圖形看起來更加生動有趣。這部分使我們在未來的章節(jié)中加載和繪制基本的3D模型。

 

添加一個貼圖到應用程序需要以下幾個步驟:

  • 創(chuàng)建設備內(nèi)存支持的圖像對象

  • 從圖像文件填充像素

  • 創(chuàng)建圖像采樣器

  • 添加組合的圖像采樣器描述符,并從紋理采樣顏色信息

我們之前已經(jīng)使用過圖像對象,但是它們都是由交換鏈擴展自動創(chuàng)建的。這次我們將要自己創(chuàng)建。創(chuàng)建一個圖像及填充數(shù)據(jù)與之前的頂點緩沖區(qū)創(chuàng)建類似。我們開始使用暫存資源并使用像素數(shù)據(jù)進行填充,接著將其拷貝到最終用于渲染使用的圖像對象中。盡管可以為此創(chuàng)建一個暫存圖像,Vulkan也允許從VkBuffer中拷貝像素到圖像中,這部分API在一些硬件上非常有效率 faster on some hardware。我們首先會創(chuàng)建緩沖區(qū)并通過像素進行填充,接著創(chuàng)建一個圖像對象拷貝像素。創(chuàng)建一個圖像與創(chuàng)建緩沖區(qū)類似。就像我們之前看到的那樣,它需要查詢內(nèi)存需求,分配設備內(nèi)存并進行綁定。

 

然而,仍然有一些額外的工作需要面對,當我們使用圖像的時候。我們知道圖像可以有不同的布局,它影響實際像素在內(nèi)存中的組織。由于圖形硬件的工作原理,簡單的逐行存儲像素可能不是最佳的性能選擇。對圖像執(zhí)行任何操作時,必須確保它們有最佳的布局,以便在該操作中使用。實際上我們已經(jīng)在指定渲染通道的時候看過這些布局類型:

  • VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: 用于呈現(xiàn),使用最佳

  • VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: 當使用附件從片段著色器進行寫入時候,使用最佳

  • VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: 作為傳送源操作的時候,使用最佳,比如vkCmdCopyImageToBuffer

  • VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: 最為傳輸目的地的時候,使用最佳,比如vkCmdCopyBufferToImage

  • VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: 著色器中用于采樣,使用最佳

變換圖像布局的最常見方式之一是管線屏障 pipeline barrier。管線屏障主要用于同步訪問資源,諸如確保圖像在讀之前寫入,但是也可以用于布局變換。在本章節(jié)中我們將會看到如何使用管線屏障完成此任務。除此之外,屏障也可以用于VK_SHARING_MODE_EXCLUSIVE模式下隊列簇宿主的變換。

Image library


用于加載圖片的庫有很多,甚至可以自己編寫代碼加載簡單格式的圖片比如BMP和PPM。在本教程中我們將會使用stb_image庫。優(yōu)勢是所有的代碼都在單一的文件中,所以它不需要任何棘手的構建配置。下載stb_image.h頭文件并將它保存在方便的位置,在這里我們存放與GLFW、GLM、vulkan頭文件的相同的目錄中 3rdparty\Include 下,如圖所示:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

Visual Studio

確認$(SolutionDir)\3rdparty\Include添加到 Additional Include Directories 路徑中。

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

Loading an image


包含image庫的頭文件:

#define STB_IMAGE_IMPLEMENTATION#include <stb_image.h>

默認情況下頭文件僅僅定義了函數(shù)的原型。一個代碼文件需要使用STB_IMAGE_IMPLEMENTATION定義包含頭文件中定義的函數(shù)體,否則會收到鏈接錯誤。

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

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

...void createTextureImage() {

}

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

創(chuàng)建新的函數(shù)createTextureImage用于加載圖片和提交到Vulkan圖像對象中。我們也會使用命令緩沖區(qū),所以需要在createCommandPool之后調(diào)用。

 

shaders目錄下新增新的textures目錄,用于存放貼圖資源。我們將會從目錄中加載名為texture.jpg的圖像。這里選擇 CC0 licensed image 并調(diào)整為512 x 512像素大小,但是在這里可以使用任何你期望的圖片。庫支持很多主流的圖片文件格式,比如JPEG,PNG,BMP和GIF。

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

使用庫加載圖片是非常容易的:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

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!");
    }
}

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

stbi_load函數(shù)使用文件的路徑和通道的數(shù)量作為參數(shù)加載圖片。STBI_rgb_alpha值強制加載圖片的alpha通道,及時它本身沒有alpha,但是這樣做對于將來加載其他的紋理的一致性非常友好。中間三個參數(shù)用于輸出width, height 和實際的圖片通道數(shù)量。返回的指針是像素數(shù)組的第一個元素地址??偣?nbsp;texWidth * texHeight * 4 個像素值,像素在STBI_rgba_alpha的情況下逐行排列,每個像素4個字節(jié)。

Staging buffer


我們現(xiàn)在要在host visible內(nèi)存中創(chuàng)建一個緩沖區(qū),以便我們可以使用vkMapMemory并將像素復制給它。在createTextureImage函數(shù)中添臨時緩沖區(qū)變量。

VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;

緩沖區(qū)必須對于host visible內(nèi)存可見,為此我們對它進行映射,之后使用它作為傳輸源拷貝像素到頭像對象中。

createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);

我們可以直接從庫中加載的圖片中拷貝像素到緩沖區(qū):

void* data;
vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data);
    memcpy(data, pixels, static_cast<size_t>(imageSize));
vkUnmapMemory(device, stagingBufferMemory);

不要忘記清理原圖像的像素數(shù)據(jù):

stbi_image_free(pixels);

Texture Image


雖然我們可以通過設置著色器訪問緩沖區(qū)中的像素值,但是在Vulkan中最好使用image對象完成該操作。圖像對象可以允許我們使用而二維坐標來更容易的快速的檢索顏色。圖像中的像素被成為紋素即紋理元素,我們將從此處開始使用該名稱。添加以下新的類成員:

VkImage textureImage;
VkDeviceMemory textureImageMemory;

對于圖像的參數(shù)通過VkImageCreateInfo結構體來描述:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

VkImageCreateInfo imageInfo = {};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.width = static_cast<uint32_t>(texWidth);
imageInfo.extent.height = static_cast<uint32_t>(texHeight);
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

imageType字段指定圖像類型,告知Vulkan采用什么樣的坐標系在圖像中采集紋素。它可以是1D,2D和3D圖像。1D圖像用于存儲數(shù)組數(shù)據(jù)或者灰度圖,2D圖像主要用于紋理貼圖,3D圖像用于存儲立體紋素。extent字段指定圖像的尺寸,基本上每個軸上有多少紋素。這就是為什么深度必須是1而不是0。我們的紋理不會是一個數(shù)組,而現(xiàn)在我們不會使用mipmapping功能。

imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;

Vulkan支持多種圖像格式,但無論如何我們要在緩沖區(qū)中為紋素應用與像素一致的格式,否則拷貝操作會失敗。

imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;

tiling字段可以設定兩者之一:

  • VK_IMAGE_TILING_LINEAR: 紋素基于行主序的布局,如pixels數(shù)組

  • VK_IMAGE_TILING_OPTIMAL: 紋素基于具體的實現(xiàn)來定義布局,以實現(xiàn)最佳訪問

與不像布局不同的是,tiling模式不能在之后修改。如果需要在內(nèi)存圖像中直接訪問紋素,必須使用VK_IMAGE_TILING_LINEAR。我們將會使用暫存緩沖區(qū)代替暫存圖像,所以這部分不是很有必要。為了更有效的從shader中訪問紋素,我們將會使用VK_IMAGE_TILING_OPTIMAL。

imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;

對于圖像的initialLayout字段,僅有兩個可選的值:

  • VK_IMAGE_LAYOUT_UNDEFINED: GPU不能使用,第一個變換將丟棄紋素。

  • VK_IMAGE_LAYOUT_PREINITIALIZED: GPU不能使用,但是第一次變換將會保存紋素。

最初未定義的布局適用于將用作附件的圖像,如顏色和深度緩沖區(qū)。在這個情況下我們不關心任何初始的數(shù)據(jù),因為它很可能會在使用前被render pass清理掉。如果你想填充數(shù)據(jù),比如貼圖,你應該使用preinitialized layout。

imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;

usage字段在緩沖區(qū)創(chuàng)建過程中有相同的語意。圖像將會被用作緩沖區(qū)拷貝的目標,所以應該設置作為傳輸目的地。我們還希望從著色器中訪問圖像對我們的mesh進行著色,因此具體的usage還要包括VK_IMAGE_USAGE_SAMPLED_BIT

imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

 因為圖像會在一個隊列簇中使用:支持圖形或者傳輸操作。

imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.flags = 0; // Optional

 samples標志位與多重采樣相關。這僅僅適用于作為附件的圖像,所以我們堅持一個采樣數(shù)值。與稀疏圖像相關的圖像有一些可選的標志。稀疏圖像是僅僅某些區(qū)域實際上被存儲器支持的圖像。例如,如果使用3D紋理進行立體地形,則可以使用此方法來避免分配內(nèi)存來存儲大量“空氣”值。我們不會在本教程中使用,所以設置默認值0

if (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS) {    throw std::runtime_error("failed to create image!");
}

使用vkCreateImage創(chuàng)建圖像,這里沒有任何特殊的參數(shù)設置。可能圖形硬件不支持VK_FORMAT_R8G8B8A8_UNORM格式。我們應該持有一個可以替代的可以接受的列表。然而對這種特定格式的支持是非常普遍的,我們將會跳過這一步。使用不同的格式也需要繁瑣的轉換過程。我們會回到深度緩沖區(qū)章節(jié),實現(xiàn)類似的系統(tǒng)。

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

VkMemoryRequirements memRequirements;
vkGetImageMemoryRequirements(device, textureImage, &memRequirements);

VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);if (vkAllocateMemory(device, &allocInfo, nullptr, &textureImageMemory) != VK_SUCCESS) {    throw std::runtime_error("failed to allocate image memory!");
}

vkBindImageMemory(device, textureImage, textureImageMemory, 0);

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

為圖像工作分配內(nèi)存與為緩沖區(qū)分配內(nèi)存是類似的,使用vkGetImageMemoryRequirements代替vkGetBufferMemoryRequirements,并使用vkBindImageMemory代替vkBindBufferMemory。

 

這個函數(shù)已經(jīng)變得比較龐大臃腫了,而且需要在后面的章節(jié)中創(chuàng)建更多的圖像,所以我們應該將圖像創(chuàng)建抽象成一個createImage函數(shù),就像之前為buffers緩沖區(qū)做的事情一樣。創(chuàng)建函數(shù)并將圖像對象的創(chuàng)建和內(nèi)存分配移動過來:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) {
    VkImageCreateInfo imageInfo = {};
    imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    imageInfo.imageType = VK_IMAGE_TYPE_2D;
    imageInfo.extent.width = width;
    imageInfo.extent.height = height;
    imageInfo.extent.depth = 1;
    imageInfo.mipLevels = 1;
    imageInfo.arrayLayers = 1;
    imageInfo.format = format;
    imageInfo.tiling = tiling;
    imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
    imageInfo.usage = usage;
    imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
    imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;    if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) {        throw std::runtime_error("failed to create image!");
    }

    VkMemoryRequirements memRequirements;
    vkGetImageMemoryRequirements(device, image, &memRequirements);

    VkMemoryAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.allocationSize = memRequirements.size;
    allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);    if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) {        throw std::runtime_error("failed to allocate image memory!");
    }

    vkBindImageMemory(device, image, imageMemory, 0);
}

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

這里使用了width, height, format, tiling mode, usage和memory properties參數(shù),因為這些參數(shù)根據(jù)教程中創(chuàng)建的圖像而不同。

 

createTextureImage函數(shù)現(xiàn)在簡化為:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

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!");
    }

    VkBuffer stagingBuffer;
    VkDeviceMemory stagingBufferMemory;
    createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);    void* data;
    vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data);
        memcpy(data, pixels, static_cast<size_t>(imageSize));
    vkUnmapMemory(device, stagingBufferMemory);

    stbi_image_free(pixels);

    createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory);
}

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

Layout transitions


我們將要編寫的函數(shù)會涉及到記錄和執(zhí)行命令緩沖區(qū),所以現(xiàn)在適當?shù)囊瞥恍┻壿嫷捷o助函數(shù)中去:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

VkCommandBuffer beginSingleTimeCommands() {
    VkCommandBufferAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    allocInfo.commandPool = commandPool;
    allocInfo.commandBufferCount = 1;

    VkCommandBuffer commandBuffer;
    vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);

    VkCommandBufferBeginInfo beginInfo = {};
    beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

    vkBeginCommandBuffer(commandBuffer, &beginInfo);    return commandBuffer;
}void endSingleTimeCommands(VkCommandBuffer commandBuffer) {
    vkEndCommandBuffer(commandBuffer);

    http://www.cnblogs.com/heitao/p/7102419.html