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

总结

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

相关推荐
LeeAt10 小时前
《谁杀死了比尔?》:使用Trae完成的一个推理游戏项目!!
前端·游戏开发·trae
龙智DevSecOps解决方案11 小时前
游戏开发中的CI/CD优化案例:知名游戏公司Gearbox使用TeamCity简化CI/CD流程
ci/cd·游戏开发·jetbrains·teamcity
一名用户1 天前
unity实现自定义粒子系统
c#·unity3d·游戏开发
技术小甜甜3 天前
【Blender Texture】【游戏开发】高质感 Blender 4K 材质资源推荐合集 —— 提升场景真实感与美术表现力
blender·游戏开发·材质·texture
Thomas游戏开发3 天前
Unity3D TextMeshPro终极使用指南
前端·unity3d·游戏开发
Thomas游戏开发4 天前
Unity3D 逻辑代码性能优化策略
前端框架·unity3d·游戏开发
byxdaz5 天前
Qt OpenGL 3D 编程入门
qt·opengl
Thomas游戏开发5 天前
Unity3D HUD高性能优化方案
前端框架·unity3d·游戏开发
陈哥聊测试6 天前
游戏公司如何同时管好上百个游戏项目?
游戏·程序员·游戏开发
byxdaz7 天前
Qt OpenGL 相机实现
opengl