前言
本节我们对VertexArray进行抽象封装,目标是将Application中对OpenGL的直接调用都剥离到Renderer层。
VertexArray抽象
原本Application中分别持有VertexArray、IndexBuffer、VertexBuffer,其中VertexArray通过id间接引用,无法避免使用了OpenGL的API。
本章节将VertexArray的逻辑抽象到VertexArray类中,并将VertexBuffer、IndexBuffer挪到VertexArray类中,如此,Application中就抽象的很干净了。
重构的示意图如下:
接口类VertexArray
和VertexBuffer类似,接口在Renderer层声明,实现在Platform/OpenGL中,通过多态的方式实现不同平台的切换。
VertexArray可以绑定多个VertexBuffer(顶点、颜色、法线等),用vector<std::shared_ptr>存储,IndexBuffer一般只有一个。从命名上看也很讲究,VertexBuffer添加用AddVertexBuffer,IndexBuffer添加用SetIndexBuffer。
Sandbox/Hazel/src/Hazel/Renderer/VertexArray.h
c++
#pragma once
#include <memory>
#include "Renderer/Buffer.h"
namespace Hazel {
class VertexArray {
public:
virtual ~VertexArray(){}
virtual void Bind() const = 0;
virtual void Unbind() const = 0;
virtual void AddVertexBuffer(const std::shared_ptr<VertexBuffer>& vertexBuffer) = 0;
virtual void SetIndexBuffer(std::shared_ptr<IndexBuffer>& indexBuffer) = 0;
virtual const std::vector<std::shared_ptr<VertexBuffer>>& GetVertexBuffers() const = 0;
virtual const std::shared_ptr<IndexBuffer>& GetIndexBuffer() const = 0;
static VertexArray* Create();
};
}
Create()放到.cpp中实现: Sandbox/Hazel/src/Hazel/Renderer/VertexArray.cpp
c++
#include "VertexArray.h"
#include "Renderer.h"
#include "Platform/OpenGL/OpenGLVertexArray.h"
namespace Hazel{
VertexArray* VertexArray::Create() {
switch (Renderer::GetAPI()) {
case RendererAPI::None: HZ_CORE_ASSERT(false, "RendererAPI::None is currently not suported!"); return nullptr;
case RendererAPI::OpenGL: return new OpenGLVertexArray();
}
HZ_CORE_ASSERT(false, "Unknow RendererAPI!");
return nullptr;
}
}
实现类OpenGLVertexArray
VertexBuffer、IndexBuffer放到OpenGLVertexArray中。简单的Get方法放到头文件中,复杂的方法放到.cpp中实现
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLVertexArray.h
c++
#pragma once
#include <cstdint>
#include "Renderer/VertexArray.h"
namespace Hazel {
class OpenGLVertexArray : public VertexArray{
public:
OpenGLVertexArray();
virtual ~OpenGLVertexArray();
virtual void Bind() const override;
virtual void Unbind() const override;
virtual void AddVertexBuffer(const std::shared_ptr<VertexBuffer>& vertexBuffer) override;
virtual void SetIndexBuffer(std::shared_ptr<IndexBuffer>& indexBuffer) override;
virtual const std::vector<std::shared_ptr<VertexBuffer>>& GetVertexBuffers() const {
return m_VertexBuffers;
};
virtual const std::shared_ptr<IndexBuffer>& GetIndexBuffer() const {
return m_IndexBuffer;
};
private:
uint32_t m_RendererID;
std::vector<std::shared_ptr<VertexBuffer>> m_VertexBuffers;
std::shared_ptr<IndexBuffer> m_IndexBuffer;
};
}
将Appplication中和vertexArray相关的逻辑都下沉到OpenGLVertexArray.cpp中: Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLVertexArray.cpp
c++
#include "OpenGLVertexArray.h"
#include <glad/glad.h>
namespace Hazel{
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;
}
OpenGLVertexArray::OpenGLVertexArray() {
glGenVertexArrays(1, &m_RendererID);
}
OpenGLVertexArray::~OpenGLVertexArray() {
glDeleteVertexArrays(1, &m_RendererID);
}
void OpenGLVertexArray::Bind() const {
glBindVertexArray(m_RendererID);
}
void OpenGLVertexArray::Unbind() const {
glBindVertexArray(0);
}
void OpenGLVertexArray::AddVertexBuffer(const std::shared_ptr<VertexBuffer> &vertexBuffer) {
HZ_CORE_ASSERT(vertexBuffer->GetLayout().GetElements().size(), "Vertex Buffer has no layout");
glBindVertexArray(m_RendererID);
vertexBuffer->Bind();
uint32_t index = 0;
const auto& layout = 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++;
}
m_VertexBuffers.push_back(vertexBuffer);
}
void OpenGLVertexArray::SetIndexBuffer(std::shared_ptr<IndexBuffer> &indexBuffer) {
glBindVertexArray(m_RendererID);
indexBuffer->Bind();
m_IndexBuffer = indexBuffer;
}
}
调整Application中的逻辑
m_Shader、m_VertexArray、m_BlueShader、m_BlueShader、m_SquareVA等字段都声明成std::shared_ptr,因为程序中有多处会使用到,声明为unique_ptr不合适: Sandbox/Hazel/src/Hazel/Application.h
c++
class Application {
...
ImGuiLayer* m_ImGuiLayer;
bool m_Running = true;
LayerStack m_LayerStack;
std::shared_ptr<Shader> m_Shader;
std::shared_ptr<VertexArray> m_VertexArray;
std::shared_ptr<Shader> m_BlueShader;
std::shared_ptr<VertexArray> m_SquareVA;
private:
static Application* s_Instance;
};
Application.cpp中替换了VertexArray的逻辑,整体清爽了很多,另外增加了一个矩阵的绘制: Sandbox/Hazel/src/Hazel/Application.cpp
c++
#include "ImGui/ImGuiLayer.h"
#include <glad/glad.h>
#include <iostream>
#include <memory>
namespace Hazel{
Application::Application() {
...
PushOverlay(m_ImGuiLayer);
//-----------------三角形----------------------
m_VertexArray.reset(VertexArray::Create());
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
};
std::shared_ptr<VertexBuffer> vertexBuffer(VertexBuffer::Create(vertices, sizeof(vertices)));
BufferLayout layout = {
{ShaderDataType::Float3, "a_Position"},
{ShaderDataType::Float4, "a_Color"},
};
vertexBuffer->SetLayout(layout);
m_VertexArray->AddVertexBuffer(vertexBuffer);
uint32_t indices[3] = {0, 1, 2};
std::shared_ptr<IndexBuffer> indexBuffer(IndexBuffer::Create(indices, sizeof(indices)/sizeof(uint32_t)));
m_VertexArray->SetIndexBuffer(indexBuffer);
// -----------------矩形--------------------
m_SquareVA.reset(VertexArray::Create());
// 注意矩形,顶点按照逆时针排列
float squareVertices[3*4] = {
-0.75f, -0.75f, 0.0f,
0.75f, -0.75f, 0.0f,
0.75f, 0.75f, 0.0f,
-0.75f, 0.75f, 0.0f
};
std::shared_ptr<VertexBuffer> squareVB(VertexBuffer::Create(squareVertices, sizeof(squareVertices)));
squareVB->SetLayout({
{ShaderDataType::Float3, "a_Position"}
});
m_SquareVA->AddVertexBuffer(squareVB);
uint32_t squareIndices[6] = {0,1,2,2,3,0};
std::shared_ptr<IndexBuffer> squareIB;
squareIB.reset(IndexBuffer::Create(squareIndices, sizeof(squareIndices)/sizeof(uint32_t)));
m_SquareVA->SetIndexBuffer(squareIB);
...
m_Shader = std::make_shared<Shader>(vertexSrc, fragmentSrc);
std::string blueShaderVertexSrc = R"(
#version 330 core
layout(location = 0) in vec3 a_Position;
out vec3 v_Position;
void main() {
v_Position = a_Position;
gl_Position = vec4(a_Position, 1.0);
}
)";
std::string blueShaderFragmentSrc = R"(
#version 330 core
layout(location=0) out vec4 color;
in vec3 v_Position;
void main() {
color = vec4(0.2, 0.3, 0.8, 1.0);
}
)";
m_BlueShader = std::make_shared<Shader>(blueShaderVertexSrc, blueShaderFragmentSrc);
}
void Application::PushLayer(Layer *layer) {
...
m_BlueShader->Bind();
m_SquareVA->Bind();
glDrawElements(GL_TRIANGLES, m_SquareVA->GetIndexBuffer()->GetCount(), GL_UNSIGNED_INT, nullptr);
m_Shader->Bind();
m_VertexArray->Bind();
glDrawElements(GL_TRIANGLES, m_VertexArray->GetIndexBuffer()->GetCount(), GL_UNSIGNED_INT, nullptr);
for (Layer* layer : m_LayerStack) {
layer->OnUpdate();
}
运行结果
如果你的代码运行没有问题,能看到如下效果,在原来的三角形背后,多了一个蓝色的矩形:
完整代码 & 总结
到此,Renderer层渲染的API基本封装完了,整体流程逐渐靠近商业引擎的使用习惯。
代码中除了上面讲的修改,还增加了一些调试日志及代码优化。
本次代码修改参考:vertex abstract