Vertex Buffer Layouts
本节主要是完成 VertexBuffer Layouts 的抽象,
关于 Buffer Layout ,实际上就是指明了顶点缓冲区中数据的布局方式,例如我在一个顶点缓冲区中存放的数据是这样的:
对于每个顶点,前 3 个浮点数存放的是顶点的 Position,之后 4 个浮点数是该顶点的颜色,在之后 3 个浮点数是 Normal,之后两个浮点数是纹理坐标,也就是说,每个顶点我们的数据是 12 个浮点数,那么要如何让 GPU 知道我们的数据是怎么排布的呢?这就要用到 glEnableVertexAttribArray
和 glVertexAttribPointer
来对我们的数据布局进行描述
先观察这两个函数:
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (const void*)0);
- 第一个参数指定我们要配置的顶点属性在布局中处于什么位置,例如第一个部分存储的是顶点位置,那么0对应的就是顶点位置的布局。在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置
- 第二个参数指定顶点某个属性的大小。如果该属性由三个浮点数组成,那么就是3。
- 第三个参数指定数据的类型,这里是GL_FLOAT。
- 下个参数定义我们是否希望数据被归一化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
- 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。可以理解为每一个顶点的所有属性所占的长度。
- 最后一个参数的类型是void*,数据指针,代表的是这个顶点属性的偏移量,第一个定点属性的偏移量是 0 ,之后每一个属性依次向后偏移。
用一个更具体的例子来解释这两个函数:
Application::Application()
{
HZ_CORE_ASSERT(!s_Instance, "Application already exists!");
s_Instance = this;
m_Window = std::unique_ptr<Window>(Window::Create());
m_Window->SetEventCallback(BIND_EVENT_FN(OnEvent));
m_ImGuiLayer = new ImGuiLayer();
PushOverLay(m_ImGuiLayer);
float vertices[3 * 7] = { //一共有 3 个顶点,每个顶点两个属性,七个浮点值组成
-0.5f,-0.5f,0.0f, 1.0f,0.0f,0.0f,1.0f, //前三个 float 代表顶点位置,之后四个代表顶点对应颜色
0.5f,-0.5f,0.0f, 0.0f,1.0f,0.0f,1.0f,
0.0f, 0.5f,0.0f, 0.0f,0.0f,1.0f,1.0f,
};
unsigned int indices[3] = { 0,1,2 };
glGenVertexArrays(1, &m_VertexArray);
glBindVertexArray(m_VertexArray);
m_VertexBuffer.reset(VertexBuffer::Create(vertices, sizeof(vertices)));
m_IndexBuffer.reset(IndexBuffer::Create(indices, 3));
glEnableVertexAttribArray(0); // 0 代表的是顶点位置
glEnableVertexAttribArray(1); // 1 代表的是顶点颜色
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), nullptr);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void*)12);
const std::string vertexSrc = R"(
#version 330 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec4 a_Color;
out vec3 v_Position;
out vec4 v_Color;
void main()
{
v_Position = a_Position;
v_Color = a_Color;
gl_Position = vec4(a_Position,1.0);
}
)";
const std::string fragmentSrc = R"(
#version 330 core
layout(location = 0) out vec4 color;
layout(location = 1) out vec4 color1;
in vec3 v_Position;
in vec4 v_Color;
void main()
{
color = v_Color;//vec4(v_Position*0.5+0.5, 1.0);
}
)";
m_Shader.reset(new Shader(vertexSrc, fragmentSrc));
}
之后就可以进行其他部分了:
首先定义 BufferElement 也就是顶点属性:
struct BufferElement
{
std::string Name;
ShaderDataType Type;
uint32_t Size;
uint32_t Offset;
bool Normalized;
BufferElement() = default;
BufferElement(ShaderDataType type, const std::string& name, bool normalized = false)
: Type(type), Name(name), Size(ShaderDataTypeSize(type)), Offset(0), Normalized(normalized)
{
}
uint32_t GetComponentCount() const
{
switch(Type)
{
case ShaderDataType::Float: return 1;
case ShaderDataType::Float2: return 2;
case ShaderDataType::Float3: return 3;
case ShaderDataType::Float4: return 4;
case ShaderDataType::Mat3: return 3 * 3;
case ShaderDataType::Mat4: return 4 * 4;
case ShaderDataType::Int: return 1;
case ShaderDataType::Int2: return 2;
case ShaderDataType::Int3: return 3;
case ShaderDataType::Int4: return 4;
case ShaderDataType::Bool: return 1;
}
HZ_CORE_ASSERT(false, "Unknown ShaderDataType!");
return 0;
}
};
之后定义属性的数据类型以及大小:
enum class ShaderDataType
{
None = 0, Float, Float2, Float3, Float4, Mat3, Mat4, Int, Int2, Int3, Int4, Bool
};
static uint32_t ShaderDataTypeSize(ShaderDataType type)
{
switch (type)
{
case ShaderDataType::Float: return 4;
case ShaderDataType::Float2: return 4 * 2;
case ShaderDataType::Float3: return 4 * 3;
case ShaderDataType::Float4: return 4 * 4;
case ShaderDataType::Mat3: return 4 * 3 * 3;
case ShaderDataType::Mat4: return 4 * 4 * 4;
case ShaderDataType::Int: return 4;
case ShaderDataType::Int2: return 4 * 2;
case ShaderDataType::Int3: return 4 * 3;
case ShaderDataType::Int4: return 4 * 4;
case ShaderDataType::Bool: return 1;
}
HZ_CORE_ASSERT(false, "Unknown ShaderDataType!");
return 0;
}
然后就可以定义 VertexBuffer Layout 了
class BufferLayout
{
public:
BufferLayout() {}
BufferLayout(const std::initializer_list<BufferElement>& elements)
: m_Elements(elements)
{
CalculateOffsetAndStride();
}
inline uint32_t GetStride() const { return m_Stride; }
inline const std::vector<BufferElement>& GetElements() const { return m_Elements; }
std::vector<BufferElement>::const_iterator begin() const { return m_Elements.begin(); }
std::vector<BufferElement>::const_iterator end() const { return m_Elements.end(); }
private:
void CalculateOffsetAndStride()
{
uint32_t offset = 0;
m_Stride = 0;
for (auto& element : m_Elements)
{
element.Offset = offset; //初始化每个element的 offset 并计算总步长
offset += element.Size;
m_Stride += element.Size;
}
}
private:
std::vector<BufferElement> m_Elements; // 存放每一个顶点属性对应一个 Element
uint32_t m_Stride = 0; //步长,代表一个顶点的所有数据的长度
};
在 Layout 中使用的是 const_iterator,是因为 GetLayout 的返回值是 const Vector 因此迭代器也要用 const 类型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mdJwaJVy-1665542651148)(/Users/liangjie/Library/Application Support/typora-user-images/image-20221011160531916.png)]
在 Application.cpp
中,添加 layout 并使用
static GLenum ShaderDataTypeToOpenGLBaseType(ShaderDataType type)
{
switch (type)
{
case Hazel::ShaderDataType::Float: return GL_FLOAT;
case Hazel::ShaderDataType::Float2: return GL_FLOAT;
case Hazel::ShaderDataType::Float3: return GL_FLOAT;
case Hazel::ShaderDataType::Float4: return GL_FLOAT;
case Hazel::ShaderDataType::Mat3: return GL_FLOAT;
case Hazel::ShaderDataType::Mat4: return GL_FLOAT;
case Hazel::ShaderDataType::Int: return GL_INT;
case Hazel::ShaderDataType::Int2: return GL_INT;
case Hazel::ShaderDataType::Int3: return GL_INT;
case Hazel::ShaderDataType::Int4: return GL_INT;
case Hazel::ShaderDataType::Bool: return GL_BOOL;
}
HZ_CORE_ASSERT(false, "Unknown ShaderDataType!");
return 0;
}
Application(){
...
glGenVertexArrays(1, &m_VertexArray);
glBindVertexArray(m_VertexArray);
float vertices[3 * 7] = {
-0.5f, -0.5f, 0.0f, 0.8f, 0.2f, 0.8f, 1.0f,
0.5f, -0.5f, 0.0f, 0.2f, 0.3f, 0.8f, 1.0f,
0.0f, 0.5f, 0.0f, 0.3f, 0.8f, 0.0f, 1.0f
};
m_VertexBuffer.reset(VertexBuffer::Create(vertices, sizeof(vertices)));
{
BufferLayout layout = {
{ ShaderDataType::Float3, "a_Position", false},
{ ShaderDataType::Float4, "a_Color", false}
};
m_VertexBuffer->SetLayout(layout);
}
uint32_t index = 0;
const auto& layout = m_VertexBuffer->GetLayout();
for (const auto& element : layout)
{
glEnableVertexAttribArray(index);
glVertexAttribPointer(index,
element.GetComponentCount(),
ShaderDataTypeToOpenGLBaseType(element.Type),
element.Normalized ? GL_TRUE : GL_FALSE,
layout.GetStride(),
(const void*)element.Offset);
index++;
}
}
运行结果: