Hazel game engine (066) Hazel's 2D particle system

If there are errors in the code, terminology, etc. in the text, please correct me

foreword

  • what to do in this section

    In Section 065, Cherno saw someone else create a 2D game with Hazel

    So he also wants the ability to create something with Hazel (also just idea, not done)

    Before he used Hazel to make a flybird game, thinking of creating a 2D game, the engine needs to have the ability to draw particle effects

    So this section needs to complete Hazel to achieve the effect of drawing particles.

  • Implementing Particle System-Related Key Points

    • Particle System

      • launch point properties
      • properties of individual particles
    • Mouse click converted to world space position

      This position determines the particle's emission point

2D particle system

Text to explain the 2D particle generation process

  1. The user clicks on the screen to get the mouse position and convert it to the world coordinate position
  2. Set this world coordinate position as the particle's emission point
  3. This emission point starts to prepare to emit multiple corresponding particles according to the properties set during initialization
  4. The corresponding particles are initialized
  5. Particles update state according to attributes (position, angle, whether alive or not)
  6. Then upload the particle properties to OpenGL to draw Quad graphics (if it is not alive, just don’t draw it).

Each particle is an independent body (object of the class) and has its own attributes: whether it is alive or not, the acceleration of gravity on the xy axis, and the survival time.

The code explains the 2D particle generation process

  • Click on the screen to get the mouse position, convert it to world coordinates (1), set this world coordinate position as the emission point of the particle (2), the emission point starts to prepare to emit multiple corresponding particles according to the properties set during initialization (3)

    void SandboxLayer::OnUpdate(Timestep ts)
    {
          
          
    	m_CameraController.OnUpdate(ts);
    	// Render here
    	//glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    	glClearColor(0,0,0, 1.0f);
    	glClear(GL_COLOR_BUFFER_BIT);
    	// 鼠标位置转换成世界空间
    	if (GLCore::Input::IsMouseButtonPressed(HZ_MOUSE_BUTTON_LEFT))
    	{
          
          
            // 点击屏幕,获取鼠标位置,将其转换世界坐标(1)
    		auto [x, y] = Input::GetMousePosition();
    		auto width = GLCore::Application::Get().GetWindow().GetWidth();
    		auto height = GLCore::Application::Get().GetWindow().GetHeight();
    
    		auto bounds = m_CameraController.GetBounds();
    		auto pos = m_CameraController.GetCamera().GetPosition();
    		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++)
                // 将这个世界坐标位置设置为粒子的**发射点**(2),这发射点开始依照初始化时设置的属性,进行**准备**发射多个对应的**粒子**(3)
    			m_ParticleSystem.Emit(m_Particle);
    	}
    
    	m_ParticleSystem.OnUpdate(ts);
    	m_ParticleSystem.OnRender(m_CameraController.GetCamera());
    }
    
  • The corresponding particles are initialized (4)

    void ParticleSystem::Emit(const ParticleProps& particleProps)
    {
          
          
    	Particle& particle = m_ParticlePool[m_PoolIndex];
    	particle.Active = true;
    	particle.Position = particleProps.Position;
    	particle.Rotation = Random::Float() * 2.0f * glm::pi<float>();
    
    	// Velocity
    	particle.Velocity = particleProps.Velocity;
    	// 在x正负2个方向,和y正负2个方向上的重力加速度,
    	particle.Velocity.x += particleProps.VelocityVariation.x * (Random::Float() - 0.5f); 
    	particle.Velocity.y += particleProps.VelocityVariation.y * (Random::Float() - 0.5f);
    
    	// Color
    	particle.ColorBegin = particleProps.ColorBegin;
    	particle.ColorEnd = particleProps.ColorEnd;
    
    	particle.LifeTime = particleProps.LifeTime;
    	particle.LifeRemaining = particleProps.LifeTime;
    	particle.SizeBegin = particleProps.SizeBegin + particleProps.SizeVariation * (Random::Float() - 0.5f);
    	particle.SizeEnd = particleProps.SizeEnd;
    
    	// 这里:由于m_ParticlePool.size();返回无符号整形,所以,-1 % 无符号整形为正数,但是不会回到999下标,只回到小于999的下标
    	m_PoolIndex = --m_PoolIndex % m_ParticlePool.size();
    }
    
  • Particles update the state according to the attributes (position, angle, whether it is alive or not) (5)

    // 更新一个个粒子的信息
    void ParticleSystem::OnUpdate(GLCore::Timestep ts)
    {
          
          
    	int i = m_ParticlePool.size() - 1;
    	for (auto& particle : m_ParticlePool)
    	{
          
          
    		i--;
    		if (!particle.Active) {
          
          
    			std::cout << "当前 " << i <<" 无 激活" << std::endl;
    			continue;
    		}
    
    		std::cout << "当前 " << i << "  有 激活*" << std::endl;
    		if (particle.LifeRemaining <= 0.0f)
    		{
          
          
    			particle.Active = false;
    			continue;
    		}
    
    		particle.LifeRemaining -= ts;
    		particle.Position += particle.Velocity * (float)ts;
    		particle.Rotation += 0.01f * ts;
    	}
    }
    
  • Upload the particle properties to OpenGL to draw Quad graphics, and decide whether to draw according to whether the mark should be destroyed: if it is destroyed, it will not be drawn. (6)

    // 绘制一个个粒子
    void ParticleSystem::OnRender(GLCore::Utils::OrthographicCamera& camera)
    {
          
          
    ......
    	glUseProgram(m_ParticleShader->GetRendererID());
    	glUniformMatrix4fv(m_ParticleShaderViewProj, 1, GL_FALSE, glm::value_ptr(camera.GetViewProjectionMatrix()));
    
    	for (auto& particle : m_ParticlePool)
    	{
          
          	// 当粒子标记为不存活,则不再绘制
    		if (!particle.Active)
    			continue;
    
    		// Fade away particles
    		float life = particle.LifeRemaining / particle.LifeTime;
    		glm::vec4 color = glm::lerp(particle.ColorEnd, particle.ColorBegin, life);
    		//color.a = color.a * life;
    		// 线性插值
    		float size = glm::lerp(particle.SizeEnd, particle.SizeBegin, life);
    		
            // 上传**粒子属性**给OpenGL**绘制**Quad图形
    		// Render
    		glm::mat4 transform = glm::translate(glm::mat4(1.0f), {
          
           particle.Position.x, particle.Position.y, 0.0f })
    			* glm::rotate(glm::mat4(1.0f), particle.Rotation, {
          
           0.0f, 0.0f, 1.0f })
    			* glm::scale(glm::mat4(1.0f), {
          
           size, size, 1.0f });
    		glUniformMatrix4fv(m_ParticleShaderTransform, 1, GL_FALSE, glm::value_ptr(transform));
    		glUniform4fv(m_ParticleShaderColor, 1, glm::value_ptr(color));
    		glBindVertexArray(m_QuadVA);
    		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
    	}
    }
    

