[DirectX11] 089~94 Particle System
Particle System은 입자라고 하는 작고 개별적인 시각적 요소를 시뮬레이션하고 렌더링하는 데 사용되는 그래픽 기술로, 서로 움직이고 환경과 상호 작용한다. 화재, 연기, 물, 폭발 등과 같은 현상의 사실적이고 역동적인 애니메이션을 만들기 위해 컴퓨터 게임 및 시각 효과에 일반적으로 사용된다.
목차
Particle System
DirectX11에서는 GPU를 사용하여 계산 및 렌더링을 수행하는 파티클 시스템을 구현할 수 있다. DirectX11에서 파티클 시스템을 만드는 기본 단계는 다음과 같다.
- 입자 데이터를 저장할 Vertex Buffer를 생성한다. 여기에는 각 입자의 위치, 속도, 색상, 크기 및 기타 속성이 포함된다.
- 입자 시뮬레이션 및 렌더링을 처리하도록 셰이더 프로그램을 설정한다. 여기에는 입자의 위치를 변환하는 정점 셰이더와 이를 렌더링하는 픽셀 셰이더를 만드는 작업이 포함된다.
- 각 프레임의 GPU에서 입자 데이터를 업데이트하여 입자의 동작과 상호 작용을 시뮬레이션한다. 여기에는 컴퓨트 셰이더를 사용하여 입자 이동, 충돌 감지 및 힘과 같은 계산을 수행하는 작업이 포함된다.
- GPU를 사용하여 화면에 입자를 렌더링한다. 여기에는 버텍스 버퍼와 셰이더 프로그램을 사용하여 입자를 올바른 색상과 크기의 포인트 또는 쿼드로 그리는 작업이 포함된다.
파티클 스폰, 사망 및 컬링과 같은 추가 기능을 구현하여 파티클 시스템을 보다 현실적이고 효율적으로 만든다.
DirectX11의 파티클 시스템은 실시간 응용 프로그램에서 복잡하고 동적인 시각 효과를 만들기 위한 강력한 도구이다.
Shaders | |
Particle.fx |
|
Framework | |
Particle | |
ParticleData.h 생성 ParticleSystem.h .cpp 생성 |
|
ParticleEditor | |
Viewer.h. cpp 생성 Main.h .cpp 생성 |
|
ParticleEditor 폴더 생성
ModelEditor 폴더를 복사하여 ParticleEditor 폴더 생성
- 필요없는 파일은 삭제.
- 솔루션 우클릭 - 추가 - 기존 프로젝트
Debug 구성 관리자 - x64제거
Framework 우클릭 - 속성 - 빌드 이벤트 - 빌드 후 이벤트 - 명령에 ParticleEditor 추가
ParticleEditor 우클릭 - 빌드 종속성 - 프로젝트종속성 - Framework 체크 후 확인
Particle.fx
Particle.fx
#include "00_Global.fx"
Texture2D ParticleMap;
struct ParticleDesc
{
float4 MinColor;
float4 MaxColor;
float3 Gravity;
float EndVelocity;
float2 StartSize;
float2 EndSize;
float2 RotateSpeed;
float ReadyTime;
float ReadyRandomTime;
float ColorAmount;
float CurrentTime;
};
cbuffer CB_Particle
{
ParticleDesc Particle;
};
struct VertexInput
{
float4 Position : Position;
float2 Corner : Uv;
float3 Velocity : Velocity;
float4 Random : Random; //x : 주기, y - 크기, z - 회전, w - 색상
float Time : Time;
};
struct VertexOutput
{
float4 Position : SV_Position;
float4 Color : Color;
float2 Uv : Uv;
};
float4 ComputePosition(float3 position, float3 velocity, float age, float normalizedAge)
{
float start = length(velocity); //속도
float end = start * Particle.EndVelocity;
float amount = start * normalizedAge + (end - start) * normalizedAge / 2;
position += normalize(velocity) * amount * Particle.ReadyTime;
position += Particle.Gravity * age * normalizedAge;
return ViewProjection(float4(position, 1));
}
float ComputeSize(float value, float normalizedAge)
{
float start = lerp(Particle.StartSize.x, Particle.StartSize.y, value);
float end = lerp(Particle.EndSize.x, Particle.EndSize.y, value);
return lerp(start, end, normalizedAge);
}
float2x2 ComputeRotation(float value, float age)
{
float angle = lerp(Particle.RotateSpeed.x, Particle.RotateSpeed.y, value);
float radian = angle * age;
float c = cos(radian);
float s = sin(radian);
return float2x2(c, -s, s, c);
}
float4 ComputeColor(float value, float normalizedAge)
{
float4 color = lerp(Particle.MinColor, Particle.MaxColor, value) * normalizedAge;
return color * Particle.ColorAmount;
}
VertexOutput VS(VertexInput input)
{
VertexOutput output;
float age = Particle.CurrentTime - input.Time;
age *= input.Random.x * Particle.ReadyRandomTime + 1;
float normalizedAge = saturate(age / Particle.ReadyTime);
output.Position = ComputePosition(input.Position.xyz, input.Velocity, age, normalizedAge);
float size = ComputeSize(input.Random.y, normalizedAge);
float2x2 rotation = ComputeRotation(input.Random.z, age);
output.Position.xy += mul(input.Corner, rotation) * size * 0.5f;
output.Uv = (input.Corner + 1.0f) * 0.5f;
output.Color = ComputeColor(input.Random.w, normalizedAge);
return output;
}
float4 PS(VertexOutput input) : SV_Target
{
return ParticleMap.Sample(LinearSampler, input.Uv) * input.Color;
}
technique11 T0
{
P_DSS_BS_VP(P0, DepthRead_Particle, OpaqueBlend, VS, PS)
P_DSS_BS_VP(P1, DepthRead_Particle, AdditiveBlend_Particle, VS, PS)
P_DSS_BS_VP(P2, DepthRead_Particle, AlphaBlend, VS, PS)
}
ParticleData
ParticleData.h
#pragma once
#include "Framework.h"
struct ParticleData
{
enum class BlendType
{
Opaque = 0, Additive, AlphaBlend
} Type = BlendType::Opaque;
bool bLoop = false;
wstring TextureFile = L"";
UINT MaxParticles = 100;
float ReadyTime = 1.0f;
float ReadyRandomTime = 0;
float StartVelocity = 1;
float EndVelocity = 1;
float MinHorizontalVelocity = 0;
float MaxHorizontalVelocity = 0;
float MinVerticalVelocity = 0;
float MaxVerticalVelocity = 0;
Vector3 Gravity = Vector3(0, 0, 0);
float ColorAmount = 1.0f;
Color MinColor = Color(1, 1, 1, 1);
Color MaxColor = Color(1, 1, 1, 1);
float MinRotateSpeed = 0;
float MaxRotateSpeed = 0;
float MinStartSize = 100;
float MaxStartSize = 100;
float MinEndSize = 100;
float MaxEndSize = 100;
};
ParticleSystem
ParticleSystem.h
#pragma once
class ParticleSystem : public Renderer
{
public:
ParticleSystem(wstring file);
~ParticleSystem();
void Reset(); //초기화 함수
void Add(Vector3& position); //외부에서 추가
public:
void Update();//업데이트 함수. 외부에서 Update를 콜할때 사용
private:
void MapVertices();
void Activate();
void Deactivate();
public:
void Render();
ParticleData& GetData() { return data; }
void SetTexture(wstring file);
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 = NULL;
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;
};
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");//.xml파일을 읽는다.
buffer = new ConstantBuffer(&desc, sizeof(Desc));
sBuffer = shader->AsConstantBuffer("CB_Particle");
sMap = shader->AsSRV("ParticleMap");
Reset();
}
ParticleSystem::~ParticleSystem()
{
SafeDelete(map);
SafeDelete(buffer);
SafeDeleteArray(vertices);
SafeDeleteArray(indices);
}
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) //60프레임 제한
return;
lastAddTime = Time::Get()->Running();
UINT count = leadCount + 1; //시작해서 갈 카운트
if (count >= data.MaxParticles)
{
if (data.bLoop == true)
{
count = 0;
}
else
{
count = data.MaxParticles;
return;
}
}
if (count == deactiveCount)
return;
Vector3 velocity = Vector3(1, 1, 1); //기본 속도이자 방향
velocity *= data.StartVelocity;
float horizontalVelocity = Math::Lerp(data.MinHorizontalVelocity, data.MaxHorizontalVelocity, Math::Random(0.0f, 1.0f));//수평이동 속도를 선형보간으로 만든다.
float horizontalAngle = Math::PI * 2.0f * Math::Random(0.0f, 1.0f);//z방향 회전
velocity.x += horizontalVelocity * cosf(horizontalAngle);//x속도
velocity.y += horizontalVelocity * sinf(horizontalAngle);//y속도
velocity.z += Math::Lerp(data.MinHorizontalVelocity, data.MaxHorizontalVelocity, Math::Random(0.0f, 1.0f));//z속도
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();
if (activeCount == leadCount)
currentTime = 0.0f;
desc.MinColor = data.MinColor;
desc.MaxColor = data.MaxColor;
desc.ColorAmount = data.ColorAmount;
desc.Gravity = data.Gravity;
desc.EndVelocity = data.EndVelocity;
desc.RotateSpeed = Vector2(data.MinRotateSpeed, data.MaxRotateSpeed);
desc.StartSize = Vector2(data.MinStartSize, data.MaxStartSize);
desc.EndSize = Vector2(data.MinEndSize, data.MaxEndSize);
desc.ReadyTime = data.ReadyTime;
desc.ReadyRandomTime = data.ReadyRandomTime;
}
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 //(leadCount < gpuCount)인 경우
{
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 = (data.bLoop == true) ? 0 : data.MaxParticles;
}
}
void ParticleSystem::Deactivate()
{
while (activeCount != deactiveCount)
{
float age = currentTime - vertices[deactiveCount * 4].Time;
if (age > data.ReadyTime)
return;
deactiveCount++;
if (deactiveCount >= data.MaxParticles)
deactiveCount = (data.bLoop == true) ? 0 : data.MaxParticles;
}
}
void ParticleSystem::Render()
{
Super::Render();
desc.CurrentTime = currentTime;
buffer->Render();
sBuffer->SetConstantBuffer(buffer->Buffer());
sMap->SetResource(map->SRV());
if (leadCount == activeCount)
return;
UINT pass = (UINT)data.Type;
if (leadCount > activeCount)
{
shader->DrawIndexed(0, pass, (leadCount - activeCount) * 6, activeCount * 6);
}
else
{
shader->DrawIndexed(0, pass, (data.MaxParticles - activeCount) * 6, activeCount * 6);
if (leadCount > 0)
shader->DrawIndexed(0, pass, leadCount * 6);
}
}
void ParticleSystem::SetTexture(wstring file)
{
SafeDelete(map);
map = new Texture(file);
}
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();
data.bLoop = node->BoolText();
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.ColorAmount = node->FloatText();
node = node->NextSiblingElement();
data.MinColor.r = node->FloatAttribute("R");
data.MinColor.g = node->FloatAttribute("G");
data.MinColor.b = node->FloatAttribute("B");
data.MinColor.a = node->FloatAttribute("A");
node = node->NextSiblingElement();
data.MaxColor.r = node->FloatAttribute("R");
data.MaxColor.g = node->FloatAttribute("G");
data.MaxColor.b = 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);
}
D3D11_MAP_WRITE | D3D11_CPU_ACCESS_WRITE일 때만 접근가능. |
D3D11_MAP_READ_WRITE | D3D11_CPU_ACCESS_READ + D3D11_CPU_ACCESS_WRITE일 때만 접근가능. |
D3D11_MAP_WRITE_DISCARD | D3D11_CPU_ACCESS_WRITE + D3D11_USAGE_DYNAMIC일 때만 접근가능. |
D3D11_MAP_WRITE_NO_OVERWRITE | D3D11_CPU_ACCESS_WRITE일 때만 사용가능. D3D11_BIND_CONSTANT BUFFER일 때는 사용불가능. |
https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_map
Viewer
Viewer.h
#pragma once
#include "Systems/IExecute.h"
class Viewer : public IExecute
{
public:
virtual void Initialize() override;
virtual void Ready() override {}
virtual void Destroy() override;
virtual void Update() override;
virtual void PreRender() override {}
virtual void Render() override;
virtual void PostRender() override {}
virtual void ResizeScreen() override {}
private:
void Mesh();
private:
Shader* shader;
CubeSky* sky;
ParticleSystem* particleSystem;
Material* floor;
Material* stone;
MeshRender* sphere;
MeshRender* grid;
};
화면에 그릴 shader, 하늘, particleSystem, 바닥 재질, 돌 재질, 구, 그리드를 세팅한다.
Viewer.cpp
#include "stdafx.h"
#include "Viewer.h"
void Viewer::Initialize()
{
Context::Get()->GetCamera()->RotationDegree(20, 0, 0);
Context::Get()->GetCamera()->Position(1, 36, -85);
((Freedom *)Context::Get()->GetCamera())->Speed(50, 2);
shader = new Shader(L"82_NormalMapping.fxo");
sky = new CubeSky(L"Environment/GrassCube1024.dds");
particleSystem = new ParticleSystem(L"Fire");//Fire 파티클 생성.
//particleSystem = new ParticleSystem(L"Explosion");
Mesh();
}
void Viewer::Destroy()
{
}
void Viewer::Update()
{
sky->Update();
sphere->Update();
grid->Update();
Vector3 position;
sphere->GetTransform(0)->Position(&position);
if (Keyboard::Get()->Press('L'))
position.x += 20 * Time::Delta();
else if (Keyboard::Get()->Press('J'))
position.x -= 20 * Time::Delta();
if (Keyboard::Get()->Press('I'))
position.z += 20 * Time::Delta();
else if (Keyboard::Get()->Press('K'))
position.z -= 20 * Time::Delta();
if (Keyboard::Get()->Press('O'))
position.y += 20 * Time::Delta();
else if (Keyboard::Get()->Press('U'))
position.y -= 20 * Time::Delta();
sphere->GetTransform(0)->Position(position);
sphere->UpdateTransforms();
particleSystem->Add(position);
particleSystem->Update();
}
void Viewer::Render()
{
sky->Render();
stone->Render();
sphere->Render();
floor->Render();
grid->Render();
particleSystem->Render();
}
void Viewer::Mesh()
{
floor = new Material(shader);
floor->DiffuseMap("Floor.png");
floor->Specular(1, 1, 1, 20);
floor->SpecularMap("Floor_Specular.png");
floor->NormalMap("Floor_Normal.png");
stone = new Material(shader);
stone->DiffuseMap("Stones.png");
stone->Specular(1, 1, 1, 20);
stone->SpecularMap("Stones_Specular.png");
stone->Emissive(0.15f, 0.15f, 0.15f, 0.3f);
stone->NormalMap("Stones_Normal.png");
Transform* transform = NULL;
grid = new MeshRender(shader, new MeshGrid(5, 5));
transform = grid->AddTransform();
transform->Position(0, 0, 0);
transform->Scale(12, 1, 12);
grid->UpdateTransforms();
sphere = new MeshRender(shader, new MeshSphere(0.5f, 20, 20));
transform = sphere->AddTransform();
transform->Position(0, 5, 0);
transform->Scale(5, 5, 5);
sphere->UpdateTransforms();
}
마무리
마무리
'⭐ DirectX > DirectX11 3D' 카테고리의 다른 글
[DirectX11] 096~98 Geometry Shader (0) | 2023.04.12 |
---|---|
[DirectX11] 095 Particle Editor (0) | 2023.04.12 |
[DirectX11] 085~88 Weather (Rain, Snow) (0) | 2023.04.02 |
[DirectX11] 083~84 Billboard (0) | 2023.03.28 |
[DirectX11] 081~82 Normal Mapping (0) | 2023.03.27 |
댓글
이 글 공유하기
다른 글
-
[DirectX11] 096~98 Geometry Shader
[DirectX11] 096~98 Geometry Shader
2023.04.12 -
[DirectX11] 095 Particle Editor
[DirectX11] 095 Particle Editor
2023.04.12 -
[DirectX11] 085~88 Weather (Rain, Snow)
[DirectX11] 085~88 Weather (Rain, Snow)
2023.04.02 -
[DirectX11] 083~84 Billboard
[DirectX11] 083~84 Billboard
2023.03.28