[C++] 3.4 파티클 시스템

Date:     Updated:

카테고리:

태그:

인프런에 있는 홍정모 교수님의 홍정모의 게임 만들기 연습 문제 패키지 강의를 듣고 정리한 필기입니다.😀
🌜 공부에 사용된 홍정모 교수님의 코드들 보러가기
🌜 [홍정모의 게임 만들기 연습 문제 패키지] 강의 들으러 가기!


Chapter 3. 게임 물리 맛보기 : 파티클 시스템

image

📜 Particle.h

#include "Game2D.h"
#include "Examples/PrimitivesGallery.h"
#include "RandomNumberGenerator.h"
#include "RigidCircle.h"
#include <vector>
#include <memory>

namespace jm
{
	using namespace std;

	static const auto gravity = vec2(0.0f, -9.8f);

	class Particle
	{
	public:
		vec2 pos;
		vec2 vel;
		RGB  color;

		void update(const float & dt)
		{
			const auto acc = gravity; //assumes GA only.

			vel += acc * dt;
			pos += vel * dt;

			// update age.
			// update color (blend with background color)
		}
	};
}
  • Particle
    • 파티클 1개 입자
      • pos 위치
      • vel 속도
      • color 색상
  • const auto acc = gravity;
    • 파티클 입자의 가속도에 영향 미치는건 중력뿐이라고 가정.
  • void update(const float & dt)
    • 파티클 입자 1개의 속도와 위치 업데이트


📜ParticleSystem.h

	class ParticleSystem
	{
	public:
		vector<Particle> particles;

		RandomNumberGenerator rn;

		ParticleSystem()
		{
			reset();
		}

		auto getRandomUnitVector()
		{
			const float theta = rn.getFloat(0.0f, 3.141592f * 2.0f); // 0.0 ~ 2pi

			return vec2{cos(theta), -sin(theta)};
		}

		auto getRandomColor()
		{
			return RGB{ rn.getFloat(0.0f, 1.0f), rn.getFloat(0.0f, 1.0f), rn.getFloat(0.0f, 1.0f) };
		}

		void reset()
		{
			particles.clear();

			// initialize particles
			for (int i = 0; i < 1000; ++i)
			{
				Particle new_particle;
				new_particle.pos = vec2(0.0f, 0.5f) + getRandomUnitVector() * rn.getFloat(0.001f, 0.03f);
				new_particle.vel = getRandomUnitVector() * rn.getFloat(0.01f, 2.0f);
				new_particle.color = getRandomColor();

				particles.push_back(new_particle);
			}
		}

		void update(const float & dt)
		{
			for (auto & pt : particles)
			{
				pt.update(dt);

				// remove particles when they are 1. too old, 2. out of screen.
			}
		}

		void draw()
		{
			beginTransformation();

			for (const auto & pt : particles)
			{
				drawPoint(pt.color, pt.pos, 3.0f);
			}

			endTransformation();
		}
	};
}
  • ParticleSystem
    • 여러 파티클 입자를 관리하기 위한 시스템
    • vector<Particle> particles
      • 파티클 입자 객체들이 벡터에 들어있다.
        • 파티클 객체를 굳이 삭제할 일이 없다면 이렇게 그냥 객체 변수로 넣자
        • 파티클 객체를 삭제할 일이 있다면 Particle* 포인터 타입 벡터로 만들어 파티클 입자들 동적할당 받아 넣어 주기
  • RandomNumberGenerator rn
    • 파티클은 랜덤한 위치 + 랜덤한 방향으로 움직이는 것이 자연스럽기 때문에 랜덤 넘버 생성 클래스의 함수들 사용할 것
  • auto getRandomUnitVector()
    • 0도 ~ 2π도 사이에서 랜덤한 각도 theta를 리턴
    • 길이가 1인 랜덤한 벡터Vec2, 즉 단위벡터를 리턴한다.
      • 길이가 1일 때의 x, y 좌표
        • image
        • x 좌표 : cos(theta)
        • y 좌표 : -sin(theta)
    • 랜덤한 방향을 나타내는 단위벡터
  • auto getRandomColor()
    • 랜덤한 색상 리턴
  • void reset()
    • particles.clear(); : 기존 벡터에 든 파티클 입자 원소들을 싹 지운다.
    • 파티클 초기화
      • Particle new_particle : 파티클 입자 객체 1개 만들기
        • new_particle.pos = vec2(0.0f, 0.5f) + getRandomUnitVector() * rn.getFloat(0.001f, 0.03f);
          • vec2(0.0f, 0.5f)
            • 파티클의 초기 위치는 여기.
          • getRandomUnitVector() * rn.getFloat(0.001f, 0.03f)
            • 단위벡터 * 랜덤한 float 스칼라값 (0.001f~ 0.03f 사이의)
            • 랜덤한 방향으로 랜덤한 길이기본 위치로부터 더해짐
          • 즉 basic 위치 vec2(0.0f, 0.5f) 로부터 랜덤한 벡터가 더해져 각 파티클마다 초기 생성 위치가 다르게 된다.
        • new_particle.vel
          • getRandomUnitVector() * rn.getFloat(0.01f, 2.0f);
            • 단위벡터 * 랜덤한 float 스칼라값 (0.001f~ 0.03f 사이의)
            • 랜덤한 방향으로 랜덤한 길이속도값이 설정됨.
        • new_particle.color
          • getRandomColor();
            • 랜덤한 색상
        • particles.push_back(new_particle);
          • 이렇게 생성위치 (pos), 속도(vel), 색상(color) 멤버변수를 설정해준 후 벡터에 넣기
  • void update(const float & dt)
    • 파티클 벡터들의 모든 원소에 시간 dt를 넘겨 속도와 위치를 업뎃한다
    • pt.update(dt);
      • Particle 의 함수 update를 소환함
  • void draw()
    • 벡터 안에 든 파티클 입자들을 하나하나 그려준다.
    • ✨ draw() 함수를 Particle 클래스가 아닌 ParticleSystem 클래스에 구현한 이유
      • Particle 클래스 안에 정의해도 되지만 그렇게 되면 호출할 때 파티클 입자 하나 그릴 때마다 함수를 1000번 호출해야 하므로 성능면에서 너무 안좋다.
      • 그냥 파티클 1000개를 관리하는 ParticleSystem 클래스 안에 구현하면 draw() 함수를 딱 한번 호출하는 것만으로도 천개를 다 그릴 수 있으므로.. for문으로 벡터의 모든 파티클을 다 그리는 함수를 ParticleSystem 안에 정의함.

