Renderer Flow and Submission

引言

我们的应用程序几乎达到了实际上不需要调用任何类型的原始 OpenGL 来渲染我们的矩形和三角形。今天我们的主题是 Draw Call。

什么是 Draw Call?简单来说,它是在屏幕上实际绘制东西的调用,这是我们告诉显卡的指令:我已经给了你一堆信息,你需要继续运行这种具有几何约束的顶点着色器、像素着色器和片段着色器来计算屏幕上的像素。

有很多不同的方法来实际调用绘图指令,但目前这种情况下我们的 Draw Call 是 OpenGL 的 glDrawElements(),它仍然内嵌在我们的应用程序代码中没有解耦。

再谈渲染架构与流程

现在我们需要一个渲染器,所以渲染器做些什么呢?来放下我们的三角形,考虑如何正确渲染一个 3D 世界的几何图形。我们可能需要这些组件:

  • 顶点数组 VertexArray

    包含了顶点缓冲区 VertexBuffer 和索引缓冲区 IndexBuffer

  • 着色器 Shader

这是我能想到的最低限度的组件。除此之外,我们还需要:

  • 相机系统 Camera System

    Move Mat、View Mat 和 Projection Mat

  • 材质系统 Material System

  • 光照系统 Light System

我们需要上面所述的一切。可以看到这些东西开始分为两个不同的方向:与环境有关的东西和与我们正在渲染的实际对象有关的东西。

总的渲染流程将会是:“嘿,渲染器我要从这个场景开始”并提供相机环境;然后渲染所有的网格,每个网格通常会有不同的变换,所以我们会使用一些变换矩阵;最终它会执行 Shader 并结束场景。

它们是一种相同的信号,所有的东西都没有立即渲染,它只是被提交到类似命令队列的渲染中。我们花所有事件提交所有内容(一种渲染类型的命令队列),稍后便可以在单独的渲染管线上执行,这就是多线程游戏引擎实际所做的事情,也是我们将来要做的事情。

渲染API重构

Renderer::BeginScene();
Renderer::Submit(xxx);
Renderer::EndScene();

渲染器作为高度抽象类不会处理诸如清理屏幕这样的事情,它需要更加关注场景、网格以及之前所说的一切。

Renderer.h 定义虚函数方法:

@@ -1,20 +1,19 @@
#pragma once

-namespace Infinite {
+#include "RenderCommand.h"

- enum class RendererAPI
- {
- None = 0,
- OpenGL = 1
- };
+namespace Infinite {

class Renderer
{
public:
- inline static RendererAPI GetAPI() { return s_RendererAPI; }
+ static void BeginScene();
+ static void EndScene();
+
+ static void Submit(const std::shared_ptr<VertexArray>& vertexArray);


- private:
- static RendererAPI s_RendererAPI;
+ inline static RendererAPI::API GetAPI() { return RendererAPI::GetAPI(); }
};

Renderer.cpp 实现方法:

@@ -3,5 +3,17 @@

namespace Infinite {

- RendererAPI Renderer::s_RendererAPI = RendererAPI::OpenGL;
+ void Renderer::BeginScene()
+ {
+ }
+
+ void Renderer::EndScene()
+ {
+ }
+
+ void Renderer::Submit(const std::shared_ptr<VertexArray>& vertexArray)
+ {
+ vertexArray->Bind();
+ RenderCommand::DrawIndexed(vertexArray);
+ }
}

与此相对的,我们可以使用渲染命令来处理清理屏幕:

RendererCommand::SetColor();
RendererCommand::Clear();

Infinite/Renderer/ 目录下新建 RendererCommand.hRendererCommand.cpp

#include "ifnpch.h"
#include "RenderCommand.h"

#include "Platform/OpenGL/OpenGLRendererAPI.h"

namespace Infinite {

RendererAPI* RenderCommand::s_RendererAPI = new OpenGLRendererAPI;

}

RendererCommand.cpp 实现屏幕清理和绘制调用:

#pragma once

#include "RendererAPI.h"

namespace Infinite {

class RenderCommand
{
public:
inline static void SetClearColor(const glm::vec4& color)
{
s_RendererAPI->SetClearColor(color);
}

inline static void Clear()
{
s_RendererAPI->Clear();
}

inline static void DrawIndexed(const std::shared_ptr<VertexArray>& vertexArray)
{
s_RendererAPI->DrawIndexed(vertexArray);
}
private:
static RendererAPI* s_RendererAPI;
};

}

而 API 的选择则是在 Infinite/Renderer/ 目录下新建 RendererAPI.hRendererAPI.cpp

@@ -0,0 +1,29 @@
#pragma once

#include <glm/glm.hpp>
#include "VertexArray.h"

namespace Infinite {

class RendererAPI
{
public:
enum class API
{
None = 0,
OpenGL = 1
};


public:
virtual void SetClearColor(const glm::vec4& color) = 0;
virtual void Clear() = 0;

virtual void DrawIndexed(const std::shared_ptr<VertexArray>& vertexArray) = 0;

inline static API GetAPI() { return s_API; }
private:
static API s_API;

};
}

RendererAPI.cpp 选择 OpenGL 作为引擎的图形 API:

#include "ifnpch.h"
#include "RendererAPI.h"

namespace Infinite {

RendererAPI::API RendererAPI::s_API = RendererAPI::API::OpenGL;

}

调整应用类

我们终于可以解耦所有有关 OpenGL 的代码,替换为封装好的抽象类:

#include "Events/ApplicationEvent.h"
#include "Log.h"

-#include <glad/glad.h>
+#include "./Renderer/Renderer.h"

#include "Input.h"

@@ -164,16 +164,19 @@ namespace Infinite {
{
while (m_Running)
{
- glClearColor(0.1f, 0.1f, 0.1f, 1);
- glClear(GL_COLOR_BUFFER_BIT);
+ RenderCommand::SetClearColor({ 0.1f, 0.1f, 0.1f, 1 });
+ RenderCommand::Clear();
+
+ Renderer::BeginScene();

m_BlueShader->Bind();
- m_SquareVertexArray->Bind();
- glDrawElements(GL_TRIANGLES, m_SquareVertexArray->GetIndexBuffer()->GetCount(), GL_UNSIGNED_INT, nullptr);
+ Renderer::Submit(m_SquareVertexArray);


m_Shader->Bind();
- m_VertexArray->Bind();
- glDrawElements(GL_TRIANGLES, m_VertexArray->GetIndexBuffer()->GetCount(), GL_UNSIGNED_INT, nullptr);
+ Renderer::Submit(m_VertexArray);
+
+ Renderer::EndScene();

for (Layer* layer : m_LayerStack)
layer->OnUpdate();

调试