Dragon Engine:OpenGL渲染架构
这一节具体介绍OpenGL的渲染架构实现:
渲染环境类:
namespace Dragon
{
class OpenGLContext : public GraphicsContext
{
public:
OpenGLContext(GLFWwindow* windowHandle);
virtual void Init() override;
virtual void SwapBuffers() override;
private:
GLFWwindow* m_WindowHandle;
};
}
实现:
namespace Dragon
{
OpenGLContext::OpenGLContext(GLFWwindow* windowHandle)
:m_WindowHandle(windowHandle)
{
DG_CORE_ASSERT(windowHandle, "Window handle is null");
}
void OpenGLContext::Init()
{
glfwMakeContextCurrent(m_WindowHandle);
int status = gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
DG_CORE_ASSERT(status, "Failed to initailize GLAD!");
DG_CORE_INFO("OpenGL info:");
DG_CORE_INFO(" Vendor: {0}", glGetString(GL_VENDOR));
DG_CORE_INFO(" Renderer: {0}", glGetString(GL_RENDERER));
DG_CORE_INFO(" Version: {0}", glGetString(GL_VERSION));
}
void OpenGLContext::SwapBuffers()
{
glfwSwapBuffers(m_WindowHandle);
}
}
构造方法会检测窗口是否为空。Init
函数调用GLFW的方法创建当前窗口的环境,接着加载glad,并打印OpenGL的版本相关信息。SwapBuffers
是一个交换缓冲的方法。
OpenGL的RendererAPI
类:
namespace Dragon
{
class OpenGLRendererAPI : public RendererAPI
{
public:
virtual void Init() override;
virtual void SetClearColor(const glm::vec4& color) override;
virtual void Clear() override;
virtual void DrawIndexed(const std::shared_ptr<VertexArray>& vertexArray) override;
};
}
方法的实现:
namespace Dragon
{
void OpenGLRendererAPI::Init()
{
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void OpenGLRendererAPI::SetClearColor(const glm::vec4& color)
{
glClearColor(color.r, color.g, color.b, color.a);
}
void OpenGLRendererAPI::Clear()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void OpenGLRendererAPI::DrawIndexed(const std::shared_ptr<VertexArray>& vertexArray)
{
glDrawElements(GL_TRIANGLES, vertexArray->GetIndexBuffer()->GetCount(), GL_UNSIGNED_INT, nullptr);
}
}
各种方法是OpenGL的一些方法的封装。
OpenGL的缓冲类:
namespace Dragon
{
class OpenGLVertexBuffer : public VertexBuffer
{
public:
OpenGLVertexBuffer(const void* vertices, uint32_t size);
virtual ~OpenGLVertexBuffer();
virtual void Bind() const override;
virtual void Unbind() const override;
virtual const BufferLayout& GetLayout() const override { return m_Layout; }
virtual void SetLayout(const BufferLayout& layout) override { m_Layout = layout; }
private:
uint32_t m_RendererID;
BufferLayout m_Layout;
};
class OpenGLIndexBuffer : public IndexBuffer
{
public:
OpenGLIndexBuffer(const void* indices, uint32_t count);
virtual ~OpenGLIndexBuffer();
virtual void Bind() const;
virtual void Unbind() const;
virtual uint32_t GetCount() const;
private:
uint32_t m_RendererID;
uint32_t m_Count;
};
}
这里定义了顶点缓冲和索引缓冲。
相关方法实现:
namespace Dragon
{
////////////////////////////////////////////////////////////////////////
//VertexBuffer///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
OpenGLVertexBuffer::OpenGLVertexBuffer(const void* vertices, uint32_t size)
{
glCreateBuffers(1, &m_RendererID);
glBindBuffer(GL_ARRAY_BUFFER, m_RendererID);
glBufferData(GL_ARRAY_BUFFER, size, vertices, GL_STATIC_DRAW);
}
OpenGLVertexBuffer::~OpenGLVertexBuffer()
{
glDeleteBuffers(1, &m_RendererID);
}
void OpenGLVertexBuffer::Bind() const
{
glBindBuffer(GL_ARRAY_BUFFER, m_RendererID);
}
void OpenGLVertexBuffer::Unbind() const
{
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
////////////////////////////////////////////////////////////////////////
//IndexBuffer///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
OpenGLIndexBuffer::OpenGLIndexBuffer(const void* indices, uint32_t count)
:m_Count(count)
{
glCreateBuffers(1, &m_RendererID);
glBindBuffer(GL_ARRAY_BUFFER, m_RendererID);
glBufferData(GL_ARRAY_BUFFER, count * sizeof(uint32_t), indices, GL_STATIC_DRAW);
}
OpenGLIndexBuffer::~OpenGLIndexBuffer()
{
glDeleteBuffers(1, &m_RendererID);
}
void OpenGLIndexBuffer::Bind() const
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID);
}
void OpenGLIndexBuffer::Unbind() const
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
uint32_t OpenGLIndexBuffer::GetCount() const
{
return m_Count;
}
}
两个重点都在于构造方法,这里使用较新OpenGL API的的glCreateBuffers
函数创建缓冲对象,接着绑定缓冲对象到特定目标,接着传递缓冲数据(这里可以使用更新的glNamedBufferStorage
方法)。析构函数删除缓冲对象,Bind和Unbind绑定和解绑。
接着是顶点数组对象类:
namespace Dragon
{
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(const 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;
//uint32_t m_VertexBufferIndex = 0;
std::vector<std::shared_ptr<VertexBuffer>> m_VertexBuffers;
std::shared_ptr<IndexBuffer> m_IndexBuffer;
};
}
成员函数基本继承自顶点数组类。
相关方法实现:
namespace Dragon
{
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;
}
DG_CORE_ASSERT(false, "Unknown Type");
return 0;
}
OpenGLVertexArray::OpenGLVertexArray()
{
glCreateVertexArrays(1, &m_RendererID);
}
OpenGLVertexArray::~OpenGLVertexArray()
{
glDeleteVertexArrays(1, &m_RendererID);
}
void OpenGLVertexArray::Bind() const
{
glBindVertexArray(m_RendererID);
}
void OpenGLVertexArray::Unbind() const
{
glBindVertexArray(0);
}
这里定义了一个静态方法,它的功能是将我们之前定义的ShaderDataType
转换为OpenGL的数据类型。
void OpenGLVertexArray::AddVertexBuffer(const std::shared_ptr<VertexBuffer>& vertexBuffer)
{
DG_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.Normalized ? GL_TRUE : GL_FALSE,
layout.GetStride(),
(const void*)(intptr_t)element.Offset);
index++;
}
m_VertexBuffers.push_back(vertexBuffer);
}
void OpenGLVertexArray::SetIndexBuffer(const std::shared_ptr<IndexBuffer>& indexBuffer)
{
glBindVertexArray(m_RendererID);
indexBuffer->Bind();
m_IndexBuffer = indexBuffer;
}
添加顶点缓冲的方法,逻辑是:先判断要添加的顶点缓冲对象是否有布局,接着绑定顶点数组对象,绑定顶点缓冲,接着遍历布局中的元素,设置所有的顶点属性,所有属性方法之前都介绍过,应该不用完整介绍相关逻辑。
设置索引缓冲的方法很简单,绑定顶点数组对象后,绑定索引缓冲后设置成员属性即可。
到此为止,一个基本的渲染架构就以完成,大家可以绘制一个简单的三角形来实验一下(不用考虑着色器,大部分GPU会设置一个默认的顶点着色器和片元着色器(白色))
在客户端,我们就可以这么使用。
比如,创建一个层对象,然后,在其构造函数中设置顶点的坐标,然后设置顶点缓冲和顶点数组:
float vertices = {...}; //三角形坐标
// 已在层类的中定义了顶点数组对象
m_VertexArray.reset(VertexArray::Create());
// 创建一个顶点缓冲对象
std::shared_ptr<VertexBuffer> vertexBuffer;
// 根据顶点数据创建布局
BufferLayout layout = {
{ShaderDataType::Float3, "a_Position"}
};
// 使用相应数据创建OpenGL顶点缓冲对象
vertexBuffer.reset(VertexBuffer::Create(vertices, sizeof(vertices)));
//设置布局
vertexBuffer->SetLayout(layout);
//添加顶点缓冲
m_VertexArray->AddVertexBuffer(vertexBuffer);
然后在层类的update()
成员函数中:
//设置背景颜色
Dragon::RenderCommand::SetColor({...});
//清除缓冲
Dragon::RenderCommand::Clear();
//开始场景
Dragon::Renderer::BeginScene();
//提交渲染数据
Dragon::Renderer::Submit(...);
//结束场景
Dragon::Renderer::EndScene();
可以看到抽象极大的简化了命令的调用,这也是我们的目的。不出意外地话,可以看到一个白色三角形。
下一节介绍摄像机的实现。
项目github地址:https://github.com/Dragon-Baby/Dragon