Vulkan【14】顶点缓冲区

本文详细介绍了在Vulkan API中创建顶点缓冲区的过程,包括创建缓冲区对象、分配内存、保存顶点数据以及绑定到渲染过程。同时,文中解释了顶点数据的结构和如何描述输入顶点数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

创建顶点缓冲区

本节的代码是 13-init_vertex_buffer.cpp

顶点缓存是一个CPU可见的和GPU可见的缓冲区,它包含了描述您想要呈现的对象(s)的几何图形的顶点数据。一般来说,顶点数据由位置(x、y、z)数据和可选的颜色、法线或其他信息组成。和其他3D api一样,这里的方法是用这个顶点数据填充缓冲区,并在绘制操作期间将其传递给GPU。

创建顶点缓冲区对象

创建一个顶点缓冲几乎等同于创建一个Uniform缓冲区,它首先创建一个缓冲对象,就像在Uniform例子中所做的那样。

VkBufferCreateInfo buf_info = {};
buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buf_info.pNext = NULL;
buf_info.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
buf_info.size = sizeof(g_vb_solid_face_colors_Data);
buf_info.queueFamilyIndexCount = 0;
buf_info.pQueueFamilyIndices = NULL;
buf_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
buf_info.flags = 0;
res = vkCreateBuffer(info.device, &buf_info, NULL, &info.vertex_buffer.buf);

创建Uniform缓冲区对象和顶点缓冲对象之间的唯一真正区别是usage字段的设置。

立方体数据(g_vb_solid_face_colors_Data)由36个顶点组成,这些顶点定义了12个三角形,每个6个立方体的每个面都有2个。每个三角形也有一个与之相关联的面的颜色。你可以检查cube_data.h文件来查看实际数据。

分配顶点缓冲区内存

同样,这些步骤与分配Uniform缓冲区的步骤非常相似。首先进行查询以获得内存需求,其中包括考虑到诸如对齐之类的机器限制。请查看样例中的代码。

将顶点数据保存到缓冲区中

一旦分配,内存就会被映射,用顶点数据初始化,然后取消映射:

uint8_t *pData;
res = vkMapMemory(info.device, info.vertex_buffer.mem, 0,
                  mem_reqs.size, 0, (void **)&pData);

memcpy(pData, g_vb_solid_face_colors_Data,
       sizeof(g_vb_solid_face_colors_Data));

vkUnmapMemory(info.device, info.vertex_buffer.mem);

作为最后一步,内存被绑定到缓冲区对象:

res = vkBindBufferMemory(info.device, info.vertex_buffer.buf,
                         info.vertex_buffer.mem, 0);

描述输入的顶点数据

下面列出了顶点数据的结构:

struct Vertex {
    float posX, posY, posZ, posW; // Position data
    float r, g, b, a;             // Color
};

您需要创建一个顶点输入绑定来告诉GPU这些数据的排列。下面的vi_bindingvi_attribs的成员是在这里设置的,但是在随后的“创建图形管线”示例中被使用。但是由于您正在查看顶点数据格式,所以最好在这里检查以下代码:

info.vi_binding.binding = 0;
info.vi_binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
info.vi_binding.stride = sizeof(g_vb_solid_face_colors_Data[0]);

