[C++] 3.4 파티클 시스템
카테고리: C++ games
태그: Cpp Graphics OpenGL Programming
인프런에 있는 홍정모 교수님의 홍정모의 게임 만들기 연습 문제 패키지 강의를 듣고 정리한 필기입니다.😀 
🌜 공부에 사용된 홍정모 교수님의 코드들 보러가기 
🌜 [홍정모의 게임 만들기 연습 문제 패키지] 강의 들으러 가기!
Chapter 3. 게임 물리 맛보기 : 파티클 시스템

📜 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 색상
 
 
- 파티클 1개 입자
        
- 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 좌표
             
- x 좌표 : cos(theta)
- y 좌표 : -sin(theta)
 
 
- 길이가 1일 때의 x, y 좌표
            
- 랜덤한 방향을 나타내는 단위벡터
 
- 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) 로부터 랜덤한 벡터가 더해져 각 파티클마다 초기 생성 위치가 다르게 된다.
 
- vec2(0.0f, 0.5f)
                    
- new_particle.vel
                - getRandomUnitVector() * rn.getFloat(0.01f, 2.0f);
                    - 단위벡터 * 랜덤한 float 스칼라값 (0.001f~ 0.03f 사이의)
- 랜덤한 방향으로- 랜덤한 길이로 속도값이 설정됨.
 
 
- getRandomUnitVector() * rn.getFloat(0.01f, 2.0f);
                    
- new_particle.color
                - getRandomColor();
                    - 랜덤한 색상
 
 
- getRandomColor();
                    
- particles.push_back(new_particle);
                - 이렇게 생성위치 (pos), 속도(vel), 색상(color) 멤버변수를 설정해준 후 벡터에 넣기
 
 
- new_particle.pos = vec2(0.0f, 0.5f) + getRandomUnitVector() * rn.getFloat(0.001f, 0.03f);
                
 
- Particle new_particle : 파티클 입자 객체 1개 만들기
            
 
- 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
    - age와- life를 추가했다.
- float age
        - 현재 나이 저장
- 단위는 시간
 
- float life
        - 수명 저장
- 단위는 시간
 
- void update(const float & dt)
        - age += 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초 중 랜덤하게
 
 
- new_particle.age = 0.0f;
            
- void draw()
        - 나이가 수명에 다다르면 파티클 입자를 그만 그리도록.
            - if (pt.age > pt.life) continue;
                - 나이가 수명을 넘어버린 Particle은 그리지 않고 그냥 continue 해버림
 
- 나이가 수명을 넘어버린 
 
- if (pt.age > pt.life) continue;
                
 
- 나이가 수명에 다다르면 파티클 입자를 그만 그리도록.
            
 
- void reset()
        
🔔 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();
			}
		}
- 배경(흰색)과 파티클 입자의 색상이 비슷해지도록 점점 블렌딩
- 나이가 수명에 다다를수록 점점 희미하게 그려지도록
    - 파티클 입자가 자연스럽게 사라지기 위해
- void draw()
        - const float alpha= 1.0f - pt.age / pt.life;
 
- const float 
 - 남은 수명이 얼마나 되는지를 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) 에 가까워질 것.
 
- 별을 그리도록 바꿔보았다.
 
- 각각의 파티클 별 객체가 랜덤 각도로 회전하면서 떨어지도록.
🔔 별이 랜덤으로 회전하도록

📜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가속도도 있다. 본 강의에선 다루지 않음
 
- rot - angular_velocity 관계는 pos - vec 관계와 동일
            
 
- 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는 그냥 더 빠르게 돌라고 덧붙임.
 
 
- -1 ~ 1 사이의 랜덤한 실수 뽑기
                
 
- rn.getFloat(-1.0f, 1.0f) * 360.0 * 4.0ff;
            
 
- rot의 초기값
        
- void draw()
    - rotate(pt.rot)
 
cf. 3차원에서의 회전
- 자유도 개념- 현재 rotation을 나타내는 변수가 rot 1개
        - 1 자유도
- 한 방향으로만 회전할 수 있음
            - 시계방향 or 반시계방향(시계방향의 음수)
- 반시계 방향 회전은 시계방향의 음수 각도만큼 회전한것이라고 보기 때문에 두 경우를 모두 고려해도 1 자유도.
 
 
- 2차원에선- 1 자유도, 즉 회전 변수가 1개여도 괜찮지만- 3차원에선 2 자유도가 아닌- 3 자유도로 늘어난다.- 따라서 2차원인 지금은float으로 회전각과 회전속도를 표현하지만
- 3차원에선 회전각과 회전속도를- 벡터로 표시해야 한다.
 
- 따라서 
 
- 현재 rotation을 나타내는 변수가 rot 1개
        
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
 
      
    
댓글 남기기