一、著色器代碼更新及構建時自動編譯著色器腳本
用內存中的頂點緩沖區替換頂點著色器中硬編碼的頂點數據
之前的頂點著色器:
#version 450layout(location = 0) out vec3 fragColor;// 頂點數據硬編碼
vec2 positions[3] = vec2[](vec2(0.0, -0.5),vec2(0.5, 0.5),vec2(-0.5, 0.5)
);// 顏色數據硬編碼
vec3 colors[3] = vec3[](vec3(1.0, 0.0, 0.0),vec3(0.0, 1.0, 0.0),vec3(0.0, 0.0, 1.0)
);void main() {gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);fragColor = colors[gl_VertexIndex];
}
現在的頂點著色器:
#version 450layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec3 inColor;layout(location = 0) out vec3 fragColor;void main() {gl_Position = vec4(inPosition, 0.0, 1.0);fragColor = inColor;
}
每次改動著色器都手動輸入glslc 命令把著色器編譯為 spv 字節碼有些效率低下,可在 CMake 構建階段自動調用 glslc 命令編譯著色器,修改 CMakeLists.txt :
# ------------后面追加修改的配置部分-----------# 查找所有著色器源文件
file(GLOB SHADER_SRC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/assets/shaders/*.vert" "${CMAKE_CURRENT_SOURCE_DIR}/assets/shaders/*.frag")
set(SHADER_SPV_FILES "")
foreach(SHADER ${SHADER_SRC_FILES})message("compile shader file "${SHADER})get_filename_component(FILE_NAME ${SHADER} NAME)set(SPV "${CMAKE_CURRENT_SOURCE_DIR}/assets/shaders/${FILE_NAME}.spv")message("spv path: "${SPV})add_custom_command(OUTPUT ${SPV}COMMAND glslc ${SHADER} -o ${SPV}DEPENDS ${SHADER}COMMENT "Compiling shader ${FILE_NAME}")list(APPEND SHADER_SPV_FILES ${SPV})
endforeach()add_custom_target(CompileShaders ALL DEPENDS ${SHADER_SPV_FILES})add_custom_command(TARGET ${PROJECT_NAME} POST_BUILDCOMMAND ${CMAKE_COMMAND} -E copy_directory${CMAKE_CURRENT_SOURCE_DIR}/assets$<TARGET_FILE_DIR:${PROJECT_NAME}>/assetsCOMMENT "Copying assets to build directory..."
)
二、頂點數據與綁定描述
創建一個VkTypes.h頭文件,添加頂點數據結構體 Vertex,頂點/顏色都作為屬性,這稱為交錯頂點屬性。
#pragma once
#include <vulkan/vulkan.h>#include <array>
#include <glm/glm.hpp>namespace renderer { // 渲染器相關類和結構的命名空間// 頂點數據結構,包含位置和顏色信息struct Vertex {glm::vec2 pos; // 二維坐標位置(x,y)glm::vec3 color; // RGB顏色值// 獲取頂點輸入綁定描述// 描述頂點數據如何按字節偏移量組織在內存中static VkVertexInputBindingDescription getBindingDescription() {VkVertexInputBindingDescription bindingDescription{};bindingDescription.binding = 0; // 綁定點索引bindingDescription.stride = sizeof(Vertex); // 頂點數據大小(字節)bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; // 按頂點處理數據return bindingDescription;}// 獲取頂點屬性描述數組// 描述如何從頂點數據中提取各個屬性static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescriptions() {std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};// 位置屬性描述attributeDescriptions[0].binding = 0; // 對應綁定點索引attributeDescriptions[0].location = 0; // 在頂點著色器中對應的locationattributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; // 32位浮點數x2 (x,y)attributeDescriptions[0].offset = offsetof(Vertex, pos); // 位置屬性的內存偏移// 顏色屬性描述attributeDescriptions[1].binding = 0; // 對應綁定點索引attributeDescriptions[1].location = 1; // 在頂點著色器中對應的locationattributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; // 32位浮點數x3 (R,G,B)attributeDescriptions[1].offset = offsetof(Vertex, color); // 顏色屬性的內存偏移return attributeDescriptions;}};} // namespace renderer
- Vertex 結構:包含頂點的位置 (pos) 和顏色 (color) 數據
- getBindingDescription:
- 告訴 Vulkan 頂點數據如何組織
- stride 設置為整個 Vertex 大小,表示每個頂點數據是連續排列的
- getAttributeDescriptions:
- 定義每個屬性的存儲格式和內存偏移
- pos 使用 2 個 32 位浮點數 (VK_FORMAT_R32G32_SFLOAT)
- color 使用 3 個 32 位浮點數 (VK_FORMAT_R32G32B32_SFLOAT)
- offsetof 宏:計算結構體成員相對于起始地址的字節偏移量
三、頂點緩沖
可見緩沖區并使用 memcpy 將頂點數據直接復制到其中的最簡單方法開始,之后我們將了解如何使用暫存緩沖區將頂點數據復制到高性能內存中。
在?vkInit 中添加創建頂點緩沖代碼:
// 創建命令池
{...
}// 創建頂點緩沖
{// 1. 創建頂點緩沖區對象VkBufferCreateInfo bufferInfo{};bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; // 結構體類型bufferInfo.size = vkcontext->vertexBufferSize; // 緩沖區大小(字節)bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; // 用作頂點緩沖區bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; // 獨占模式(單隊列族訪問)// 創建緩沖區if (vkCreateBuffer(vkcontext->device, &bufferInfo, nullptr, &vkcontext->vertexBuffer) != VK_SUCCESS) {throw std::runtime_error("頂點緩沖創建失敗!");} // 2. 查詢緩沖區內存需求VkMemoryRequirements memRequirements;vkGetBufferMemoryRequirements(vkcontext->device, vkcontext->vertexBuffer, &memRequirements);// 3. 分配內存VkMemoryAllocateInfo allocInfo{};allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; // 結構體類型allocInfo.allocationSize = memRequirements.size; // 分配大小與需求一致// 查找合適的內存類型:主機可見(可映射CPU內存)且具有一致性allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, // 可用內存類型位掩碼VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, // 內存屬性vkcontext->physicalDevice // 物理設備);// 分配內存if (vkAllocateMemory(vkcontext->device, &allocInfo, nullptr, &vkcontext->vertexBufferMemory) != VK_SUCCESS) {throw std::runtime_error("為頂點緩沖分配內存失敗!");}// 4. 將內存綁定到緩沖區vkBindBufferMemory(vkcontext->device, vkcontext->vertexBuffer, vkcontext->vertexBufferMemory, 0);// 5. 向頂點緩沖區寫入數據void* data;// 映射內存到CPU可訪問的地址空間vkMapMemory(vkcontext->device, vkcontext->vertexBufferMemory, 0, bufferInfo.size, 0, &data);// 使用memcpy復制頂點數據到映射的內存區域memcpy(data, vkcontext->vertexData, (size_t) bufferInfo.size);// 解除內存映射(數據已提交)vkUnmapMemory(vkcontext->device, vkcontext->vertexBufferMemory);// 注:由于使用了VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,// 無需額外的內存屏障來確保GPU可見性
}// 創建命令緩沖
{...
}
關鍵步驟解析:
-
創建緩沖區對象:
- 指定緩沖區大小和用途(頂點緩沖區)
- 分享模式設為獨占,因為通常只由圖形隊列族訪問
-
查詢內存需求:
- Vulkan 要求先查詢緩沖區的內存需求(大小、對齊和可用內存類型)
-
分配內存:
- 創建 CPU 使用 findMemoryType 函數查找符合條件的內存類型
- 選擇主機可見內存以便 CPU 可以寫入數據
- 選擇一致性內存以避免手動刷新內存范圍
-
綁定內存到緩沖區:
- 將分配的內存塊與緩沖區對象關聯
-
數據傳輸:
- 映射內存到 CPU 地址空間
- 使用
memcpy
復制頂點數據 - 解除映射,完成數據傳輸
四、綁定頂點緩沖
修改HelloTriangle類,添加頂點顏色數據:
--------------HelloTriangle.h----------
#pragma once
#include <vector>#include "renderer/VkTypes.h"using namespace renderer;class HelloTriangle {
public:HelloTriangle();std::vector<Vertex> vertices;
};-------------HelloTriangle.cpp-------------
#include "HelloTriangle.h"HelloTriangle::HelloTriangle(): vertices({{{0.0f, -0.5f}, {1.0f, 1.0f, 1.0f}},{{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},{{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}
}) {}
在VkContext 中新增成員變量:
struct VkContext {...void* vertexData;uint32_t vertexNum{0};size_t vertexBufferSize{0};VkBuffer vertexBuffer;VkDeviceMemory vertexBufferMemory;};
在修改主函數邏輯:
int main() {initWindow();vkcontext.window = window;HelloTriangle helloTriangleApp;vkcontext.vertexData = helloTriangleApp.vertices.data();vkcontext.vertexNum = helloTriangleApp.vertices.size();vkcontext.vertexBufferSize = helloTriangleApp.vertices.size() * sizeof(Vertex);if (!vkInit(&vkcontext)) {throw std::runtime_error("Vulkan 初始化失敗!");}try {while (!glfwWindowShouldClose(window)) {glfwPollEvents();vkRender(&vkcontext);}vkDeviceWaitIdle(vkcontext.device);vkClean(&vkcontext);glfwDestroyWindow(window);glfwTerminate();} catch (const std::exception& e) {std::cerr << e.what() << std::endl;return EXIT_FAILURE;}return EXIT_SUCCESS;
}
修改命令錄制部分,綁定頂點緩沖:
VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = vkcontext->swapChainExtent;
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);// --------修改部分開始-----------------------------
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vkcontext->vertexBuffer, offsets);vkCmdDraw(commandBuffer, vkcontext->vertexNum, 1, 0, 0);
//---------修改部分結束--------------------------------------
vkCmdEndRenderPass(commandBuffer);
現在修改下頂點顏色值,構建運行看看效果:
一切正常,Validation Layer 也沒有輸出錯誤日志!
當前代碼分支為 06_vertexInputdescription_vertexbuffer