2D particle correct effect

Please add a picture description

The particle pool size is: 50000

Colored squares: 400 Quads

Others: 5 Quads

So Quads in the upper left corner of the rendering: 50405 = 50000 + 400 + 5

2D particle system bug

  • Regarding a%b modulo, the left and right operands a and b will affect the result

    Let a = -1, b = maximum number of particles ( unsigned integer)

    a % b= unsigned integer c not exceeding b , 0 < c < b

    This will make the particles in the interval (c < x < b) that are smaller than b ( c < x < b) beyond c idle

    Please add a picture description

  • Code at Bug

    void ParticleSystem::Emit(const ParticleProps& particleProps)
    {
          
          
    	Particle& particle = m_ParticlePool[m_PoolIndex];
    	particle.Active = true;
    	......
    
    	// 这里:由于m_ParticlePool.size();返回无符号整形,所以,-1 % 无符号整形为正数,但是不会回到999下标,只回到小于999的下标
    	m_PoolIndex = --m_PoolIndex % m_ParticlePool.size();
    }
    
  • Test run results
    Please add a picture description

    As shown in the figure, after taking the modulus, it only returns to 79 , and the particles larger than 79 are in an inactive state and cannot be used

Introduce particle system into Hazel project

Bug1: have particle idle

  • Set the particle pool size to 500

  • The effect of bug1

    Please add a picture description

  • Observe the Settings window

    Colored squares: 400 Quads

    Others: 5 Quads

    So the particle quads displayed on the screen are: 701 - 405 = 296 or so, and the maximum number is obviously 500 .

    Indicates that 200 particles are free

  • How to fix this bug

    m_PoolIndex = --m_PoolIndex % m_ParticlePool.size();
    换成
    m_PoolIndex = m_PoolIndex == 0 ? (m_ParticlePool.size() - 1) : --m_PoolIndex % m_ParticlePool.size();
    

    Please add a picture description

    Colored squares: 400 Quads

    Others: 5 Quads

    The particle pool size is: 500

    So the Quads displayed in the settings window of the rendering: 905= 400+ 5+ 500

    The description is not idle .

Bug2: Residual painting after solving Bug1

  • Using the above code, although there are no idle particles, there will be residual drawing graphics, as follows:

    Please add a picture description

  • Imperfect workaround:

    Change the code to

    m_PoolIndex = m_PoolIndex == 0 ? (m_ParticlePool.size() / 2) : --m_PoolIndex % m_ParticlePool.size();
    

    Although there is no residual graphics, but it is back to bug1 , there are half of the idle particles, I don't know why. ! ! !

    The later discovery is: the code problem of batch processing, there will be no Bug2 when updating the code of batch processing, and there will be no idle particles

