1. 程式人生 > >Vulkan Tutorial 22 Index buffer

Vulkan Tutorial 22 Index buffer

簡單的 apm man ets 發生 pecs 數量 紅色 www

操作系統:Windows8.1

顯卡:Nivida GTX965M

開發工具:Visual Studio 2017


Introduction

在實際產品的運行環境中3D模型的數據往往共享多個三角形之間的頂點數據。即使繪制一些簡單的圖形也是如此,比如矩形:

技術分享

繪制矩形需要兩個三角形,通常意味著我們需要6個頂點數據。問題是其中的兩個頂點會重復,導致數據會有50%的冗余。如果更復雜的模型,該問題會更加嚴重,平均每三個三角形就會發生重復頂點使用的情況。解決問題的方法是使用index buffer,即索引緩沖區。

索引緩沖區純粹是一個指向頂點緩沖區的指針數組。它允許我們重排列頂點數據,並復用多個已經存在的頂點數據。上圖顯示了有一個包含四個不重復頂點數據的頂點緩沖區,通過索引緩沖區將如何顯示矩形的情況。前三個索引定義右上角的三角形,最後三個索引定義了左下角的三角形。

Index buffer creation


在本章節中,為了繪制如上圖所示的矩形,我們需要修改頂點數據並添加索引數據。修改後的頂點數據分別代表矩形四個角:

const std::vector<Vertex> vertices = {
    {{-0.5f, -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}},
    {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}
};

左上角是紅色,右上角是綠色,右下角是藍色,左下角是白色。我們添加一個新的索引數組indices

代表索引緩沖區的數據內容。它應該匹配途中的索引來繪制右上角的三角形和左下角的三角形。

const std::vector<uint16_t> indices = {
    0, 1, 2, 2, 3, 0
};

根據vertices中的條目個數,我們可以使用uint16_tuint32_t作為索引緩沖區類型。現在我們可以使用uint16_t,因為我們使用的獨立頂點數量小於65535。

如頂點數據,為了使GPU可以訪問到它們,需要將索引數據上傳到緩沖區VkBuffer。定義兩個類成員保存索引緩沖區的資源:

VkBuffer vertexBuffer;
VkDeviceMemory vertexBufferMemory;
VkBuffer indexBuffer;
VkDeviceMemory indexBufferMemory;

createIndexBuffer函數與之前的createVertexBuffer函數非常類似:

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

void createIndexBuffer() {
    VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size();

    VkBuffer stagingBuffer;
    VkDeviceMemory stagingBufferMemory;
    createBuffer(bufferSize, 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, bufferSize, 0, &data);
    memcpy(data, indices.data(), (size_t) bufferSize);
    vkUnmapMemory(device, stagingBufferMemory);

    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory);

    copyBuffer(stagingBuffer, indexBuffer, bufferSize);

    vkDestroyBuffer(device, stagingBuffer, nullptr);
    vkFreeMemory(device, stagingBufferMemory, nullptr);
}

僅有的兩個差異。bufferSize現在等於索引數量乘以索引類型的大小,該類型或者是uint16_t,或者是uint32_tindexBuffer的用法需改用VK_BUFFER_USAGE_INDEX_BUFFER_BIT代替VK_BUFFER_USAGE_VERTEX_BUFFER_BIT。其他的過程是一致的。我們創建暫存緩沖區拷貝頂點數據的內容,並最終拷貝到設備本地索引緩沖區。

索引緩沖區在程序退出的時候需要清理,與頂點緩沖區類似:

void cleanup() {
    cleanupSwapChain();

    vkDestroyBuffer(device, indexBuffer, nullptr);
    vkFreeMemory(device, indexBufferMemory, nullptr);

    vkDestroyBuffer(device, vertexBuffer, nullptr);
    vkFreeMemory(device, vertexBufferMemory, nullptr);

    ...
}

Using an index buffer


使用索引緩沖區繪制需要修改createCommandBuffers函數兩個地方。首先需要綁定索引緩沖區,就像之前的頂點緩沖區一樣。區別是現在僅使用一個索引緩沖區。不幸的是,不可能對每個頂點屬性使用不同的索引,所以即使只有一個屬性不同,我們仍然必須完全復制頂點數據。

vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);

vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16);

索引緩沖區使用vkCmdBindIndexBuffer綁定,它持有索引緩沖區作為參數,還需要偏移量和索引數據的類型。如前所述,可能的類型是VK_INDEX_TYPE_UINT16VK_INDEX_TYPE_UINT32

僅僅綁定索引緩沖區不會發生任何改變,我們還需要告知Vulkan在使用索引緩沖區後,對應的繪制命令的變化。移除vkCmdDraw函數,並用vkCmdDrawIndexed替換:

vkCmdDrawIndexed(commandBuffers[i], static_cast<uint32_t>(indices.size()), 1, 0, 0, 0);

該函數的調用與vkCmdDraw非常類似。前兩個參數指定索引的數量和幾何instance數量。我們沒有使用instancing,所以指定1。索引數表示被傳遞到頂點緩沖區中的頂點數量。下一個參數指定索引緩沖區的偏移量,使用1將會導致圖形卡在第二個索引處開始讀取。倒數第二個參數指定索引緩沖區中添加的索引的偏移。最後一個參數指定instancing偏移量,我們沒有使用該特性。

現在運行程序如下所示:

技術分享

現在我們已經通過索引緩沖區復用了頂點數據。在未來的加載復雜的3D模型數據中,該技術會非常的重要。

在前一章提到,為了更優的分配使用資源,推薦在單個內存中分配多個資源,如緩沖區,但是實際上,我們應該更進一步細化。來自Nvidia的驅動程序開發者建議將多個緩沖區(頂點緩沖區、索引緩沖區)存儲到單個VkBuffer中。並在諸如vkCmdBindVertexBuffers之類的命令中使用偏移量。優點在於,在這種情況下,數據會更加充分的利用緩存,因為它們排列在一塊區域。甚至在同一個渲染操作中可以復用來自相同內存塊的多個資源塊,只要刷新數據即可。該技巧稱為稱為aliasing,一些Vulkan函數有明確的標誌指定這樣做的意圖。

項目代碼 GitHub 地址。

Vulkan Tutorial 22 Index buffer