[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
D3D11_MAP (d3d11.h) - Win32 apps
Identifies a resource to be accessed for reading and writing by the CPU. Applications may combine one or more of these flags. (D3D11_MAP)
learn.microsoft.com
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
댓글을 사용할 수 없습니다.