info.vi_attribs[0].binding = 0;
info.vi_attribs[0].location = 0;
info.vi_attribs[0].format = VK_FORMAT_R32G32B32A32_SFLOAT;
info.vi_attribs[0].offset = 0;
info.vi_attribs[1].binding = 0;
info.vi_attribs[1].location = 1;
info.vi_attribs[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
info.vi_attribs[1].offset = 16;

stride是一个顶点的大小,或者是添加到一个指向下一个顶点的指针所需要的数量。

bindingbinding成员在GLSL着色器源代码中引用它们各自的值。您可以在着色器样本中查看着色器源代码以查看对应关系。

第一个属性是位置数据,一个4字节的浮点格式被用来表述 attribute 0的format成员。 attribute 1的format很显然是颜色格式,因此attribute 1指代颜色。

offset成员是在顶点数据中找到每个属性的指示器。

将顶点缓冲绑定到渲染过程

您可以跳过设置渲染过程的大部分代码,因为您将在后面的示例中看到它。但是现在,找到将顶点缓冲绑定到渲染过程的代码:

vkCmdBeginRenderPass(info.cmd, &rp_begin, VK_SUBPASS_CONTENTS_INLINE);

vkCmdBindVertexBuffers(info.cmd, 0,             /* Start Binding */
                       1,                       /* Binding Count */
                       &info.vertex_buffer.buf, /* pBuffers */
                       offsets);                /* pOffsets */

vkCmdEndRenderPass(info.cmd);

请注意,您只能在一个渲染过程内部将顶点缓冲与渲染过程连接在一起;换句话说,在vkCmdBeginRenderPassvkCmdEndRenderPass之间。这实际上告诉GPU在绘制时要使用什么顶点缓冲。

© Copyright 2016 LunarG, Inc

<think>好的,我现在需要回答用户关于Vulkan顶点着色器创建的问题,特别是关于`#version 450`的含义。首先,用户之前已经询问过Vulkan着色器的使用步骤,现在他们希望更深入地了解顶点着色器的创建过程和版本号的作用。 首先,我需要确认用户的问题。他们问到了`#version 450`是什么,以及如何详细创建Vulkan顶点着色器。之前的回答中已经提到了GLSL代码的编写和编译步骤,但现在需要更详细地解释版本号的意义,以及整个顶点着色器创建流程的各个步骤。 接下来,我要分解问题。关于`#version 450`,需要解释GLSL的版本声明,特别是与Vulkan的关系。用户可能想知道为什么这个版本号是必需的,以及它如何影响着色器的功能。同时,他们可能对Vulkan与OpenGL在GLSL版本上的差异感兴趣。 然后是顶点着色器的创建过程。用户需要详细的步骤,从编写GLSL代码到SPIR-V编译,再到Vulkan中创建着色器模块和配置管线阶段。需要确保每个步骤都清晰,并解释相关概念,如SPIR-V、着色器模块、管线配置等。 我需要考虑用户可能的背景。他们可能有一定的图形编程基础,但对Vulkan的具体细节不熟悉。因此,解释时应避免过于技术化的术语,或在必要时进行解释。例如,SPIR-V是什么,为什么需要将GLSL编译为SPIR-V,以及Vulkan如何处理这些二进制格式。 另外,用户之前的问题中已经涉及了一些代码示例,但可能需要更详细的注释和说明,尤其是在创建着色器模块和配置管线阶段时。需要强调关键的数据结构和函数,如`VkShaderModuleCreateInfo`和`vkCreateShaderModule`的作用。 还要注意可能出现的常见错误,例如GLSL版本不匹配、编译错误、资源绑定问题等,并在回答中提到这些注意事项,帮助用户避免陷阱。例如,Vulkan要求使用特定版本的GLSL,并且需要正确的扩展和功能启用。 在结构上,回答可以分为几个主要部分:解释`#version 450`,顶点着色器编写的步骤,编译过程,Vulkan中的模块创建,管线配置,以及注意事项。每个部分需要详细但简洁,确保用户能逐步理解整个过程。 最后,需要验证信息的准确性。例如,确认Vulkan支持的GLSL版本,SPIR-V编译器的使用方式,以及Vulkan API调用的正确参数设置。可能需要参考官方文档或权威资料,确保回答的正确性。</think>### 一、`#version 450` 的详细解释 `#version 450` 是 **GLSL(OpenGL Shading Language)的版本声明**,具体含义如下: 1. **核心作用**: - 指定着色器使用的GLSL语言版本(此处为4.50版) - 启用对应版本的语法特性和内置函数 - 在Vulkan中**必须显式声明**(与OpenGL不同) 2. **Vulkan特殊要求**: - Vulkan仅支持 **GLSL 4.50及以上版本**(对应SPIR-V 1.0+) - 必须添加以下编译指令: ```glsl #extension GL_EXT_scalar_block_layout : enable // 显式内存布局支持 #extension GL_GOOGLE_include_directive : enable // 允许使用#include ``` 3. **与SPIR-V的关系**: - Vulkan不直接执行GLSL代码 - 需通过编译器(如`glslangValidator`或`glslc`)将GLSL转换为 **SPIR-V字节码** - SPIR-V是Vulkan、OpenCL等API通用的中间表示格式 --- ### 二、Vulkan顶点着色器完整创建流程(代码级详解) #### 步骤1:编写顶点着色器(GLSL) ```glsl #version 450 // 显式启用Vulkan所需扩展 #extension GL_EXT_scalar_block_layout : enable // 顶点输入属性(对应顶点缓冲区结构) layout(location = 0) in vec3 inPosition; // 位置属性(绑定location 0) layout(location = 1) in vec2 inTexCoord; // 纹理坐标(绑定location 1) // 输出到片段着色器的数据 layout(location = 0) out vec2 fragTexCoord; // 统一缓冲区对象(UBO)声明 layout(binding = 0) uniform UniformBuffer { mat4 projView; // 投影视图矩阵 vec3 lightDir; // 光照方向 } ubo; // 推送常量(小数据直接传递) layout(push_constant) uniform PushConsts { mat4 model; // 模型矩阵 } pc; void main() { fragTexCoord = inTexCoord; gl_Position = ubo.projView * pc.model * vec4(inPosition, 1.0); } ``` #### 步骤2:编译为SPIR-V 使用Vulkan SDK自带的编译器: ```bash glslc vert_shader.vert -o vert.spv -O # -O表示优化 ``` 或使用`glslangValidator`: ```bash glslangValidator -V vert_shader.vert -o vert.spv ``` #### 步骤3:在Vulkan中创建着色器模块(C++示例) ```cpp // 1. 读取SPIR-V字节码 std::vector<char> shaderCode; std::ifstream file("vert.spv", std::ios::ate | std::ios::binary); size_t fileSize = (size_t)file.tellg(); shaderCode.resize(fileSize); file.seekg(0); file.read(shaderCode.data(), fileSize); file.close(); // 2. 创建着色器模块 VkShaderModuleCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = shaderCode.size(); createInfo.pCode = reinterpret_cast<const uint32_t*>(shaderCode.data()); VkShaderModule vertModule; if (vkCreateShaderModule(device, &createInfo, nullptr, &vertModule) != VK_SUCCESS) { throw std::runtime_error("顶点着色器模块创建失败"); } // 注:需在程序退出时调用vkDestroyShaderModule清理资源 ``` #### 步骤4:配置管线着色器阶段 ```cpp VkPipelineShaderStageCreateInfo vertStageInfo{}; vertStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; vertStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; // 明确指定为顶点阶段 vertStageInfo.module = vertModule; // 关联已创建的模块 vertStageInfo.pName = "main"; // 入口函数名必须与GLSL中的main匹配 // 高级用法:可添加特殊化常量(优化着色器变体) VkSpecializationMapEntry specEntries[2] = {...}; VkSpecializationInfo specInfo{...}; vertStageInfo.pSpecializationInfo = &specInfo; ``` --- ### 三、关键关联配置(与顶点着色器配合) #### 1. 顶点输入绑定描述 ```cpp VkVertexInputBindingDescription bindingDesc{}; bindingDesc.binding = 0; // 绑定索引 bindingDesc.stride = sizeof(Vertex); // 单个顶点数据总大小 bindingDesc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;// 按顶点还是实例 // 属性描述(对应shader中的location) std::array<VkVertexInputAttributeDescription, 2> attribDescs; attribDescs[0].binding = 0; attribDescs[0].location = 0; // 对应layout(location=0) attribDescs[0].format = VK_FORMAT_R32G32B32_SFLOAT;// vec3对应格式 attribDescs[0].offset = offsetof(Vertex, position); attribDescs[1].binding = 0; attribDescs[1].location = 1; // 对应layout(location=1) attribDescs[1].format = VK_FORMAT_R32G32_SFLOAT; // vec2对应格式 attribDescs[1].offset = offsetof(Vertex, texCoord); ``` #### 2. 描述符集布局(绑定UBO) ```cpp VkDescriptorSetLayoutBinding uboBinding{}; uboBinding.binding = 0; // 对应shader中的binding=0 uboBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; uboBinding.descriptorCount = 1; uboBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; // 仅顶点阶段使用 VkDescriptorSetLayoutCreateInfo layoutInfo{...}; vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorLayout); ``` #### 3. 推送常量范围声明 ```cpp VkPushConstantRange pushConstantRange{}; pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; pushConstantRange.offset = 0; // 必须与shader中的定义对齐 pushConstantRange.size = sizeof(glm::mat4); // 单个mat4的大小 ``` --- ### 四、调试与验证技巧 1. **着色器编译错误排查**: ```bash glslc vert.vert -o vert.spv --target-env=vulkan1.2 -Werror ``` - 添加`-Werror`将警告视为错误 - `--target-env`指定目标Vulkan版本 2. **SPIR-V反汇编验证**: ```bash spirv-dis vert.spv -o vert.asm # 生成可读的汇编格式 ``` 3. **验证层错误检测**: 启用以下层可捕获常见错误: - `VK_LAYER_KHRONOS_validation` - 典型错误:描述符绑定不匹配、location冲突、内存对齐错误 --- ### 五、高级优化策略 1. **着色器模块复用**: ```cpp // 多个管线共用同一着色器模块 std::unordered_map<std::string, VkShaderModule> shaderCache; ``` 2. **使用`VK_KHR_shader_non_semantic_info`扩展**: ```glsl #extension GL_EXT_nonuniform_qualifier : enable // 支持动态资源索引等高级特性 ``` 3. **离线编译管道**: - 在构建阶段预编译所有着色器变体 - 使用`VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT`创建基础管线 --- **最终管线整合示例**: ```cpp VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.stageCount = 1; // 当前仅配置顶点阶段 pipelineInfo.pStages = &vertStageInfo; // 指向顶点着色器配置 pipelineInfo.pVertexInputState = &vertexInputInfo; // 关联顶点绑定描述 pipelineInfo.layout = pipelineLayout; // 包含描述符和推送常量布局 // ...其他配置如光栅化、混合等 vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline); ``` 通过以上步骤,即可在Vulkan中完整实现顶点着色器的创建与集成。实际开发中建议使用**着色器反射库**(如SPIRV-Cross)自动生成绑定布局,避免手动配置错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值