공부/Graphics, DirectX, 포트폴리오 구조

Particle 구현을 위한 Structured Buffer, Compute Shader 그리고 Geometry Shader

sudo 2022. 4. 1. 06:22

멀티쓰레드와 구조화버퍼를 이용해서 Compute Shader에서 파티클의 Alive 여부부터 Position, Color 등을 설정하고 Update할 수 있도록 했다. 아래 RWStructuredBuffer는 주석에서도 볼 수 있듯이 C++코드(CPU)에서가 아니라, 셰이더 코드(GPU)에서만 읽기/쓰기가 가능하게 하기 위해서 'RW'를 붙였고 C++코드에서 SRV(Shader Resource View)가 아니라 Unordered Access View(UAV)를 Set해줘야 한다.

 

[numthreads(64, 1, 1)] 이라고 쓰여진 부분은 3차원 Cube를 생각했을때 x, y, z방향으로 각각 64개, 1개, 1개의 그룹을 만든다는 의미이고 64개에 분포되어 들어갈 쓰레드의 수는 C++코드에서 Compute Shader의 Excute함수를 호출 할 때 인자로 넣어 줬을 것이다(여기선 Paticle의 Spawn Count가 될 것 이다)

// 읽기/쓰기 모두 가능한 구조화버퍼를 만들려면 앞에 RW를 붙여야한다. RW가 없으면 읽기 전용이다
// C++ 코드 CStructuredBuffer로 D3D11_BIND_UNORDERED_ACCESS Flag사용해서 만든 그 구조화버퍼다
// g_ParticleArray 구조체 배열안에 각각의 구조체를 각각의 파티클로 본다(1:1 매핑)
RWStructuredBuffer<ParticleInfo>		g_ParticleArray	: register(u0);
RWStructuredBuffer<ParticleInfoShared>	g_ParticleShare	: register(u1);

// SV_DispatchThreadID에서 제공하는 쓰레드ID는 그룹ID까지 고려해서 ID를 만든것이다
// dispatchThreadID.xyz = groupID.xyz * ThreadGroupSize.xyz + groupThreadID.xyz
// https://docs.microsoft.com/ko-kr/windows/win32/direct3dhlsl/sm5-attributes-numthreads 참고
// 즉, 아래에선 64개의 쓰레드 '그룹'이 형성될 것이고, C++코드(여기선 CParticleComponent::PostUpdate)에서 Excute(x,y,z)를 호출하면
// System Value인 ThreadID는 0 ~ x*y*z - 1 까지가 될 것이다. 결국 x*y*z개의 쓰레드가 64개의 그룹내에 자동적으로 분배될 것이다.
[numthreads(64, 1, 1)]	// 그룹내 스레드 수를 지정한다.
void ParticleUpdate(uint3 ThreadID : SV_DispatchThreadID)
{
...
}

 

Geometry Shader : 입력 받은 임의의 기본 도형을 임의로 모양을 변경할 수 있다. 예를 들어 아래 Geometry Shader 예시는 1개의 정점을 입력 받아서 셰이더에서는 2개의 삼각형으로 이루어진(즉, 6개의 정점으로 이루어진) 사각형을 출력한다

struct VertexParticleOutput
{
	uint InstanceID : TEXCOORD;
};

[maxvertexcount(6)] // 6개의 정점으로 이루어진 1개의 사각형을 출력할것이므로 6
void ParticleGS(point VertexParticleOutput input[1],
	inout TriangleStream<GeometryParticleOutput> output)
{
...
...
	// UV좌표 기준으로 (0,0)은 좌상단, (1,1)은 우하단이니까 그리고 아래에서 RestartStrip를 이용해서
	// 출력은 TriangleStrip으로 해야하지만 TriangleList처럼 출력하고 있으므로 원래 Mesh만들때 TriangleList처럼 
	// 0,1,3,0,3,2 이 순서대로 그려보면 결국 이거도 winding이 시계방향대로 된다
	OutputArray[0].UV = float2(0.f, 0.f);
	OutputArray[1].UV = float2(1.f, 0.f);
	OutputArray[2].UV = float2(0.f, 1.f);
	OutputArray[3].UV = float2(1.f, 1.f);
...
...
	output.Append(OutputArray[0]);
	output.Append(OutputArray[1]);
	output.Append(OutputArray[3]);
	output.RestartStrip();

	output.Append(OutputArray[0]);
	output.Append(OutputArray[3]);
	output.Append(OutputArray[2]);
	output.RestartStrip();
}

 