📜 main

class Example : public Game2D
	{
	public:
		ParticleSystem ps;

		Example()
			: Game2D()
		{
			reset();
		}

		void reset()
		{
			ps.reset();
		}

		void update() override
		{
			const float dt = getTimeStep() * 0.4f;

			ps.update(dt);

			ps.draw();

			// reset button
			if (isKeyPressedAndReleased(GLFW_KEY_R)) reset();
		}

	};
}

int main(void)
{
	jm::Example().run();

	return 0;
}
  • ParticleSystem ps
    • ParticleSystem 파티클 시스템 객체만 생성면 된다.


🔔 파티클에 수명을 넣어보자.

  • Particle 입자 객체마다 수명이 다하면 사라지도록
  • Particle 입자 객체마다 수명이 다르도록

📜Particle.h

#include "Game2D.h"
#include "Examples/PrimitivesGallery.h"
#include "RandomNumberGenerator.h"
#include "RigidCircle.h"
#include <vector>
#include <memory>

namespace jm
{
	using namespace std;

	static const auto gravity = vec2(0.0f, -9.8f);

	class Particle
	{
	public:
		vec2 pos;
		vec2 vel;
		RGB  color;

		float age;
		float life;

		void update(const float & dt)
		{
			const auto acc = gravity; //assumes GA only.

			vel += acc * dt;
			pos += vel * dt;

			// update age
			age += dt;

			// update age.
			// update color (blend with background color)
		}
	};
}
  • class Particle
    • agelife를 추가했다.
    • float age
      • 현재 나이 저장
      • 단위는 시간
    • float life
      • 수명 저장
      • 단위는 시간
    • void update(const float & dt)
      • age += dt;
        • 나이 업데이트

