感兴趣可以看看狗蛋的文章https://zhuanlan.zhihu.com/p/450157594
几乎所有的着色器语言,从CPU向GPU传递数据,大致有两种方式,一种是通过Uniform Buffer,可以看看这篇文章,而另一种传递方式是通过 顶点输入(Vertex Input) 来传递数据
OpenGL和Vulkan中,您可以使用顶点缓冲区来传递数据给顶点着色器。这种方法通常用于传递顶点的位置,法线,纹理坐标,
颜色,顶点的骨骼或关节信息,动画相关的权重,切线向量等
首先,一个最简单的顶点的结构体假设为
struct Vertex
{
glm::vec3 pos;
glm::vec3 color;
}
CPU端给出顶点数据
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}}
};
在Vulkan,将其传递给顶点着色器,需要两个步骤(两种结构):
1. VertexInput BindingDescription
在 Vulkan 中,图形渲染管线的输入阶段需要知道如何解释顶点数据。VkVertexInputBindingDescription
结构体用于描述顶点数据在缓冲中的排列方式。
binding
字段表示缓冲区的索引。pipeline
可以绑定多个顶点缓冲,可以创建多个VkVertexInputBindingDescription
来描述这些顶点缓冲,binding
字段是顶点缓冲区的索引,表示这个description是针对哪个缓冲
VkVertexInputBindingDescription bindingDescription{};
bindingDescription.binding = 0; // 顶点缓冲区的索引
bindingDescription.stride = sizeof(Vertex); // 缓冲区单个顶点结构大小
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; // 输入率,可以是每顶点或每实例(_INSTANCE)
在记录渲染指令到指令缓冲的时候,通过 vkCmdBindVertexBuffers
来绑定顶点缓冲。
offset
参数指定了顶点属性数据在缓冲中的起始位置的偏移量一般为0
VkDeviceSize offset = 0;
vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer, &offset);
可以在一个顶点缓冲中存储多个模型的顶点数据。每次绘制不同的模型时,通过适当的偏移量来指定顶点数据的起始位置。这样可以用一个大的顶点缓冲区代替多个小的顶点缓冲区,虽然占用的空间不变,但是这样可以减少顶点缓冲的绑定切换,提高渲染效率。
- 问题1: 既然一个顶点缓冲存放多个模型的顶点数据,这里
vkCmdBindVertexBuffers(.... , offset)
指定了一个偏移量,但是vulkan如何知道应该读取多少个顶点呢?- 实际上,绘制时的顶点数量是通过
vkCmdDraw(commandBuffer, vertexCount, instanceCount, firstVertex, firstInstance)
或vkCmdDrawIndexed( commandBuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstance)
命令来指定的。 vkCmdDraw()
中的firstVertex
参数表示从顶点缓冲的哪个索引开始读取顶点数据。如果在绑定顶点缓冲时指定了偏移量(offset),那么读取的实际位置会是firstVertex + offset
。例如,如果你在绑定顶点缓冲时设置了一个偏移量为 10,然后调用 vkCmdDraw 时 firstVertex 设置为 5,那么实际上 Vulkan 会从顶点缓冲的索引 10 + 5 = 15 的位置开始读取顶点数据。
- 实际上,绘制时的顶点数量是通过
2. VertexInput AttributeDescription
顶点的属性有多少个,分别是什么格式、偏移量,就需要 VkVertexInputAttributeDescription
。
std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};
// 属性1
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Vertex, pos); //0
// 属性2
attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, color); //12
此时,shader就知道每24个字节为一个顶点,前12个字节是属性1,后12个字节是属性2。
在shader中的使用, 这里的location
对应于attributeDescription
中的location
.
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
3. VkPipelineVertexInputStateCreateInfo
上面的两个结构体合成一个VkPipelineVertexInputStateCreateInfo
,在创建管线的时候传递给管线对象,从而让渲染管线了解顶点的状态
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
VkGraphicsPipelineCreateInfo pipelineCI;
pipelineCI.pInputAssemblyState = &inputAssemblyState;
pipelineCI.pRasterizationState = &rasterizationState;
pipelineCI.pColorBlendState = &colorBlendState;
pipelineCI.pMultisampleState = &multisampleState;
pipelineCI.pViewportState = &viewportState;
pipelineCI.pDepthStencilState = &depthStencilState;
pipelineCI.pDynamicState = &dynamicState;
pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
pipelineCI.pStages = shaderStages.data();
pipelineCI.subpass = 0;
pipelineCI.pVertexInputState = &vertexInputInfo; /