游戏引擎从零开始(39)-批渲染应用于粒子场景

608 阅读3分钟

批渲染应用到粒子场景

批渲染非常适用于粒子场景,数量大且特征相同。

这章我们在之前的场景上,加上粒子效果,使用合批的方式来渲染,看看性能如何。

最终的实现效果如下,滑动鼠标时,有拖尾的粒子效果,每个粒子是一个矩形:

粒子渲染合批

引入粒子

在第34篇《1小时开发2D游戏》中,我们实现了一个简单的粒子系统,稍微修改下就可以拿来用了。对粒子不熟悉的朋友可以翻回去回顾下。

修改OrthographicCameraController

Sandbox/Hazel/src/Hazel/OrthographicCameraController.h

OrthographicCameraController类中增加Bounds结构体,后面会用于鼠标位置映射的计算

#pragma once
#include "MouseEvent.h"
#include "ApplicationEvent.h"
#include "Timestep.h"
#include "Renderer/OrthographicCamera.h"

namespace Hazel {
    // 增加CameraBounds
    struct OrthographicCameraBounds
    {
        float Left, Right;
        float Bottom, Top;

        float GetWidth() { return Right - Left; }
        float GetHeight() { return Top - Bottom; }
    };

    class OrthographicCameraController {
    public:
        OrthographicCameraController(float aspecRato, bool rotation = false);
  
        void OnUpdate(Timestep ts);
        void OnEvent(Event& e);

        OrthographicCamera& GetCamera(){return m_Camera;}
        const OrthographicCamera& GetCamera() const {return m_Camera;}
        const OrthographicCameraBounds& GetBounds() const { return m_Bounds; }

    private:
        bool OnMouseScrolled(MouseScrolledEvent& e);

  
        bool OnWindowsResized(WindowResizeEvent& e);
    private:
        float m_AspectRatio;
        float m_ZoomLevel = 1.0f;
        bool m_Rotation = false;
        glm::vec3 m_CameraPosition;
        float m_CameraRotation = 0.0f;
        float m_CameraMoveSpeed = 5.f;
        float m_CameraRotationSpeed = 180.0f;
        OrthographicCamera m_Camera;
        OrthographicCameraBounds m_Bounds;
    };

}

Sandbox/Hazel/src/Hazel/OrthographicCameraController.cpp

OrthographicCameraController构造函数增加bounds

OrthographicCameraController::OrthographicCameraController(float aspecRato, bool rotation)
    : m_AspectRatio(aspecRato),
    m_Bounds({ -m_AspectRatio * m_ZoomLevel, m_AspectRatio * m_ZoomLevel, -m_ZoomLevel, m_ZoomLevel }),
    m_Camera(-m_AspectRatio * m_ZoomLevel, m_AspectRatio * m_ZoomLevel, -m_ZoomLevel, m_ZoomLevel),
    m_Rotation(rotation)
{}

鼠标滚动时,窗口缩放,此时更新bounds

bool OrthographicCameraController::OnMouseScrolled(MouseScrolledEvent &e) {
    HZ_PROFILE_FUNCTION();

    m_ZoomLevel -= e.GetYOffset() * 0.25;
    m_ZoomLevel = std::max(m_ZoomLevel, 0.25f);
    m_Camera.SetProjection(-m_AspectRatio * m_ZoomLevel, m_AspectRatio * m_ZoomLevel, -m_ZoomLevel, m_ZoomLevel);
    m_Bounds = { -m_AspectRatio * m_ZoomLevel, m_AspectRatio * m_ZoomLevel, -m_ZoomLevel, m_ZoomLevel };
    return false;
}

改进粒子系统ParticleSystem

Sandbox/src/ParticleSystem.h

ParticleSystem构造函数增加默认参数,存量粒子上限maxParticles,默认为1000

class ParticleSystem {
public:
    ParticleSystem(uint32_t maxParticles = 1000);
...
}

Sandbox/src/ParticleSystem.cpp m_ParticlePool按照maxParticles初始化,m_PoolIndex初始时指向粒子容器的末尾(maxParticles-1)

