前言
Application.cpp中,设置一组极点到OpenGL的 ArrayBuffer:
float vertices[3 * 3] = {
-0.5f, -0.5, 0.0,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
m_VertexBuffer.reset(VertexBuffer::Create(vertices, sizeof(vertices)));
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), nullptr);
现在只有一种极点特色,即由3个float组成的position(坐标),假设再加上color(色彩)、normal(法线),glEnableVertexAttribArray和glVertexAttribPointer函数就会重复调用。
软件规划中,有重复代码的当地,便是坏味道,应该条件反射的想到抽象,提取相同代码。
本章节经过BufferLayout类,减少glEnableVertexAttribArray和glVertexAttribPointer的重复调用。
从Application倒推BufferLayout规划
为了更好的了解BufferLayout的规划,我们从Application调用的当地出发,从需求倒推BufferLayout应该提供什么样的接口。
先来看看glVertexAttribPointer函数的各个参数的含义:
我们需求将这些参数封装起来,不要写死,有多个极点特色时,在for循环里设置。
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.Normallized ? GL_TRUE : GL_FALSE,
layout.GetStride(),
(const void*)element.Offset);
index++;
}
规划BufferLayout数据结构
VertexBuffer中增加BufferLayout字段,BufferLayout里边放置一个BufferElement的列表。
关键看BufferElement的完结,要针对每种类型的数据定义数据size: Sandbox/Hazel/src/Hazel/Renderer/Buffer.h
enum class ShaderDataType {
None = 0, Float, Float2, Float3, Float4, Mat3, Mat4, Int, Int2, Int3, Int4, Bool
};
/**ShaderDataTypeSize:用于核算偏移*/
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, "Unknow ShaderDataType!")
return 0;
}
BufferElement纯数据结构,用struct来定义:
struct BufferElement {
std::string Name;
ShaderDataType Type;
uint32_t Size;
uint32_t Offset;
bool Normallized;
BufferElement(){}
BufferElement( ShaderDataType type, const std::string &name,bool normallized = false)
: Name(name), Type(type), Size(ShaderDataTypeSize(type)), Offset(0), Normallized(normallized) {}
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, "Unknow ShaderDataType!");
return 0;
}
};
BufferLayout:
class BufferLayout {
public:
BufferLayout() {}
BufferLayout(const std::initializer_list<BufferElement>& elements):m_Elements(elements) {
CalculateOffsetsAndStride();
}
inline uint32_t GetStride() const {return m_Stride;}
inline const std::vector<BufferElement>& GetElements() const {return m_Elements;}
std::vector<BufferElement>::iterator begin() {return m_Elements.begin();}
std::vector<BufferElement>::iterator end() {return m_Elements.end();}
std::vector<BufferElement>::const_iterator begin() const {return m_Elements.begin();}
std::vector<BufferElement>::const_iterator end() const {return m_Elements.end();}
private:
void CalculateOffsetsAndStride() {
uint32_t offset = 0;
m_Stride = 0;
for (auto& element : m_Elements) {
element.Offset = offset;
offset += element.Size;
m_Stride += element.Size;
}
}
private:
std::vector<BufferElement> m_Elements;
uint32_t m_Stride = 0;
};
留意以下几点:
BufferLayout中定义了一组获取std::vector::iterator的方法,C++中完结begin()、end()函数的class支撑快速for循环操作。
BufferLayout中定义了支撑初始化列表参数的结构函数,以支撑{“key”, value}方式的入参,比较便当。
CalculateOffsetsAndStride函数,用于核算一组数据中,不同特色的子数据的偏移,比方有position,color的极点数据,position的偏移为0,color的偏移便是position的数据长度,此处不了解,可能到这篇代码悉数写完,联系上下文来了解。
完善VertexBuffer类:
Sandbox/Hazel/src/Hazel/Renderer/Buffer.h
声明: Sandbox/Hazel/src/Hazel/Renderer/Buffer.h
class VertexBuffer {
public:
...
virtual const BufferLayout& GetLayout() const = 0;
virtual void SetLayout(const BufferLayout& layout) = 0;
...
};
完结: Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLBuffer.h
class OpenGLVertexBuffer : public VertexBuffer {
public:
...
const BufferLayout &GetLayout() const override {
return m_Layout;
};
void SetLayout(const BufferLayout &layout) override {
m_Layout = layout;
};
private:
uint32_t m_RendererID;
BufferLayout m_Layout;
};
Application中的逻辑替换
定义数据类型转化,我们要做跨渠道的shader,上面定义的数据类型和OpenGL无关,application中我们清晰基于OpenGL来完结,需求转化成OpenGL的类别。
Sandbox/Hazel/src/Hazel/Application.cpp
static GLenum ShaderDataTypeToOpenGLBaseType(ShaderDataType type) {
switch (type) {
case ShaderDataType::Float: return GL_FLOAT;
case ShaderDataType::Float2: return GL_FLOAT;
case ShaderDataType::Float3: return GL_FLOAT;
case ShaderDataType::Float4: return GL_FLOAT;
case ShaderDataType::Mat3: return GL_FLOAT;
case ShaderDataType::Mat4: return GL_FLOAT;
case ShaderDataType::Int: return GL_INT;
case ShaderDataType::Int2: return GL_INT;
case ShaderDataType::Int3: return GL_INT;
case ShaderDataType::Int4: return GL_INT;
case ShaderDataType::Bool: return GL_BOOL;
}
HZ_CORE_ASSERT(false, "Unknow ShaderDataType!");
return 0;
vertex数据增加color,丰厚测验事例
Application::Application() {
...
float vertices[3 * 7] = {
-0.5f, -0.5, 0.0, 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.8f, 0.8f, 0.2f, 1.0f
};
m_VertexBuffer.reset(VertexBuffer::Create(vertices, sizeof(vertices)));
...
}
设置极点特色,前面做了很多封装的作业,便是为了这儿更通用一点,运用的当地更高雅一点。
...
{
BufferLayout layout = {
{ShaderDataType::Float3, "a_Position"},
{ShaderDataType::Float4, "a_Color"},
};
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.Normallized ? GL_TRUE : GL_FALSE,
layout.GetStride(),
(const void*)element.Offset);
index++;
...
}
shader glsl中增加color
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);
}
)";
std::string fragmentSrc = R"(
#version 330 core
layout(location = 0) out vec4 color;
in vec3 v_Position;
in vec4 v_Color; //新增
void main()
{
// color = vec4(v_Position * 0.5 + 0.5, 1.0);
color = v_Color;
}
)";
到此,BufferLayout的封装和接入就完结了。代码没问题的话,运转能看到一个五颜六色的三角形
完整代码 & 总结
本次代码修正参阅:Buffer API abstract-vertex
可以看到,我们写了这么多代码,大部分都和图形烘托无关。都是在工程层面做封装、解耦。
游戏引擎本质上是一个产品工程,很多的作业都是对渠道API的封装抽象、对数据的封装传递、对线程和内存的处理。更多的需求开发者有出色的c++编程才能、出色的工程规划才能。