游戏引擎从零开始(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...

总结

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

相关推荐
avi91111 天前
Lua高级语法-第二篇
lua·游戏开发·编程语言·语法糖
SmalBox1 天前
【URP】Unity[内置Shader]简单光照SimpleLit
unity3d·游戏开发·图形学
龙智DevSecOps解决方案3 天前
Perforce《2025游戏技术现状报告》Part 1:游戏引擎技术的广泛影响以及生成式AI的成熟之路
人工智能·unity·游戏引擎·游戏开发·perforce
leon_zeng05 天前
Qt Modern OpenGL 入门:从零开始绘制彩色图形
开发语言·qt·opengl
ht巷子8 天前
OpenGL:坐标系
opengl
SmalBox10 天前
【URP】Unity[RendererFeatures]渲染对象RenderObjects
unity3d·游戏开发·图形学
lvxiangyu1114 天前
wsl2 ubuntu24 opengl 无法使用nvidia显卡 解决方法记录
wsl·cuda·opengl
下位子14 天前
『OpenGL学习滤镜相机』- Day10: 相机预览与 OpenGL 结合
android·opengl
UWA15 天前
Gears 实测室:第六期・《黑色沙漠》手游如何精准决策性能优化
性能优化·游戏开发·uwa·游戏测试
用户2946555091916 天前
在unity中,如何把敌人和玩家之间的碰撞检测的性能优化一下
游戏开发