ParticleSystem::ParticleSystem(uint32_t maxParticles) : m_PoolIndex(maxParticles-1)
{
    m_ParticlePool.resize(maxParticles);
}

修改下粒子的z坐标为0.2f,使得粒子能绘制到最上面。

void ParticleSystem::OnRender() {
    for(auto& particle : m_ParticlePool)
    {
      ...
        glm::vec3 position = {particle.Position, 0.2f};
        Hazel::Renderer2D::DrawQuad(position, {size, size}, particle.Rotation, color);
    }

demo工程中加入粒子

Sandbox/src/Sandbox2D.h

#include "ParticleSystem.h"

class Sandbox2D : public Hazel::Layer {
  ...
  
  glm::vec4 m_SquareColor = {0.2f, 0.3f, 0.8f, 1.0f};

  ParticleSystem m_ParticleSystem;
  ParticleProps m_Particle;
}

Sandbox/src/Sandbox2D.cpp 初始化粒子,配置粒子的属性

void Sandbox2D::OnAttach() {
    HZ_PROFILE_FUNCTION();

    m_CheckerboardTexture = Hazel::Texture2D::Create("../assets/textures/Checkerboard.png");
    m_CoverTexture = Hazel::Texture2D::Create("../assets/textures/Cover.jpeg");

    // Init here
    m_Particle.ColorBegin = { 254 / 255.0f, 212 / 255.0f, 123 / 255.0f, 1.0f };
    m_Particle.ColorEnd = { 254 / 255.0f, 109 / 255.0f, 41 / 255.0f, 1.0f };
    m_Particle.SizeBegin = 0.5f, m_Particle.SizeVariation = 0.3f, m_Particle.SizeEnd = 0.0f;
    m_Particle.LifeTime = 1.0f;
    m_Particle.Velocity = { 0.0f, 0.0f };
    m_Particle.VelocityVariation = { 3.0f, 1.0f };
    m_Particle.Position = { 0.0f, 0.0f };
}

Sandbox2D::OnUpdate中增加粒子的绘制。先处理输入,根据鼠标的位置生成粒子,之后绘制粒子。

void Sandbox2D::OnUpdate(Hazel::Timestep ts) {

    ...
    
    if (Hazel::Input::IsMouseButtonPressed(HZ_MOUSE_BUTTON_LEFT))
    {  
        // 获取鼠标的坐标,在当前窗口内的坐标,不是屏幕的绝对坐标
        auto [x, y] = Hazel::Input::GetMousePosition();
        // 获取窗口宽高
        auto width = Hazel::Application::Get().GetWindow().GetWidth();
        auto height = Hazel::Application::Get().GetWindow().GetHeight();

        auto bounds = m_CameraController.GetBounds();
        auto pos = m_CameraController.GetCamera().GetPosition();
        // 换算成camera视角的坐标
        x = (x / width) * bounds.GetWidth() - bounds.GetWidth() * 0.5f;
        y = bounds.GetHeight() * 0.5f - (y / height) * bounds.GetHeight();
        m_Particle.Position = { x + pos.x, y + pos.y };
        for (int i = 0; i < 5; i++)
            m_ParticleSystem.Emit(m_Particle);
    }
    
    // 注意绘制开始结束,要加上BeginScene()和EndScene()
    Hazel::Renderer2D::BeginScene(m_CameraController.GetCamera());
    m_ParticleSystem.OnUpdate(ts);
    m_ParticleSystem.OnRender();
    Hazel::Renderer2D::EndScene();
}

运行正常的话,可以看到开头的效果,可以看到几百个粒子,只调用了1次Draw Call,加上之前的矩形和背景面板,一共调用了3次Draw Call,性能杠杠的,粒子也非常流畅。

粒子合批Draw calls

完整代码 & 总结

完整代码

github.com/summer-go/H…

总结

到此,批渲染从原理到实践算是讲完了,没有复杂的原理,属于工程上的性能优化,非常实用。给读者们留个问题:什么情况下,不能使用批渲染呢?