아래 필기는 나의 포트폴리오에서만 적용되는 구조라 다른 사람 공부 용도로는 적합하지 않을 듯 하다.

실제 구조화 버퍼의 Dynamic가 true/false인지에 따라 Dynamic이 false이면 UAV까지 만들어서 GPU에서만 읽고 쓸 수 있고, CPU에서는 접근 불가능하게 Description의 USAGE를 D3D11_USAGE_DEFAULT로 설정한 것을 확인할 수 있다.

bool CStructuredBuffer::Init(const std::string& Name, unsigned int Size, unsigned int Count, int Register,
	bool Dynamic, int StructuredBufferShaderType)
{
	SAFE_RELEASE(m_SRV);
	SAFE_RELEASE(m_UAV);
	SAFE_RELEASE(m_Buffer);

	m_Dynamic = Dynamic;
	m_Name = Name;
	m_Size = Size;
	m_Count = Count;
	m_Register = Register;
	m_StructuredBufferShaderType = StructuredBufferShaderType;

	m_Desc.ByteWidth = m_Size * m_Count;
	m_Desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
	m_Desc.StructureByteStride = m_Size;

	if (m_Dynamic)
	{
		m_Desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
		// Usage가 Dynamic-> CPU에서 쓰고 GPU에서 사용(ex. 상수버퍼, transform)
		m_Desc.Usage = D3D11_USAGE_DYNAMIC;
		m_Desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	}

	else
	{
		// RW용도로써, 셰이더로 보낼 수도 있고, 셰이더에서 저장한 데이터를 가져올 수 도 있게 한다
		m_Desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS;
		// Usage가 Default-> GPU의 읽기,쓰기만 가능하고 CPU는 접근 불가(ex. 렌더 타겟이 되는 백버퍼) 
		m_Desc.Usage = D3D11_USAGE_DEFAULT;
	}
	
	if (FAILED(CDevice::GetInst()->GetDevice()->CreateBuffer(&m_Desc, nullptr, &m_Buffer)))
		return false;

	D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc = {};

	SRVDesc.Format = DXGI_FORMAT_UNKNOWN;
	SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFEREX;
	SRVDesc.BufferEx.FirstElement = 0;
	SRVDesc.BufferEx.Flags = 0;
	SRVDesc.BufferEx.NumElements = m_Count;

	if (FAILED(CDevice::GetInst()->GetDevice()->CreateShaderResourceView(m_Buffer, &SRVDesc, &m_SRV)))
		return false;

	if (!m_Dynamic)
	{
		D3D11_UNORDERED_ACCESS_VIEW_DESC	UAVDesc = {};

		UAVDesc.Format = DXGI_FORMAT_UNKNOWN;
		UAVDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
		UAVDesc.Buffer.FirstElement = 0;
		UAVDesc.Buffer.Flags = 0;
		UAVDesc.Buffer.NumElements = m_Count;

		if (FAILED(CDevice::GetInst()->GetDevice()->CreateUnorderedAccessView(m_Buffer, &UAVDesc, &m_UAV)))
			return false;
	}

	return true;
}

 

 

Reference 

https://docs.microsoft.com/ko-kr/windows/win32/direct3dhlsl/sm5-attributes-numthreads

 

numthreads - Win32 apps

계산 셰이더가 디스패치 될 때 단일 스레드 그룹에서 실행할 스레드 수를 정의 합니다 (ID3D11DeviceContext 디스패치 참조).

docs.microsoft.com