For me
[DirectX12] 21) Particle System 본문
Particle System
위의 사진은 Unity Particle System인데, 파티클 시스템(particle system)은 게임 물리학, 모션 그래픽, 컴퓨터 그래픽스의 기술로, 전통적인 렌더링 기법으로는 재현하기 어려운 특정 종류의 Fuzzy 현상을 시뮬레이션하기 위해 많은 미세 스프라이트, 3D 모델 또는 기타 그래픽 객체를 사용
몇백 몇천개의 오브젝트를 직접 생성하면 프로그램에 엄청난 부하를 주게됨.
따라서 constant Buffer을 사용하지 않음. ( 사이즈가 정해져 있기 때문 )
=> StructuredBuffer 을 사용
SturcturedBuffer을 사용하면서 GPU에게 명령, CPU와 GPU의 긴 통신비용을 없앨 수 있음. (VRAM 과 GPU)
Compute Shader 사용하여 위치, 어떠한 물체가 spawn되는지 결정 이후 인스턴싱되어 실행.IM 단계 - 점 하나만 넘겨 텍스쳐를 SRV에 넘김Vertex Shader - 어디에 위치 계산Geometry Shader - 그려야 하는지 안그려야 하는지 판별 ( 필요없으면 이 과정에서 CUT )
물체가 업데이트 될 때, Compute Shader이 실행되고, Render이 실행이 되면서 Setting되어 Particle을 Camera에서 MeshRenderer이 아니라면 출력하는 형식으로 됨.
Particle Shader
더보기
#ifndef _PARTICLE_FX_
#define _PARTICLE_FX_
#include "params.fx"
#include "utils.fx"
struct Particle
{
float3 worldPos;
float curTime;
float3 worldDir;
float lifeTime;
int alive;
float3 padding;
};
StructuredBuffer<Particle> g_data : register(t9);
struct VS_IN
{
float3 pos : POSITION;
float2 uv : TEXCOORD;
float3 normal : NORMAL;
float3 tangent : TANGENT;
uint id : SV_InstanceID;
};
struct VS_OUT
{
float4 viewPos : POSITION;
float2 uv : TEXCOORD;
float id : ID;
};
// VS_MAIN
// g_float_0 : Start Scale
// g_float_1 : End Scale
// g_tex_0 : Particle Texture
VS_OUT VS_Main(VS_IN input)
{
VS_OUT output = (VS_OUT)0.f;
float3 worldPos = mul(float4(input.pos, 1.f), g_matWorld).xyz;
worldPos += g_data[input.id].worldPos;
output.viewPos = mul(float4(worldPos, 1.f), g_matView);
output.uv = input.uv;
output.id = input.id;
return output;
}
struct GS_OUT
{
float4 position : SV_Position;
float2 uv : TEXCOORD;
uint id : SV_InstanceID;
};
[maxvertexcount(6)]
void GS_Main(point VS_OUT input[1], inout TriangleStream<GS_OUT> outputStream)
{
GS_OUT output[4] =
{
(GS_OUT)0.f, (GS_OUT)0.f, (GS_OUT)0.f, (GS_OUT)0.f
};
VS_OUT vtx = input[0];
uint id = (uint)vtx.id;
if (0 == g_data[id].alive)
return;
float ratio = g_data[id].curTime / g_data[id].lifeTime;
float scale = ((g_float_1 - g_float_0) * ratio + g_float_0) / 2.f;
// View Space
output[0].position = vtx.viewPos + float4(-scale, scale, 0.f, 0.f);
output[1].position = vtx.viewPos + float4(scale, scale, 0.f, 0.f);
output[2].position = vtx.viewPos + float4(scale, -scale, 0.f, 0.f);
output[3].position = vtx.viewPos + float4(-scale, -scale, 0.f, 0.f);
// Projection Space
output[0].position = mul(output[0].position, g_matProjection);
output[1].position = mul(output[1].position, g_matProjection);
output[2].position = mul(output[2].position, g_matProjection);
output[3].position = mul(output[3].position, g_matProjection);
output[0].uv = float2(0.f, 0.f);
output[1].uv = float2(1.f, 0.f);
output[2].uv = float2(1.f, 1.f);
output[3].uv = float2(0.f, 1.f);
output[0].id = id;
output[1].id = id;
output[2].id = id;
output[3].id = id;
outputStream.Append(output[0]);
outputStream.Append(output[1]);
outputStream.Append(output[2]);
outputStream.RestartStrip();
outputStream.Append(output[0]);
outputStream.Append(output[2]);
outputStream.Append(output[3]);
outputStream.RestartStrip();
}
float4 PS_Main(GS_OUT input) : SV_Target
{
return g_tex_0.Sample(g_sam_0, input.uv);
}
struct ComputeShared
{
int addCount;
float3 padding;
};
RWStructuredBuffer<Particle> g_particle : register(u0);
RWStructuredBuffer<ComputeShared> g_shared : register(u1);
// CS_Main
// g_vec2_1 : DeltaTime / AccTime
// g_int_0 : Particle Max Count
// g_int_1 : AddCount
// g_vec4_0 : MinLifeTime / MaxLifeTime / MinSpeed / MaxSpeed
[numthreads(1024, 1, 1)]
void CS_Main(int3 threadIndex : SV_DispatchThreadID)
{
if (threadIndex.x >= g_int_0)
return;
int maxCount = g_int_0;
int addCount = g_int_1;
int frameNumber = g_int_2;
float deltaTime = g_vec2_1.x;
float accTime = g_vec2_1.y;
float minLifeTime = g_vec4_0.x;
float maxLifeTime = g_vec4_0.y;
float minSpeed = g_vec4_0.z;
float maxSpeed = g_vec4_0.w;
g_shared[0].addCount = addCount;
GroupMemoryBarrierWithGroupSync();
if (g_particle[threadIndex.x].alive == 0)
{
while (true)
{
int remaining = g_shared[0].addCount; // 5
if (remaining <= 0)
break;
int expected = remaining; //5
int desired = remaining - 1; //4
int originalValue; //4
/*
if(g_shared[0].addCount==expected)
{
g_shared[0].addCount=desired;
}
originalValue = g_shared[0].addCount;
*/
InterlockedCompareExchange(g_shared[0].addCount, expected, desired, originalValue);
if (originalValue == expected)
{
g_particle[threadIndex.x].alive = 1;
break;
}
}
if (g_particle[threadIndex.x].alive == 1)
{
float x = ((float)threadIndex.x / (float)maxCount) + accTime;
float r1 = Rand(float2(x, accTime));
float r2 = Rand(float2(x * accTime, accTime));
float r3 = Rand(float2(x * accTime * accTime, accTime * accTime));
// [0.5~1] -> [0~1]
float3 noise =
{
2 * r1 - 1,
2 * r2 - 1,
2 * r3 - 1
};
// [0~1] -> [-1~1]
float3 dir = (noise - 0.5f) * 2.f;
g_particle[threadIndex.x].worldDir = normalize(dir);
g_particle[threadIndex.x].worldPos = (noise.xyz - 0.5f) * 25;
g_particle[threadIndex.x].lifeTime = ((maxLifeTime - minLifeTime) * noise.x) + minLifeTime;
g_particle[threadIndex.x].curTime = 0.f;
}
}
else
{
g_particle[threadIndex.x].curTime += deltaTime;
if (g_particle[threadIndex.x].lifeTime < g_particle[threadIndex.x].curTime)
{
g_particle[threadIndex.x].alive = 0;
return;
}
float ratio = g_particle[threadIndex.x].curTime / g_particle[threadIndex.x].lifeTime;
float speed = (maxSpeed - minSpeed) * ratio + minSpeed;
g_particle[threadIndex.x].worldPos += g_particle[threadIndex.x].worldDir * speed * deltaTime;
}
}
#endif
float Rand(float2 co)
{
return 0.5 + (frac(sin(dot(co.xy, float2(12.9898, 78.233))) * 43758.5453)) * 0.5;
}
SturcturedBuffer.h
더보기
#pragma once
class StructuredBuffer
{
public:
StructuredBuffer();
~StructuredBuffer();
void Init(uint32 elementSize, uint32 elementCount, void* initialData = nullptr);
void PushGraphicsData(SRV_REGISTER reg);
void PushComputeSRVData(SRV_REGISTER reg);
void PushComputeUAVData(UAV_REGISTER reg);
ComPtr<ID3D12DescriptorHeap> GetSRV() { return _srvHeap; }
ComPtr<ID3D12DescriptorHeap> GetUAV() { return _uavHeap; }
void SetResourceState(D3D12_RESOURCE_STATES state) { _resourceState = state; }
D3D12_RESOURCE_STATES GetResourceState() { return _resourceState; }
ComPtr<ID3D12Resource> GetBuffer() { return _buffer; }
uint32 GetElementSize() { return _elementSize; }
uint32 GetElementCount() { return _elementCount; }
UINT GetBufferSize() { return _elementSize * _elementCount; }
private:
void CopyInitialData(uint64 bufferSize, void* initialData);
private:
ComPtr<ID3D12Resource> _buffer;
ComPtr<ID3D12DescriptorHeap> _srvHeap;
ComPtr<ID3D12DescriptorHeap> _uavHeap;
uint32 _elementSize = 0;
uint32 _elementCount = 0;
D3D12_RESOURCE_STATES _resourceState = {};
private:
D3D12_CPU_DESCRIPTOR_HANDLE _srvHeapBegin = {};
D3D12_CPU_DESCRIPTOR_HANDLE _uavHeapBegin = {};
};
SturcturedBuffer.cpp
더보기
#include "pch.h"
#include "StructuredBuffer.h"
#include "Engine.h"
StructuredBuffer::StructuredBuffer()
{
}
StructuredBuffer::~StructuredBuffer()
{
}
void StructuredBuffer::Init(uint32 elementSize, uint32 elementCount, void* initialData)
{
_elementSize = elementSize;
_elementCount = elementCount;
_resourceState = D3D12_RESOURCE_STATE_COMMON;
// Buffer
{
uint64 bufferSize = static_cast<uint64>(_elementSize) * _elementCount;
D3D12_RESOURCE_DESC desc = CD3DX12_RESOURCE_DESC::Buffer(bufferSize, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
D3D12_HEAP_PROPERTIES heapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);
DEVICE->CreateCommittedResource(
&heapProperties,
D3D12_HEAP_FLAG_NONE,
&desc,
_resourceState,
nullptr,
IID_PPV_ARGS(&_buffer));
if (initialData)
CopyInitialData(bufferSize, initialData);
}
// SRV
{
D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
srvHeapDesc.NumDescriptors = 1;
srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
DEVICE->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(&_srvHeap));
_srvHeapBegin = _srvHeap->GetCPUDescriptorHandleForHeapStart();
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = DXGI_FORMAT_UNKNOWN;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER;
srvDesc.Buffer.FirstElement = 0;
srvDesc.Buffer.NumElements = _elementCount;
srvDesc.Buffer.StructureByteStride = _elementSize;
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
DEVICE->CreateShaderResourceView(_buffer.Get(), &srvDesc, _srvHeapBegin);
}
// UAV
{
D3D12_DESCRIPTOR_HEAP_DESC uavheapDesc = {};
uavheapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
uavheapDesc.NumDescriptors = 1;
uavheapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
DEVICE->CreateDescriptorHeap(&uavheapDesc, IID_PPV_ARGS(&_uavHeap));
_uavHeapBegin = _uavHeap->GetCPUDescriptorHandleForHeapStart();
D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
uavDesc.Format = DXGI_FORMAT_UNKNOWN;
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;
uavDesc.Buffer.FirstElement = 0;
uavDesc.Buffer.NumElements = _elementCount;
uavDesc.Buffer.StructureByteStride = _elementSize;
uavDesc.Buffer.CounterOffsetInBytes = 0;
uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_NONE;
DEVICE->CreateUnorderedAccessView(_buffer.Get(), nullptr, &uavDesc, _uavHeapBegin);
}
}
void StructuredBuffer::PushGraphicsData(SRV_REGISTER reg)
{
GEngine->GetGraphicsDescHeap()->SetSRV(_srvHeapBegin, reg);
}
void StructuredBuffer::PushComputeSRVData(SRV_REGISTER reg)
{
GEngine->GetComputeDescHeap()->SetSRV(_srvHeapBegin, reg);
}
void StructuredBuffer::PushComputeUAVData(UAV_REGISTER reg)
{
GEngine->GetComputeDescHeap()->SetUAV(_uavHeapBegin, reg);
}
void StructuredBuffer::CopyInitialData(uint64 bufferSize, void* initialData)
{
ComPtr<ID3D12Resource> readBuffer = nullptr;
D3D12_RESOURCE_DESC desc = CD3DX12_RESOURCE_DESC::Buffer(bufferSize, D3D12_RESOURCE_FLAG_NONE);
D3D12_HEAP_PROPERTIES heapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
DEVICE->CreateCommittedResource(
&heapProperties,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&readBuffer));
uint8* dataBegin = nullptr;
D3D12_RANGE readRange{ 0, 0 };
readBuffer->Map(0, &readRange, reinterpret_cast<void**>(&dataBegin));
memcpy(dataBegin, initialData, bufferSize);
readBuffer->Unmap(0, nullptr);
// Common -> Copy
{
D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(_buffer.Get(),
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST);
RESOURCE_CMD_LIST->ResourceBarrier(1, &barrier);
}
RESOURCE_CMD_LIST->CopyBufferRegion(_buffer.Get(), 0, readBuffer.Get(), 0, bufferSize);
// Copy -> Common
{
D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(_buffer.Get(),
D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COMMON);
RESOURCE_CMD_LIST->ResourceBarrier(1, &barrier);
}
GEngine->GetGraphicsCmdQueue()->FlushResourceCommandQueue();
_resourceState = D3D12_RESOURCE_STATE_COMMON;
}
ParticleSystem.h
더보기
#pragma once
#include "Component.h"
class Material;
class Mesh;
class StructuredBuffer;
struct ParticleInfo
{
Vec3 worldPos;
float curTime;
Vec3 worldDir;
float lifeTime;
int32 alive;
int32 padding[3];
};
struct ComputeSharedInfo
{
int32 addCount;
int32 padding[3];
};
class ParticleSystem : public Component
{
public:
ParticleSystem();
virtual ~ParticleSystem();
public:
virtual void FinalUpdate();
void Render();
public:
virtual void Load(const wstring& path) override { }
virtual void Save(const wstring& path) override { }
private:
shared_ptr<StructuredBuffer> _particleBuffer;
shared_ptr<StructuredBuffer> _computeSharedBuffer;
uint32 _maxParticle = 1000;
shared_ptr<Material> _computeMaterial;
shared_ptr<Material> _materials;
shared_ptr<Mesh> _mesh;
float _createInterval = 0.005f;
float _accTime = 0.f;
float _minLifeTime = 0.5f;
float _maxLifeTime = 1.f;
float _minSpeed = 100;
float _maxSpeed = 50;
float _startScale = 10.f;
float _endScale = 5.f;
};
ParticleSystem.cpp
더보기
#include "pch.h"
#include "ParticleSystem.h"
#include "StructuredBuffer.h"
#include "Mesh.h"
#include "Resources.h"
#include "Transform.h"
#include "Timer.h"
ParticleSystem::ParticleSystem() : Component(COMPONENT_TYPE::PARTICLE_SYSTEM)
{
_particleBuffer = make_shared<StructuredBuffer>();
_particleBuffer->Init(sizeof(ParticleInfo), _maxParticle);
_computeSharedBuffer = make_shared<StructuredBuffer>();
_computeSharedBuffer->Init(sizeof(ComputeSharedInfo), 1);
_mesh = GET_SINGLE(Resources)->LoadPointMesh();
_materials = GET_SINGLE(Resources)->Get<Material>(L"Particle");
shared_ptr<Texture> tex = GET_SINGLE(Resources)->Load<Texture>(
L"Bubbles", L"..\\Resources\\Texture\\Particle\\bubble.png");
_materials->SetTexture(0, tex);
_computeMaterial = GET_SINGLE(Resources)->Get<Material>(L"ComputeParticle");
}
ParticleSystem::~ParticleSystem()
{
}
void ParticleSystem::FinalUpdate()
{
_accTime += DELTA_TIME;
int32 add = 0;
if (_createInterval < _accTime)
{
_accTime = _accTime - _createInterval;
add = 1;
}
_particleBuffer->PushComputeUAVData(UAV_REGISTER::u0);
_computeSharedBuffer->PushComputeUAVData(UAV_REGISTER::u1);
_computeMaterial->SetInt(0, _maxParticle);
_computeMaterial->SetInt(1, add);
_computeMaterial->SetVec2(1, Vec2(DELTA_TIME, _accTime));
_computeMaterial->SetVec4(0, Vec4(_minLifeTime, _maxLifeTime, _minSpeed, _maxSpeed));
_computeMaterial->Dispatch(1, 1, 1);
}
void ParticleSystem::Render()
{
GetTransform()->PushData();
_particleBuffer->PushGraphicsData(SRV_REGISTER::t9);
_materials->SetFloat(0, _startScale);
_materials->SetFloat(1, _endScale);
_materials->PushGraphicsData();
_mesh->Render(_maxParticle);
}
'DirectX12' 카테고리의 다른 글
[DirectX12] 23) Tesellation (0) | 2023.01.20 |
---|---|
[DirectX12] 22) Instancing (0) | 2023.01.20 |
[DirectX12] 20) Compute Shader (0) | 2023.01.19 |
[DirectX12] 19) Deferred , Forward Rendering (0) | 2023.01.19 |
[DirectX12] 18) Render Target (1) | 2023.01.19 |