操作系統:Windows8.1
顯卡:Nivida GTX965M
開發工具:Visual Studio 2017
Introduction
在接下來幾個章節中,我們將會使用內存頂點緩沖區來替換之前硬編碼到vertex shader中的頂點數據。我們將從最簡單的方法開始創建一個CPU可見的緩沖區,並使用memcpy直接將頂點數據直接復制到緩沖區,之后將會使用暫存緩沖區將頂點數據賦值到高性能的內存。
Vertex shader
首先要修改的是頂點着色器,不再包含頂點數據。頂點着色器接受頂點緩沖區的輸入使用in關鍵字。
#version 450 #extension GL_ARB_separate_shader_objects : enable layout(location = 0) in vec2 inPosition; layout(location = 1) in vec3 inColor; layout(location = 0) out vec3 fragColor; out gl_PerVertex { vec4 gl_Position; }; void main() { gl_Position = vec4(inPosition, 0.0, 1.0); fragColor = inColor; }
inPosition和inColor變量是頂點屬性。它們被頂點緩沖區中的每一個頂點指定,就像我們使用兩個數組手動指定每個頂點的position和color一樣。現在確保着色器被正確編譯!
Vertex data
我們將頂點數組從着色器代碼移到我們程序自定義的數組中。首先我們需要引入GLM庫,它提供了像向量和矩陣之類的線性代數數據結構。我們使用這些類型指定position和顏色。
#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結構體作為頂點數組的元素類型。我們使用與之前完全相同的position和color值,但是現在它們被組合成一個頂點數組。這被稱為 interleaving 頂點屬性。
Binding descriptions
一旦數據被提交到GPU的顯存中,就需要告訴Vulkan傳遞到頂點着色器中數據的格式。有兩個結構體用於描述這部分信息。
第一個結構體VkVertexInputBingdingDescription,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之后移動到下一個數據條目
我們不會使用instancing渲染,所以堅持使用per-vertex data方式。
Attribute descriptions
第二個結構體描VkVertexInputAttributeDescription述如何處理頂點的輸入。我們需要在Vertex中增加一個新的輔助函數。
#include <array> ... static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescriptions() { std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions = {}; return attributeDescriptions; }
如函數圓形所示,該結構體為兩個。一個屬性描述結構體最終描述了頂點屬性如何從對應的綁定描述過的頂點數據來解析數據。我們有兩個屬性,position和color,所以我們需要兩個屬性描述結構體。
attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; attributeDescriptions[0].offset = offsetof(Vertex, pos);
binding參數告訴了Vulkan每個頂點數據的來源。location參數引用了vertex shader作為輸入的location指令。頂點着色器中,location為0代表position,它是32bit單精度數據。
format參數描述了屬性的類型。該格式使用與顏色格式一樣的枚舉,看起來有點亂。下列的着色器類型和格式是比較常用的搭配。
- float: VK_FORMAT_R32_SFLOAT
- vec2: VK_FORMAT_R32G32_SFLOAT
- vec3: VK_FORMAT_R32G32B32_SFLOAT
- vec4: V_FORMAT_R32G32B32A32_SFLOAT
如你所見,你應該使用顏色數量與着色器數據類型中的分量個數匹配的格式。允許使用比着色器中的分量個數更大的范圍,但是它將會被默認丟棄。如果低於着色器分量的數量,則BGA組件將使用默認值(0, 0, 1)。顏色類型(SFLOAT, UINT, SINT) 和位寬度應該與着色器輸入的類型對應匹配。如下示例:
- ivec2: VK_FORMAT_R32G32_SINT,由兩個32位有符號整數分量組成的向量
- uvec4: VK_FORMAT_R32G32B32A32_UINT, 由四個32位無符號正式分量組成的向量
- double: VK_FORMAT_R64_SFLOAT, 雙精度浮點數(64-bit)
format參數在屬性數據中被隱式的定義為字節單位大小,並且offset參數指定了每個頂點數據讀取的字節寬度偏移量。綁定一次加載一個Vertex,position屬性(pos)的偏移量在字節數據中為0字節。這是使用offsetof macro宏自動計算的。
attributeDescriptions[1].binding = 0; attributeDescriptions[1].location = 1; attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; attributeDescriptions[1].offset = offsetof(Vertex, color);
color顏色屬性與position位置屬性的描述基本一致。
Pipeline vertex input
我們現在需要在createGraphicsPipeline函數中,配置圖形管線可以接受重新定義的頂點數據的格式。找到vertexInputInfo結構體,修改引用之前定義的兩個有關輸入頂點的description結構體:
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();
圖形管線現在准備接受vertices容器封裝后的頂點數據,並將該格式的頂點數據傳遞到vertex shader。如果開啟了validation layers運行程序,我們將會看到無頂點緩沖區綁定的提示。所以下一章節我們將會創建頂點緩沖區vertex buffer並把頂點數據存儲在里面,最終GPU通過頂點緩沖區讀取到頂點數據。
項目代碼 GitHub 地址。
