批渲染应用到粒子场景
批渲染非常适用于粒子场景,数量大且特征相同。
这章我们在之前的场景上,加上粒子效果,使用合批的方式来渲染,看看性能如何。
最终的实现效果如下,滑动鼠标时,有拖尾的粒子效果,每个粒子是一个矩形:
引入粒子
在第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,性能杠杠的,粒子也非常流畅。
完整代码 & 总结
完整代码
总结
到此,批渲染从原理到实践算是讲完了,没有复杂的原理,属于工程上的性能优化,非常实用。给读者们留个问题:什么情况下,不能使用批渲染呢?