游戏引擎从零开始(38)-Batch Rendering(4)

分批处理的应用

批处理是不是真的有性能优化呢?

这章我们绘制更多的图元,对比批渲染和多次渲染的效果。

最后实现如下图所示的效果,在上一章的基础上更加了很多小矩形,并增加了统计数据的显示。

增加分批 & 统计

代码修改不多,最核心的修改是,根据MaxQuads自动刷新,超过上限就触发一次绘制。

Hazel/src/Hazel/Renderer/Renderer2D.h

c++ 复制代码
// Stats
struct Statistics
{
  uint32_t DrawCalls = 0;
  uint32_t QuadCount = 0;

  uint32_t GetTotalVertexCount() { return QuadCount * 4; }
  uint32_t GetTotalIndexCount() { return QuadCount * 6; }
};

// 每帧绘制结束重置统计数据
static void ResetStats();
static Statistics GetStats();
private:
static void FlushAndReset();

Sandbox/src/Sandbox2D.cpp

c++ 复制代码
struct Renderer2DData
{
  static const uint32_t MaxQuads = 10000;
  static const uint32_t MaxVertices = MaxQuads * 4;
  static const uint32_t MaxIndices = MaxQuads * 6;
  
  ...
  // 增加统计数据
  Renderer2D::Statistics Stats;
}

void Renderer2D::Flush()
{
  ...
  // 刷新结束增加DrawCall统计
  s_Data->Stats.DrawCalls++;
}

// FlushAndReset实现,刷新后重置s_Data,新启一个批次
void Renderer2D::FlushAndReset() {
    EndScene();

    s_Data->QuadIndexCount = 0;
    s_Data->QuadVertexBufferPtr = s_Data->QuadVertexBufferBase;

    s_Data->TextureSlotIndex = 1;
}

// DrawQuad中累计数据超过上限则触发刷新和重置
// 其他DrawQuad均有同样的代码逻辑,此处仅展示一个DrawQuad的代码变化,完整代码参考文末github链接
void Renderer2D::DrawQuad(const glm::vec3 &position, const glm::vec2 &size, const glm::vec4 &color) {
    HZ_PROFILE_FUNCTION();

    // s_Data中累计的数据超过单批绘制的上限,则触发刷新和重置
    if (s_Data->QuadIndexCount >= Renderer2DData::MaxIndices) {
        FlushAndReset();
    }
    
    ...
    // DrawQuad结束,累计QuadCount值
    s_Data->Stats.QuadCount++;
}


// 重置统计数据
void Renderer2D::ResetStats() {
    // 注意这里哟花姑娘memset的方式重置数据为0
    memset(&s_Data->Stats, 0, sizeof(Statistics));
}

// 获取统计数据
Renderer2D::Statistics Renderer2D::GetStats() {
    return s_Data->Stats;
}
    

Demo工程中的修改

Sandbox/src/Sandbox2D.cpp

c++ 复制代码
void Sandbox2D::OnUpdate(Hazel::Timestep ts) {
    // Update
    m_CameraController.OnUpdate(ts);
    // 每帧重置统计数据
    Hazel::Renderer2D::ResetStats();
    
    
    ...
    // 增加测试数据
    // 绘制10 * 10的矩形彩色棋盘
    Hazel::Renderer2D::BeginScene(m_CameraController.GetCamera());
for(float y = -5.0f; y < 5.0f; y += 0.5f) {
    for (float x = -5.0f; x < 5.0f; x += 0.5f) {
        glm::vec4 color = {(x+5.0f)/10.0f, 0.4f, (y+5.0f)/10.0f, 0.7f};
        Hazel::Renderer2D::DrawQuad({x, y}, {0.45f, 0.45f}, color);
    }
}
Hazel::Renderer2D::EndScene();
}

// ImGui实现统计面板
void Sandbox2D::OnImGuiRender() {
    HZ_PROFILE_FUNCTION();

    ImGui::Begin("Settings");

    auto stats = Hazel::Renderer2D::GetStats();
    ImGui::Text("Renderer2D Stats:");
    ImGui::Text("Draw Calls : %d", stats.DrawCalls);
    ImGui::Text("Quads : %d", stats.QuadCount);
    ImGui::Text("Vertices : %d", stats.GetTotalVertexCount());
    ImGui::Text("Indices : %d", stats.GetTotalIndexCount());

    ImGui::ColorEdit4("Square Color", glm::value_ptr(m_SquareColor));
    ImGui::End();
}

代码运行正常的话,会看到文章开头的画面。接下来我们简单测试下性能

性能测试

修改MaxQuads值观察是否卡顿

  1. MaxQuads默认是10000,很流畅
c++ 复制代码
static const uint32_t MaxQuads = 10000;
  1. MaxQuads改成1,有明显的卡顿
c++ 复制代码
static const uint32_t MaxQuads = 1;

可见批渲染确实能优化性能。这里我们只是粗略的测试和统计。有兴趣的读者可以实现帧率的实时显示,增加滑动条动态调整网格密度以观察帧率的变化。

完整代码&总结

完整代码

github.com/summer-go/H...

总结

批处理在实际应用中是很常见的优化,作为基础属性,在常见的游戏引擎中都有很好的支持。

相关推荐
Thomas游戏开发2 天前
Unity3D 图形渲染(Graphics & Rendering)详解
前端·unity3d·游戏开发
Thomas游戏开发2 天前
Unity3D 光栅化 vs 光线追踪:技术详解
前端框架·unity3d·游戏开发
Thomas游戏开发3 天前
Unity3D 多线程与协程优化详解
前端框架·unity3d·游戏开发
蓝裕安3 天前
用CMake编译glfw进行OpenGL配置,在Visual Studio上运行
c++·ide·visual studio·opengl
敢嗣先锋3 天前
鸿蒙5.0实战案例:基于OpenGL渲染视频画面帧
移动开发·音视频·harmonyos·arkts·opengl·arkui·鸿蒙开发
TechNomad4 天前
二、Visual Studio2022配置OpenGL环境
c++·opengl
Thomas_YXQ7 天前
Unity3D Cinemachine 高级应用详解
数码相机·unity·面试·职场和发展·unity3d·游戏开发
kikikidult9 天前
OpenGL 04--GLSL、数据类型、Uniform、着色器类
c++·笔记·学习·opengl
Uzuki9 天前
Vulkan环境配置 | vscode+msvc 解决方案
vscode·游戏开发·图形学·c/c++
非衣居士9 天前
游戏编程模式(28种编程模式)
数据结构·游戏开发