Particle System
무작위한 방식으로 움지기이는 입자들의 집합을 모형화 한 것
비, 연기, 폭발등 이펙트를 표현하는데 사용했다.
하지만, 2D 스프라이트보다 연산량이 많고, 두단계 쉐이더를 거쳐야 하기 때문에 무겁다.
ParticleSystem.h
ParticleSystem.h
#pragma once
class ParticleSystem : public Renderer
{
public:
ParticleSystem(wstring file);
~ParticleSystem();
void Reset();
void Add(Vector3& position);
public:
void Update();
private:
// 정점들을 GPU로 옮겨줌
void MapVertices();
void Activate();
void Deactivate();
private:
void ReadFile(wstring file);
private:
struct VertexParticle
{
Vector3 Position;
Vector2 Corner; // (-1 ~ +1)
Vector3 Velocity;
Vector4 Random; // x : 주기, y : 크기, z : 회전, w:색상
float Time;
};
private:
struct Desc
{
Color MinColor;
Color MaxColor;
Vector3 Gravity;
float EndVelocity;
Vector2 StartSize;
Vector2 EndSize;
Vector2 RotateSpeed;
float ReadyTime;
float ReadyRandomTime;
float ColorAmount;
float CurrentTime;
float Padding[2];
} desc;
private:
ParticleData data;
Texture* map;
ID3DX11EffectShaderResourceVariable* sMap;
ConstantBuffer* buffer;
ID3DX11EffectConstantBuffer* sBuffer;
VertexParticle* vertices = NULL;
UINT* indices = NULL;
float currentTime = 0.0f;
float lastAddTime = 0.0f;
UINT leadCount = 0;
UINT gpuCount = 0;
UINT activeCount = 0;
UINT deactiveCount = 0;
};
현재 만들 파티클의 위치와 랜덤으로 표현하기 위한 구조체를 만들어준뒤, GPU에 넘겨줄 Desc를 만들어준다.
ParticleSystem.cpp
#include "Framework.h"
#include "ParticleSystem.h"
#include "Utilities/Xml.h"
ParticleSystem::ParticleSystem(wstring file)
: Renderer(L"89_Particle.fxo")
{
ReadFile(L"../../_Textures/Particles/" + file + L".xml");
buffer = new ConstantBuffer(&desc, sizeof(Desc));
sBuffer = shader->AsConstantBuffer("CB_Particle");
sMap = shader->AsSRV("ParticleMap");
Reset();
}
ParticleSystem::~ParticleSystem()
{
}
void ParticleSystem::Reset()
{
currentTime = 0.0f;
lastAddTime = Time::Get()->Running();
gpuCount = leadCount = activeCount = deactiveCount = 0;
SafeDeleteArray(vertices);
SafeDeleteArray(indices);
SafeDelete(vertexBuffer);
SafeDelete(indexBuffer);
vertices = new VertexParticle[data.MaxParticles * 4];
for (UINT i = 0; i < data.MaxParticles; i++)
{
vertices[i * 4 + 0].Corner = Vector2(-1, -1);
vertices[i * 4 + 1].Corner = Vector2(-1, +1);
vertices[i * 4 + 2].Corner = Vector2(+1, -1);
vertices[i * 4 + 3].Corner = Vector2(+1, +1);
}
indices = new UINT[data.MaxParticles * 6];
for (UINT i = 0; i < data.MaxParticles; i++)
{
indices[i * 6 + 0] = i * 4 + 0;
indices[i * 6 + 1] = i * 4 + 1;
indices[i * 6 + 2] = i * 4 + 2;
indices[i * 6 + 3] = i * 4 + 2;
indices[i * 6 + 4] = i * 4 + 1;
indices[i * 6 + 5] = i * 4 + 3;
}
vertexBuffer = new VertexBuffer(vertices, data.MaxParticles * 4, sizeof(VertexParticle), 0, true);
indexBuffer = new IndexBuffer(indices, data.MaxParticles * 6);
}
void ParticleSystem::Add(Vector3 & position)
{
if (Time::Get()->Running() - lastAddTime < 60.0f / 1000.0f)
{
return;
}
lastAddTime = Time::Get()->Running();
UINT count = leadCount + 1;
if (count >= data.MaxParticles)
{
count = 0;
}
if (count == deactiveCount) { return; }
Vector3 velocity = Vector3(1, 1, 1);
velocity *= data.StartVelocity;
float horizontalVelocity = Math::Lerp(data.MinVerticalVelocity, data.MaxVerticalVelocity, Math::Random(0.0f, 1.0f));
float horizontalAngle = Math::PI * 2.0f * Math::Random(0.0f, 1.0f);
velocity.x += horizontalVelocity * cosf(horizontalAngle);
velocity.y += horizontalVelocity * sinf(horizontalAngle);
velocity.z += Math::Lerp(data.MinHorizontalVelocity, data.MaxHorizontalVelocity, Math::Random(0.0f, 1.0f));
Vector4 random = Math::RandomVec4(0.0f, 1.0f);
for (UINT i = 0; i < 4; i++)
{
vertices[leadCount * 4 + i].Position = position;
vertices[leadCount * 4 + i].Velocity = velocity;
vertices[leadCount * 4 + i].Random = random;
vertices[leadCount * 4 + i].Time = currentTime;
}
leadCount = count;
}
void ParticleSystem::Update()
{
Super::Update();
currentTime += Time::Delta();
MapVertices();
Activate();
Deactivate();
}
void ParticleSystem::MapVertices()
{
if (gpuCount == leadCount) { return; }
D3D11_MAPPED_SUBRESOURCE subResource;
if (leadCount > gpuCount)
{
D3D::GetDC()->Map(vertexBuffer->Buffer(), 0, D3D11_MAP_WRITE_NO_OVERWRITE, 0, &subResource);
{
UINT start = gpuCount * 4;
UINT size = (leadCount - gpuCount) * sizeof(VertexParticle) * 4;
UINT offset = gpuCount * sizeof(VertexParticle) * 4;
BYTE* p = (BYTE*)subResource.pData + offset;
memcpy(p, vertices + start, size);
}
D3D::GetDC()->Unmap(vertexBuffer->Buffer(), 0);
}
else
{
D3D::GetDC()->Map(vertexBuffer->Buffer(), 0, D3D11_MAP_WRITE_NO_OVERWRITE, 0, &subResource);
{
UINT start = gpuCount * 4;
UINT size = (data.MaxParticles - gpuCount) * sizeof(VertexParticle) * 4;
UINT offset = gpuCount * sizeof(VertexParticle) * 4;
BYTE* p = (BYTE*)subResource.pData + offset;
memcpy(p, vertices + start, size);
}
if (leadCount > 0)
{
UINT size = leadCount * sizeof(VertexParticle) * 4;
memcpy(subResource.pData, vertices, size);
}
D3D::GetDC()->Unmap(vertexBuffer->Buffer(), 0);
}
gpuCount = leadCount;
}
void ParticleSystem::Activate()
{
while (activeCount != gpuCount)
{
float age = currentTime - vertices[activeCount * 4].Time;
if (age < data.ReadyTime) { return; }
vertices[activeCount * 4].Time = currentTime;
activeCount++;
if (activeCount >= data.MaxParticles)
{
activeCount = 0;
}
}
}
void ParticleSystem::Deactivate()
{
while (activeCount != deactiveCount)
{
float age = currentTime - vertices[deactiveCount * 4].Time;
if (age > data.ReadyTime) { return; }
activeCount++;
if (deactiveCount >= data.MaxParticles)
{
deactiveCount = 0;
}
}
}
void ParticleSystem::ReadFile(wstring file)
{
Xml::XMLDocument* document = new Xml::XMLDocument();
Xml::XMLError error = document->LoadFile(String::ToString(file).c_str());
assert(error == Xml::XML_SUCCESS);
Xml::XMLElement* root = document->FirstChildElement();
Xml::XMLElement* node = root->FirstChildElement();
data.Type = (ParticleData::BlendType)node->IntText();
node = node->NextSiblingElement();
node = node->NextSiblingElement();
wstring textureFile = String::ToWString(node->GetText());
data.TextureFile = L"Particles/" + textureFile;
map = new Texture(data.TextureFile);
node = node->NextSiblingElement();
data.MaxParticles = node->IntText();
node = node->NextSiblingElement();
data.ReadyTime = node->FloatText();
node = node->NextSiblingElement();
data.ReadyRandomTime = node->FloatText();
node = node->NextSiblingElement();
data.StartVelocity = node->FloatText();
node = node->NextSiblingElement();
data.EndVelocity = node->FloatText();
node = node->NextSiblingElement();
data.MinHorizontalVelocity = node->FloatText();
node = node->NextSiblingElement();
data.MaxHorizontalVelocity = node->FloatText();
node = node->NextSiblingElement();
data.MinVerticalVelocity = node->FloatText();
node = node->NextSiblingElement();
data.MaxVerticalVelocity = node->FloatText();
node = node->NextSiblingElement();
data.Gravity.x = node->FloatAttribute("X");
data.Gravity.y = node->FloatAttribute("Y");
data.Gravity.z = node->FloatAttribute("Z");
node = node->NextSiblingElement();
data.MinColor.r = node->FloatAttribute("R");
data.MinColor.b = node->FloatAttribute("G");
data.MinColor.g = node->FloatAttribute("B");
data.MinColor.a = node->FloatAttribute("A");
node = node->NextSiblingElement();
data.MaxColor.r = node->FloatAttribute("R");
data.MaxColor.b = node->FloatAttribute("G");
data.MaxColor.g = node->FloatAttribute("B");
data.MaxColor.a = node->FloatAttribute("A");
node = node->NextSiblingElement();
data.MinRotateSpeed = node->FloatText();
node = node->NextSiblingElement();
data.MaxRotateSpeed = node->FloatText();
node = node->NextSiblingElement();
data.MinStartSize = node->FloatText();
node = node->NextSiblingElement();
data.MaxStartSize = node->FloatText();
node = node->NextSiblingElement();
data.MinEndSize = node->FloatText();
node = node->NextSiblingElement();
data.MaxEndSize = node->FloatText();
SafeDelete(document);
}
가장 중요한 함수 몇개만 보자
void ParticleSystem::Add(Vector3 & position)
{
if (Time::Get()->Running() - lastAddTime < 60.0f / 1000.0f)
{
return;
}
lastAddTime = Time::Get()->Running();
UINT count = leadCount + 1;
if (count >= data.MaxParticles)
{
count = 0;
}
if (count == deactiveCount) { return; }
Vector3 velocity = Vector3(1, 1, 1);
velocity *= data.StartVelocity;
float horizontalVelocity = Math::Lerp(data.MinVerticalVelocity, data.MaxVerticalVelocity, Math::Random(0.0f, 1.0f));
float horizontalAngle = Math::PI * 2.0f * Math::Random(0.0f, 1.0f);
velocity.x += horizontalVelocity * cosf(horizontalAngle);
velocity.y += horizontalVelocity * sinf(horizontalAngle);
velocity.z += Math::Lerp(data.MinHorizontalVelocity, data.MaxHorizontalVelocity, Math::Random(0.0f, 1.0f));
Vector4 random = Math::RandomVec4(0.0f, 1.0f);
for (UINT i = 0; i < 4; i++)
{
vertices[leadCount * 4 + i].Position = position;
vertices[leadCount * 4 + i].Velocity = velocity;
vertices[leadCount * 4 + i].Random = random;
vertices[leadCount * 4 + i].Time = currentTime;
}
leadCount = count;
}
각 프레임마다 정점들을 4개씩 묶어 업데이트를 해준다.
void ParticleSystem::MapVertices()
{
if (gpuCount == leadCount) { return; }
D3D11_MAPPED_SUBRESOURCE subResource;
if (leadCount > gpuCount)
{
D3D::GetDC()->Map(vertexBuffer->Buffer(), 0, D3D11_MAP_WRITE_NO_OVERWRITE, 0, &subResource);
{
UINT start = gpuCount * 4;
UINT size = (leadCount - gpuCount) * sizeof(VertexParticle) * 4;
UINT offset = gpuCount * sizeof(VertexParticle) * 4;
BYTE* p = (BYTE*)subResource.pData + offset;
memcpy(p, vertices + start, size);
}
D3D::GetDC()->Unmap(vertexBuffer->Buffer(), 0);
}
else
{
D3D::GetDC()->Map(vertexBuffer->Buffer(), 0, D3D11_MAP_WRITE_NO_OVERWRITE, 0, &subResource);
{
UINT start = gpuCount * 4;
UINT size = (data.MaxParticles - gpuCount) * sizeof(VertexParticle) * 4;
UINT offset = gpuCount * sizeof(VertexParticle) * 4;
BYTE* p = (BYTE*)subResource.pData + offset;
memcpy(p, vertices + start, size);
}
if (leadCount > 0)
{
UINT size = leadCount * sizeof(VertexParticle) * 4;
memcpy(subResource.pData, vertices, size);
}
D3D::GetDC()->Unmap(vertexBuffer->Buffer(), 0);
}
gpuCount = leadCount;
}
GPU로 넘겨줬던 Pointer와 현재 Pointer를 비교해서 넘겨준다.
만약 현재 GPUcount가 LeadCount 보다 작을 경우 GPUCount까지 넘겨주면 된다.
하지만 그 반대일 경우에는 LeadCount에서 끝까지 넘겨주고 처음부터 GpuCount까지 넘겨준다.
void ParticleSystem::Activate()
{
while (activeCount != gpuCount)
{
float age = currentTime - vertices[activeCount * 4].Time;
if (age < data.ReadyTime) { return; }
vertices[activeCount * 4].Time = currentTime;
activeCount++;
if (activeCount >= data.MaxParticles)
{
activeCount = 0;
}
}
}
void ParticleSystem::Deactivate()
{
while (activeCount != deactiveCount)
{
float age = currentTime - vertices[deactiveCount * 4].Time;
if (age > data.ReadyTime) { return; }
activeCount++;
if (deactiveCount >= data.MaxParticles)
{
deactiveCount = 0;
}
}
}
그리고 현재 시간에 맞춰 각 파티클을 Active, Deactive 해준다.