📜ParticleSystem.h

	class ParticleSystem
	{
	public:
		vector<Particle> particles;

		RandomNumberGenerator rn;

		ParticleSystem()
		{
			reset();
		}

		auto getRandomUnitVector()
		{
			const float theta = rn.getFloat(0.0f, 3.141592f * 2.0f); // 0.0 ~ 2pi

			return vec2{cos(theta), -sin(theta)};
		}

		auto getRandomColor()
		{
			return RGB{ rn.getFloat(0.0f, 1.0f), rn.getFloat(0.0f, 1.0f), rn.getFloat(0.0f, 1.0f) };
		}

		void reset()
		{
			particles.clear();

			// initialize particles
			for (int i = 0; i < 1000; ++i)
			{
				Particle new_particle;
				new_particle.pos = vec2(0.0f, 0.5f) + getRandomUnitVector() * rn.getFloat(0.001f, 0.03f);
				new_particle.vel = getRandomUnitVector() * rn.getFloat(0.01f, 2.0f);
				new_particle.color = getRandomColor();
				new_particle.age = 0.0f;
				new_particle.life = rn.getFloat(0.1f, 1.0f);

				particles.push_back(new_particle);
			}
		}

		void update(const float & dt)
		{
			for (auto & pt : particles)
			{
				pt.update(dt);

				// remove particles when they are 1. too old, 2. out of screen.
			}
		}

		void draw()
		{
			beginTransformation();

			for (const auto & pt : particles)
			{
				if (pt.age > pt.life) continue;
				drawPoint(pt.color, pt.pos, 3.0f);
			}

			endTransformation();
		}
	};
}
  • ParticleSystem
    • void reset()
      • new_particle.age = 0.0f;
        • 파티클의 현재 나이를 0으로 초기화
      • new_particle.life = rn.getFloat(0.1f,1.0f);
        • 파티클의 수명은 0.1초~1초 중 랜덤하게
    • void draw()
      • 나이가 수명에 다다르면 파티클 입자를 그만 그리도록.
        • if (pt.age > pt.life) continue;
          • 나이가 수명을 넘어버린 Particle은 그리지 않고 그냥 continue 해버림


🔔 Fade out 효과, 블렌딩 효과 주기

📜ParticleSystem.h 의 draw()함수

void draw()
{
	for (const auto & pt : particles)
	{
		if (pt.age > pt.life) continue;

		const float alpha = 1.0f - pt.age / pt.life;
		const RGB blended_color = {
			pt.color.r * alpha + 1.0f * (1.0f - alpha),
			pt.color.g * alpha + 1.0f * (1.0f - alpha),
			pt.color.b * alpha + 1.0f * (1.0f - alpha) };

		beginTransformation();
		translate(pt.pos);
		drawFilledStar(blended_color, 0.03f, 0.01f);
		endTransformation();
			}
		}
  1. 배경(흰색)과 파티클 입자의 색상이 비슷해지도록 점점 블렌딩
  2. 나이가 수명에 다다를수록 점점 희미하게 그려지도록
    • 파티클 입자가 자연스럽게 사라지기 위해
    • void draw()
      • const float alpha = 1.0f - pt.age / pt.life;
    • 남은 수명이 얼마나 되는지를 0~1 사이의 숫자로 표현한다.
    • 갓 만들어진 age=0 의 파티클 입자라면 alpha값은 1이 될 것.
    • 반대로 수명이 다했다면 alpha=0 이 될 것. 수명에 다다를수록 alpha 값은 0에 가까워진다. - *const RGB blended_color *
    • 흰색의 RGB값은 (1, 1, 1) 이다.
      • 수명에 다다를 수록 alpha는 0에 가까워지므로 수명이 다하면 기존 파티클 색상인 pt.color.r,g,b 값은 0이 되갈 것이고 (1,1,1) 에 가까워질 것.
    • 별을 그리도록 바꿔보았다.
  3. 각각의 파티클 별 객체가 랜덤 각도로 회전하면서 떨어지도록.


🔔 별이 랜덤으로 회전하도록

image

📜Particle.h

