参考资料
简述
接下来,我们将用内存中的顶点缓冲区替换顶点着色器中的硬编码顶点数据。我们将从创建CPU可见缓冲区的最简单方法开始,并使用memcpy将顶点数据直接复制到其中,然后我们将看到如何使用分段缓冲区将顶点数据复制到高性能内存。
首先修改顶点着色器不再包含顶点数据在着色器代码本身, 顶点着色器使用in关键字从顶点缓冲区获取输入。
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec3 inColor;
// 输出为fragColor
layout(location = 0) out vec3 fragColor;
void main() {
gl_Position = vec4(inPosition, 0.0, 1.0);
fragColor = inColor;
}
inPosition和inColor变量是顶点属性。 它们是在顶点缓冲区中为每个顶点指定的属性,就像我们使用两个数组为每个顶点手动指定位置和颜色一样。 更改后记得重新编译顶点着色器!
像fragColor一样,layout(location = x)批注为输入分配索引,我们之后可以使用索引来引用它们。 重要的是要知道某些类型(例如dvec3 64位向量)使用多个插槽。 这意味着之后的索引必须至少高2倍(这里没搞懂, 需要学习一下GLSL的语法:https://www.khronos.org/opengl/wiki/Layout_Qualifier_(GLSL)):
layout(location = 0) in dvec3 inPosition;
layout(location = 2) in vec3 inColor;
一. 顶点数据
将顶点数据从着色器代码移动到程序代码中的数组中。需要引入GLM库,它为我们提供了与线性代数相关的类型,如向量和矩阵, 有与着色器语言中使用的向量类型完全匹配的c++类型。我们将使用这些类型来指定位置和颜色向量。
#include <glm/glm.hpp>
struct Vertex {
glm::vec2 pos;
glm::vec3 color;
};
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的新结构,内有两个属性,我们将在其内部的顶点着色器中使用. 使用顶点结构来指定顶点数据的数组。我们使用和之前完全相同的位置和颜色值,但现在它们被组合到一个顶点数组中, 这就是所谓的交错顶点(interleaving vertex)属性。
接下来是告诉Vulkan,一旦数据格式被上传到GPU内存,如何将其传递到顶点着色器。而传达这个信息需要有两种类型的结构: VkVertexInputBindingDescription和VkVertexInputAttributeDescription.
1.1 绑定描述
第一个结构是VkVertexInputBindingDescription,我们将向顶点结构添加一个成员函数,用正确的数据填充它。
struct Vertex {
glm::vec2 pos;
glm::vec3 color;
static VkVertexInputBindingDescription getBindingDescription() {
VkVertexInputBindingDescription bindingDescription = {};
return bindingDescription;
}
};
1.1.1 VkVertexInputBindingDescription
typedef struct VkVertexInputBindingDescription {
uint32_t binding;
uint32_t stride;
VkVertexInputRate inputRate;
} VkVertexInputBindingDescription;
- binding: 该结构描述的绑定号
- stride : 是缓冲区中两个连续元素之间的距离(以字节为单位)
- inputRate: 是一个VkVertexInputRate值,指定顶点属性寻址是顶点索引还是实例索引的函数
- VK_VERTEX_INPUT_RATE_VERTEX: 指定顶点属性寻址是顶点索引的函数,即移动到每个顶点后的下一个数据项
- VK_VERTEX_INPUT_RATE_INSTANCE: 指定顶点属性寻址是实例索引的函数,即移到每个实例之后的下一个数据项
顶点绑定描述在所有顶点中从内存加载数据的速率。它指定数据条目之间的字节数,以及是在每个顶点之后还是在每个实例之后移动到下一个数据条目。
1.1.2 绑定
所有的顶点数据都打包在一个数组中,所以我们只需要一个绑定:
static VkVertexInputBindingDescription getBindingDescription() {
VkVertexInputBindingDescription bindingDescription = {};
VkVertexInputBindingDescription bindingDescription = {};
bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
return bindingDescription;
}
1.2 属性描述
第二个描述如何处理顶点输入的结构是VkVertexInputAttributeDescription。我们将添加另一个辅助函数到顶点来填充这些结构体。
#include <array>
struct Vertex {
glm::vec2 pos;
glm::vec3 color;
static VkVertexInputBindingDescription getBindingDescription() {
VkVertexInputBindingDescription bindingDescription = {};
return bindingDescription;
}
static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescriptions() {
std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions = {};
return attributeDescriptions;
}
};
正如函数原型所表明的,有两个VkVertexInputAttributeDescription,分别代表位置和颜色。
属性描述结构描述如何从源自绑定描述的顶点数据块中提取顶点属性。
1.2.1 VkVertexInputAttributeDescription
typedef struct VkVertexInputAttributeDescription {
uint32_t location;
uint32_t binding;
VkFormat format;
uint32_t offset;
} VkVertexInputAttributeDescription;
- location: 属性的着色器绑定位置号
- binding: 该属性获取其数据的绑定号
- format: 指顶点属性数据的大小和类型, 应使用颜色通道数量与