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

总结

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

相关推荐
闲暇部落7 小时前
Android OpenGL ES详解——绘制圆角矩形
opengl·圆形·矩形·圆角矩形
哈市雪花18 小时前
图像处理 之 凸包和最小外围轮廓生成
图像处理·人工智能·图形学·最小外围轮廓·最小外包
Thomas游戏开发19 小时前
Unity3D 逻辑服的Entity, ComponentData与System划分详解
前端框架·unity3d·游戏开发
凌云行者1 天前
OpenGL入门008——环境光在片段着色器中的应用
c++·cmake·opengl
闲暇部落5 天前
Android OpenGL ES详解——立方体贴图
opengl·天空盒·立方体贴图·环境映射·动态环境贴图
闲暇部落6 天前
Android OpenGL ES详解——实例化
android·opengl·实例化·实例化数组·小行星带
闲暇部落8 天前
Android OpenGL ES详解——几何着色器
opengl·法线·法向量·几何着色器
刘好念13 天前
[OpenGL]使用OpenGL实现硬阴影效果
c++·计算机图形学·opengl
闲暇部落14 天前
Android OpenGL ES详解——纹理:纹理过滤GL_NEAREST和GL_LINEAR的区别
opengl·texture·linear·纹理过滤·nearest·邻近过滤·线性过滤
凌云行者15 天前
OpenGL入门005——使用Shader类管理着色器
c++·cmake·opengl