Particle system

ㅋㅋ·2022년 7월 28일
0

DirectX12강의

목록 보기
32/39

특수한 효과를 내기 위하여 작은 입자들을 사용할 시 수백, 수천개가 필요할 수 있고,

이를 이전과 마찬가지로 렌더링할 시 부하가 매우 큼

=>

instancing 기술 사용

input assembler로 한번 전달 받은 정보를 정한 회수만큼 반복

=>

reduce draw call


입자들은 회수만큼 반복하여 만들어진 instance들은 ID를 기준으로 계산하게 됨

만들어진 입자들의 정보는 각기 다르기 때문에 이를 위하여 버퍼 필요

=>

structured buffer

t 레지스터나 u 레지스터를 사용하여 유동적인 크기로 할당하여 사용

constant buffer로 사용하던 b나 t 레지스터는 read only임

u 레지스터는 read/write가 가능


입자들은 각기 다른 상태를 가지게 됨

입자가 하나씩 만들어지며 life time을 넘어간 입자는 없어지게 됨

회수만큼 반복하여 만든 이 입자들을 필터링할 필요가 있음

=>

지오메트리 쉐이더에서 정점들을 그릴 필요가 있는지 판단하여 필터링


particle 쉐이더 파일

struct ComputeShared
{
    int addCount;
    float3 padding;
};

RWStructuredBuffer<Particle> g_particle : register(u0);
RWStructuredBuffer<ComputeShared> g_shared : register(u1);

[numthreads(1024, 1, 1)]
void CS_Main(int3 threadIndex : SV_DispatchThreadID)
{
    ...

    g_shared[0].addCount = addCount;
    GroupMemoryBarrierWithGroupSync();

    ...
    
}

compute 쉐이더를 이용하여 스레드를 이용할 때 공용 메모리를 사용한다면

GroupMemoryBarrierWithGroupSync 함수를 사용하여 모든 스레드가 해당 위치까지

온 것을 보장하도록 동기화할 수 있음

addCount는 life time이 끝나 죽어있는 파티클을 살릴 때 사용할 카운트


while (true)
{
    int remaining = g_shared[0].addCount;
    if (remaining <= 0)
        break;

    int expected = remaining;
    int desired = remaining - 1;
    int originalValue;
    InterlockedCompareExchange(g_shared[0].addCount, expected, desired, originalValue);

    if (originalValue == expected)
    {
        g_particle[threadIndex.x].alive = 1;
        break;
    }
}

파티클을 살려야하는 상황에서 해당 스레드가 공용 메모리의 변수 값을 수정할 때

InterlockedCompareExchange 함수로 스레드간 동기화하여 변수를 수정

g_shared[0].addCount와 expected가 같다면,

originalValue에 g_shared[0].addCount를 저장 후

g_shared[0].addCount에 desired를 저장


쉐이더에서는 랜덤 함수를 지원하지 않기 때문에

해당 함수를 만들거나 noise texture라는 패턴 없이 랜덤한 값이 들어간

텍스쳐를 사용하는 경우도 있음


[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;

    ...

    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();
}

maxvertexcount: 해당 지오메트리 쉐이더에서 반환하는 정점의 최대 개수

input으로 하나의 정점 정보를 받아와 해당 점의 정보를 사용하여 rectangle이

그려져야하는지 판단함

그려져야 한다면 outputStream에 정점들을 넣고 restartstrip 함수로 삼각형을 정의


structured buffer 클래스 구현

class StructuredBuffer
{
public:
	...

	void Init(uint32 elementSize, uint32 elementCount);

	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; }


	...
};

constant buffer와 비슷하지만 사용할 크기를 초기화 때 받게 됨

compute 쉐이더를 사용하기 때문에 SRV와 UAV를 동시에 사용


particle system 클래스 구현

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();
	
    ...
};

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);

	...
    
private:
	shared_ptr<StructuredBuffer>	_particleBuffer;
	shared_ptr<StructuredBuffer>	_computeSharedBuffer;
    
    ...
}

