멀티쓰레드와 구조화버퍼를 이용해서 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
'공부 > Graphics, DirectX, 포트폴리오 구조' 카테고리의 다른 글
디퍼드 렌더링(Deferred Rendering)에서 반투명한 물체를 위한 블렌딩처리가 안되는 이유 (0) | 2022.05.24 |
---|---|
렌더링 파이프라인(Rendering Pipeline) (0) | 2022.04.10 |
Component, Mesh, Matarial간의 관계와 렌더링 과정 요약 필기 (0) | 2022.03.30 |
Texture, Render Target 그리고 Surface란? (2) | 2022.01.11 |
[포트폴리오 구조] AlphaBlend 적용 방법 (0) | 2021.12.22 |