#include "Game2D.h"
#include "Examples/PrimitivesGallery.h"
#include "RandomNumberGenerator.h"
#include "RigidCircle.h"
#include <vector>
#include <memory>

namespace jm
{
	using namespace std;

	static const auto gravity = vec2(0.0f, -9.8f);

	class Particle
	{
	public:
		vec2 pos;
		vec2 vel;
		RGB  color;

		float rot;  // 추가
		float angular_velocity; //추가

		float age;
		float life;

		void update(const float & dt)
		{
			const auto acc = gravity; //assumes GA only.

			vel += acc * dt;
			pos += vel * dt;

			rot += angular_velocity * dt; // 추가

			// update age
			age += dt;

			// update age.
			// update color (blend with background color)
		}
	};
}
  • float rot;
    • 회전 각도
      • 위치가 속도에 의해 바뀌듯이
      • 회전도 속도에 의해 바뀌도록.
  • float angular_velocity;
    • 회전 속도
      • rot - angular_velocity 관계는 pos - vec 관계와 동일
        • rot에 angular_velocity를 더해 rot을 계속 업데이트
      • cf)angular가속도도 있다. 본 강의에선 다루지 않음
  • void update(const float & dt)
    • rot += angular_velocity * dt;
    • 회전각도를 업데이트 한다.

📜ParticleSystem.h 의 reset(), draw() 함수

void reset()
{
	particles.clear();

	// initialize particles
	for (int i = 0; i < 1000; ++i)
	{
		Particle new_particle;
		new_particle.pos = vec2(0.0f, 0.5f) + getRandomUnitVector() * rn.getFloat(0.001f, 0.03f);
		new_particle.vel = getRandomUnitVector() * rn.getFloat(0.01f, 2.0f);
		new_particle.color = getRandomColor();
		new_particle.age = 0.0f;
		new_particle.life = rn.getFloat(0.1f, 1.0f);
		new_particle.rot = rn.getFloat(0.0f, 360.0f);
		new_particle.angular_velocity = 0.0f;

		particles.push_back(new_particle);
	}
}

void draw()
{
	for (const auto & pt : particles)
	{
		if (pt.age > pt.life) continue;

		const float alpha = 1.0f - pt.age / pt.life;
		const RGB blended_color = {
					pt.color.r * alpha + 1.0f * (1.0f - alpha),
					pt.color.g * alpha + 1.0f * (1.0f - alpha),
					pt.color.b * alpha + 1.0f * (1.0f - alpha) };

		beginTransformation();
		translate(pt.pos);
		rotate(pt.rot);
		drawFilledStar(blended_color, 0.03f, 0.01f);
		endTransformation();
	}
}
  • void reset()
    • rot의 초기값
      • 0 ~ 360 중 랜덤한 float값
    • angular_velocity
      • rn.getFloat(-1.0f, 1.0f) * 360.0 * 4.0ff;
        • -1 ~ 1 사이의 랜덤한 실수 뽑기
          • 음수면 반시계방향, 양수면 시계방향
          • 360을 곱해 속도각 정하기.
          • 랜덤한 수로 0.1f가 나왔다면 0.1 * 360 = 36. 시간당 36각도로 돌 것.
          • 4.0f는 그냥 더 빠르게 돌라고 덧붙임.
  • void draw()
    • rotate(pt.rot)

cf. 3차원에서의 회전

  • 자유도 개념
    • 현재 rotation을 나타내는 변수가 rot 1개
      • 1 자유도
      • 한 방향으로만 회전할 수 있음
        • 시계방향 or 반시계방향(시계방향의 음수)
        • 반시계 방향 회전은 시계방향의 음수 각도만큼 회전한것이라고 보기 때문에 두 경우를 모두 고려해도 1 자유도.
    • 2차원에선 1 자유도, 즉 회전 변수가 1개여도 괜찮지만 3차원에선 2 자유도가 아닌 3 자유도로 늘어난다.
      • 따라서 2차원인 지금은 float으로 회전각과 회전속도를 표현하지만
      • 3차원에선 회전각과 회전속도를 벡터로 표시해야 한다.


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

댓글남기기