쉐이더에서 사용할 자료들을 정의

버퍼는 파티클 시스템이 생성될 때 sturcted buffer를 생성하게 됨


void ParticleSystem::FinalUpdate()
{
	...

	_particleBuffer->PushComputeUAVData(UAV_REGISTER::u0);
	_computeSharedBuffer->PushComputeUAVData(UAV_REGISTER::u1);

	...

	_computeMaterial->Dispatch(1, 1, 1);
}

finalupdate 함수에서 u0과 u1 레지스터에 파티클의 정보를 넣고

compute 쉐이더를 사용한 계산 요청


void ParticleSystem::Render()
{
	GetTransform()->PushData();

	_particleBuffer->PushGraphicsData(SRV_REGISTER::t9);
	
    ...
    
	_material->PushGraphicsData();

	_mesh->Render(_maxParticle);
}

실제 파티클의 렌더 시에 particle buffer에 계산된 compute 쉐이더의 계산 결과를

t9 레지스터에 입력하여 사용하게 된다.


mesh 클래스 수정

void Mesh::Render(uint32 instanceCount)
{
	//GRAPHICS_CMD_LIST->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	...
    
	GRAPHICS_CMD_LIST->DrawIndexedInstanced(_indexCount, instanceCount, 0, 0, 0);
}

메쉬가 파티클인 경우 점일 수도 있기 때문에

기존 메쉬가 무조건 triangle이라 설정한 구문을 삭제

렌더 시에 몇 개의 인스턴스를 만들지 파라미터로 받도록 수정


shader 클래스 수정

enum class SHADER_TYPE : uint8
{
	DEFERRED,
	FORWARD,
	LIGHTING,
	PARTICLE,
	COMPUTE,
};

struct ShaderInfo
{
	SHADER_TYPE shaderType = SHADER_TYPE::FORWARD;
	RASTERIZER_TYPE rasterizerType = RASTERIZER_TYPE::CULL_BACK;
	DEPTH_STENCIL_TYPE depthStencilType = DEPTH_STENCIL_TYPE::LESS;
	BLEND_TYPE blendType = BLEND_TYPE::DEFAULT;
	D3D_PRIMITIVE_TOPOLOGY topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
};

쉐이더 타입에 파티클을 추가

쉐이더 인포에 토폴로지 타입이 아닌 포톨로지로 수정


class Shader : public Object
{
public:
	...

	void CreateGraphicsShader(const wstring& path, ShaderInfo info = ShaderInfo(), const string& vs = "VS_Main", const string& ps = "PS_Main", const string& gs = "");
	
    ...

	static D3D12_PRIMITIVE_TOPOLOGY_TYPE GetTopologyType(D3D_PRIMITIVE_TOPOLOGY topology);
	
    ...
    
	void CreateGeometryShader(const wstring& path, const string& name, const string& version);
	
    ...
};

그래픽 쉐이더를 만들 때 지오메트리 쉐이더를 사용하는 경우 해당 함수 이름을 받도록 수정

토폴로지를 통하여 토폴로지 타입을 가지고 올 수 있는 함수 추가


void Shader::Update()
{
	...
    
	{
		GRAPHICS_CMD_LIST->IASetPrimitiveTopology(_info.topology);
		GRAPHICS_CMD_LIST->SetPipelineState(_pipelineState.Get());
	}
}

쉐이더 업데이트 시에 인풋 어셈블러에 해당 토폴로지 정보를 설정


class Camera : public Component
{
public:
	...

private:
	vector<shared_ptr<GameObject>>	_vecDeferred;
	vector<shared_ptr<GameObject>>	_vecForward;
	vector<shared_ptr<GameObject>>	_vecParticle;

	...
};

카메라에서 파티클을 렌더하기 위하여 벡터로 관리

void Camera::Render_Forward()
{
	...

	for (auto& gameObject : _vecParticle)
	{
		gameObject->GetParticleSystem()->Render();
	}
}

포워드 렌더 시 가장 마지막에 파티클들을 렌더

0개의 댓글