Introducing Particle Hazel project changes

  • OrthographicCameraController

    Introduce the struct boundary, for the world coordinates of the particle emission point .

    namespace Hazel {
          
          
    	struct OrthographicCameraBounds
    	{
          
          
    		float Left, Right;
    		float Bottom, Top;
    
    		float GetWidth() {
          
           return Right - Left; }
    		float GetHeight() {
          
           return Top - Bottom; }
    	};
    
    	class OrthographicCameraController
    	{
          
          
    
    Hazel::OrthographicCameraController::OrthographicCameraController(float aspectRatio, bool rotation)
        :m_AspectRatio(aspectRatio), m_Bounds({
          
           -m_AspectRatio * m_ZoomLevel, m_AspectRatio * m_ZoomLevel, -m_ZoomLevel, m_ZoomLevel }), m_Camera(m_Bounds.Left, m_Bounds.Right, m_Bounds.Bottom, m_Bounds.Top), m_Rotation(rotation)
        {
          
          
        }
    bool Hazel::OrthographicCameraController::OnMouseScrolled(MouseScrolledEvent& e)
    {
          
          
        HZ_PROFILE_FUNCTION();
    
        m_ZoomLevel -= e.GetYOffset() * 0.25f;
        m_ZoomLevel = std::max(m_ZoomLevel, 0.25f);
        m_Bounds = {
          
           -m_AspectRatio * m_ZoomLevel, m_AspectRatio * m_ZoomLevel, -m_ZoomLevel, m_ZoomLevel };
        m_Camera.SetProjection(m_Bounds.Left, m_Bounds.Right, m_Bounds.Bottom, m_Bounds.Top);
        return false;
    }
    bool Hazel::OrthographicCameraController::OnWindowResized(WindowResizeEvent& e)
    {
          
          
        HZ_PROFILE_FUNCTION();
    
        m_AspectRatio = (float)e.GetWidth() / (float)e.GetHeight();
        m_Bounds = {
          
           -m_AspectRatio * m_ZoomLevel, m_AspectRatio * m_ZoomLevel, -m_ZoomLevel, m_ZoomLevel };
        m_Camera.SetProjection(m_Bounds.Left, m_Bounds.Right, m_Bounds.Bottom, m_Bounds.Top);
        return false;
    }
    
  • Create a new ParticleSystem class, import particle file h and cpp code

    #pragma once
    
    #include "Hazel.h"
    
    // 发射点属性
    struct ParticleProps
    {
          
          
    	glm::vec2 Position;
    	glm::vec2 Velocity, VelocityVariation;
    	glm::vec4 ColorBegin, ColorEnd;
    	float SizeBegin, SizeEnd, SizeVariation;
    	float LifeTime = 1.0f;
    };
    
    class ParticleSystem
    {
          
          
    public:
    	ParticleSystem();
    
    	void OnUpdate(Hazel::Timestep ts);
    	void OnRender(Hazel::OrthographicCamera& camera);
    
    	void Emit(const ParticleProps& particleProps);
    private:
    	// 一个个粒子属性
    	struct Particle
    	{
          
          
    		glm::vec2 Position;
    		glm::vec2 Velocity;
    		glm::vec4 ColorBegin, ColorEnd;
    		float Rotation = 0.0f;
    		float SizeBegin, SizeEnd;
    
    		float LifeTime = 1.0f;
    		float LifeRemaining = 0.0f;
    
    		bool Active = false;
    	};
    	std::vector<Particle> m_ParticlePool;// 粒子池
    	uint32_t m_PoolIndex = 999;
    };
    

    The three methods OnUpdate, OnRender, and Emit explained the 2D particle generation process before.

    Mainly modify OnRender: use the Renderer2D rendering class to open and close the scene, draw Quad particles

    // 添加random类到此文件中,避免多一个文件,random只有此文件需要用
    
    // 绘制一个个粒子
    void ParticleSystem::OnRender(Hazel::OrthographicCamera& camera)
    {
          
          
    	Hazel::Renderer2D::BeginScene(camera);
    	for (auto& particle : m_ParticlePool)
    	{
          
          // 当粒子标记为不存活,则不再绘制
    		if (!particle.Active)
    			continue;
    
    		// Fade away particles
    		float life = particle.LifeRemaining / particle.LifeTime;
    		glm::vec4 color = glm::lerp(particle.ColorEnd, particle.ColorBegin, life);
    		//color.a = color.a * life;
    		// 线性插值
    		float size = glm::lerp(particle.SizeEnd, particle.SizeBegin, life);
    
    		glm::vec3 position = {
          
           particle.Position.x, particle.Position.y, 0.2f };
    		// 渲染 Rotation is radius
    		Hazel::Renderer2D::DrawrRotatedQuad(position, {
          
           size, size }, particle.Rotation, color);
    	}
    	Hazel::Renderer2D::EndScene();
    }
    
  • Sandbox2D

    #include "Hazel.h"
    #include "ParticleSystem.h"
    
    class Sandbox2D :public Hazel::Layer
    {
          
          
    ......
    	ParticleProps m_Particle;// 发射点
    	ParticleSystem m_ParticleSystem;
    };
    
    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();
    		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);
    	}
    	m_ParticleSystem.OnUpdate(ts);
    	m_ParticleSystem.OnRender(m_CameraController.GetCamera());
    }
    

Guess you like

Origin blog.csdn.net/qq_34060370/article/details/131883031