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