[DirectX] ComputeShader


UAV(Unordered Access View)


읽기와 쓰기가 동시에 되는 레지스터가 UAV이다.


SetColor.fx (test 쉐이더 코드)


#ifndef _SETCOLOR
#define _SETCOLOR

#include "value.fx"

RWTexture2D<float4> g_Output : register(u0);

[numthreads(1, 1, 1)] // 스레드 그룹 개수 (1 * 1 * 1)
// 만약에 2, 2, 2가 되었다면 스레드 그룹 개수는 8 (2 * 2 * 2)개.
void CS_SetColor(int3 _ID : SV_DispatchThreadID)
{
    g_Output[0] = float4(1.f, 0.f, 0.f, 1.f);
}

#endif

u0, 즉 u로 시작하는 레지스터 번호가 읽기와 쓰기가 동시에 되는 레지스터 번호이다.
그리고 선언할 때에도 기존 레지스터의 선언방식과는 달리, RW를 앞에 붙이고, 뒤에는 픽셀 포맷을 적어줘야한다.

RWTexture2D<float4> g_Output : register(u0);

스레드의 개수는 위에 나온 코드와 같이 numthreads(a, b, c)에서 a, b, c의 값을 조정하여 조절할 수 있으며, a * b * c의 값이 호출되는 스레드 그룹의 개수이다.

HLSL 5.0기준, 최대 호출 가능 개수는 1024개이다.

ID값 활용하기


쓰레드마다 자신이 받아오는 _ID 값이 다르며, 이를 활용해서 각 쓰레드가 다른 픽셀을 칠하도록 만들어야 할 것이다.


ComputeShader 입력 Sematic


  • SV_GroupThreadID : 그룹 내에서의 인덱스 좌표
  • SV_GroupID : 스레드가 몇번째 그룹에 속해있는가?
  • SV_GroupIndex : 모든 스레드 그룹을 나열했을 때 자신의 위치
  • SV_DispatchThreadID : 그룹 내에서 (0,0,0) 으로부터 몇 번째 스레드인가?


예시 Dispatch : (5, 3, 2), ThreadGroup : (10, 8, 3)


  • SV_GroupThreadID : (7, 5, 0)
  • SV_GroupID : (2, 1, 0)
  • SV_GroupIndex : ((2,1,0) * (10, 8, 3)) + (7, 5, 0) = (27, 13, 0)
  • SV_DispatchThreadID : (0 * 10 * 8 + 5 * 10 + 7) = 57
CONTEXT->Dispatch(x, y, z);

위의 코드에서 x, y, z의 값을 지정해서 스레드 그룹의 개수를 지정할 수 있다.

#ifndef _SETCOLOR
#define _SETCOLOR

#include "value.fx"

RWTexture2D<float4> g_Output : register(u0);

[numthreads(32, 32, 1)]
void CS_SetColor(int3 _ID : SV_DispatchThreadID)
{
    g_Output[_ID.xy] = float4(1.f, 0.f, 0.f, 1.f);
}

#endif

스레드 그룹의 개수는 1024(32 * 32 * 1 구성)개, 색칠해야하는 픽셀의 개수는 1280 * 768개 일때?

거로로 40개, 세로로 24개가 필요하므로

CONTEXT->Dispatch(40, 24, 1);

을 지정하면 된다.

그리고 자신의 담당픽셀을 칠하기 위해서

g_Output[_ID.xy]

fx파일에서 이렇게 작성해서 매칭시킨다.

ComputeShader 클래스의 구성


// 헤더파일

#pragma once
#include "CShader.h"

class CComputeShader :
    public CShader
{
private:
    ComPtr<ID3D11ComputeShader>     m_CS;
    ComPtr<ID3DBlob>                m_CSBlob;

public:
    void CreateComputeShader(const wstring& _strFileName, const string& _strFuncName);

    void Dispatch(UINT _X, UINT _Y, UINT _Z);
    virtual void UpdateData();
    virtual void Clear();


    CLONE_DISABLE(CComputeShader);
public:
    CComputeShader();
    ~CComputeShader();
};
// cpp 파일

#include "pch.h"
#include "CComputeShader.h"

#include "CPathMgr.h"
#include "CDevice.h"


CComputeShader::CComputeShader()
	: CShader(RES_TYPE::COMPUTE_SHADER)
{
}

CComputeShader::~CComputeShader()
{
}

void CComputeShader::CreateComputeShader(const wstring& _strFileName, const string& _strFuncName)
{
	// Shader 파일 경로
	wstring strShaderFile = CPathMgr::GetInst()->GetContentPath();
	strShaderFile += _strFileName;

	// Shader Compile	
	if (FAILED(D3DCompileFromFile(strShaderFile.c_str(), nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE
		, _strFuncName.c_str(), "cs_5_0", 0, 0, m_CSBlob.GetAddressOf(), m_ErrBlob.GetAddressOf())))
	{
		MessageBoxA(nullptr, (const char*)m_ErrBlob->GetBufferPointer()
			, "Compute Shader Compile Failed!!", MB_OK);
	}

	// 컴파일된 객체로 Shader 를 만든다.
	DEVICE->CreateComputeShader(m_CSBlob->GetBufferPointer(), m_CSBlob->GetBufferSize()
		, nullptr, m_CS.GetAddressOf());
}

void CComputeShader::Dispatch(UINT _X, UINT _Y, UINT _Z)
{
	CONTEXT->CSSetShader(m_CS.Get(), nullptr, 0);
	CONTEXT->Dispatch(_X, _Y, _Z);
}

void CComputeShader::UpdateData()
{	
	
}

void CComputeShader::Clear()
{
}


ResMgr에서 컴퓨트 쉐이더 추가하기

// CResMgr.cpp 중에서 CreateDefaultComputeShader함수

void CResMgr::CreateDefaultComputeShader()
{
	Ptr<CComputeShader> pCS = nullptr;

	// Texture 색상 변경 쉐이더
	pCS = new CComputeShader;
	pCS->SetKey(L"SetColorCS");
	pCS->CreateComputeShader(L"shader\\setcolor.fx", "CS_SetColor");
	AddRes(pCS->GetKey(), pCS);
}

UpdateData_CS함수


파이프라인 스테이지로 보내는 것이 아니라 UAV를 사용하도록 새로운 함수를 생성해준다.

// CTexture.cpp 파일 중에서

void CTexture::UpdateData_CS(int _iRegisterNum)
{
	m_iRecentCSNum = _iRegisterNum;

	UINT i = -1;
	CONTEXT->CSSetUnorderedAccessViews(_iRegisterNum, 1, m_UAV.GetAddressOf(), &i);
}


적용해보기

// CLevelMgr.cpp 중에서 init 함수

    // 텍스쳐 색칠하기
	// 텍스쳐 생성(UnorderedAccess)
	Ptr<CTexture> pCreateTex = CResMgr::GetInst()->CreateTexture(
								L"SampleTexture"
								, 1280, 768
								, DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM
								, D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS
								, D3D11_USAGE_DEFAULT);		 
	
	// U0 에 바인딩
	pCreateTex->UpdateData_CS(0);

	// ComputeShader 로 텍스쳐 색 변경하기
	Ptr<CComputeShader> pCS = CResMgr::GetInst()->FindRes<CComputeShader>(L"SetColorCS");
	pCS->Dispatch(pCreateTex->Width() / 32, pCreateTex->Height() / 32, 1);

	// U0 에 바인딩 된 텍스쳐 해제
	pCreateTex->Clear();

그리고 바인딩 해제를 위한 Clear 함수 구현

// CTexture.cpp 파일 중에서

void CTexture::Clear()
{
	ID3D11UnorderedAccessView* pUAV = nullptr;
	UINT i = -1;
	CONTEXT->CSSetUnorderedAccessViews(m_iRecentCSNum, 1, &pUAV, &i);
}


C++ 클래스와 연동


일일이 셰이더 코드를 호출할 때 마다 스레드 개수를 일일이 계산해줄 필요가 없도록 하고 싶다.

이를 위해서 setcolor 셰이더코드를 C++ 클래스화 시킨다면?

CComputeShader 클래스의 재구성


// 헤더파일

#pragma once
#include "CShader.h"

class CComputeShader :
    public CShader
{
private:
    ComPtr<ID3D11ComputeShader>     m_CS;
    ComPtr<ID3DBlob>                m_CSBlob;    

protected:
    // 쉐이더에 전달할 상수 데이터
    tMtrlConst                      m_Const;

    // 그룹 개수
    UINT                            m_iGroupX; 
    UINT                            m_iGroupY;
    UINT                            m_iGroupZ;

    // 그룹 1개당 스레드 개수
    UINT                            m_iGroupPerThreadX;
    UINT                            m_iGroupPerThreadY;
    UINT                            m_iGroupPerThreadZ;

public:
    void CreateComputeShader(const wstring& _strFileName, const string& _strFuncName);
    void Execute();

private:
    virtual void UpdateData() = 0;
    virtual void Clear() = 0;


    CLONE_DISABLE(CComputeShader);
public:
    CComputeShader();
    ~CComputeShader();
};



// cpp 파일

#include "pch.h"
#include "CComputeShader.h"

#include "CPathMgr.h"
#include "CDevice.h"
#include "CConstBuffer.h"


CComputeShader::CComputeShader()
	: CShader(RES_TYPE::COMPUTE_SHADER)
	, m_iGroupX(1)
	, m_iGroupY(1)	
	, m_iGroupZ(1)
{
}

CComputeShader::~CComputeShader()
{
}

void CComputeShader::CreateComputeShader(const wstring& _strFileName, const string& _strFuncName)
{
	// Shader 파일 경로
	wstring strShaderFile = CPathMgr::GetInst()->GetContentPath();
	strShaderFile += _strFileName;

	// Shader Compile	
	if (FAILED(D3DCompileFromFile(strShaderFile.c_str(), nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE
		, _strFuncName.c_str(), "cs_5_0", 0, 0, m_CSBlob.GetAddressOf(), m_ErrBlob.GetAddressOf())))
	{
		MessageBoxA(nullptr, (const char*)m_ErrBlob->GetBufferPointer()
			, "Compute Shader Compile Failed!!", MB_OK);
	}

	// 컴파일된 객체로 Shader 를 만든다.
	DEVICE->CreateComputeShader(m_CSBlob->GetBufferPointer(), m_CSBlob->GetBufferSize()
		, nullptr, m_CS.GetAddressOf());
}

void CComputeShader::Execute()
{
	UpdateData();

	static CConstBuffer* pCB = CDevice::GetInst()->GetConstBuffer(CB_TYPE::MATERIAL);
	pCB->SetData(&m_Const);
	pCB->UpdateData_CS();

	CONTEXT->CSSetShader(m_CS.Get(), nullptr, 0);
	CONTEXT->Dispatch(m_iGroupX, m_iGroupY, m_iGroupZ);

	Clear();
}


UpdataData와 Clear함수를 가상함수화 시켜서

자식클래스가 사용할 수 있는 추상클래스로 만들어준다.

CSetColorShader 클래스의 구성


// 헤더 파일

#pragma once
#include "CComputeShader.h"

#include "ptr.h"
#include "CTexture.h"

class CSetColorShader :
    public CComputeShader
{
private:
    Ptr<CTexture>   m_OutTex;    

public:
    void SetTargetTexture(Ptr<CTexture> _Tex) {m_OutTex = _Tex; }
    void SetColor(Vec3 _RGB) { m_Const.arrV4[0] = _RGB; }

public:
    virtual void UpdateData() override;
    virtual void Clear() override;

public:
    CSetColorShader(UINT _iGroupPerThreadX, UINT _iGroupPerThreadY, UINT _iGroupPerThreadZ);
    ~CSetColorShader();
};


// cpp 파일

#include "pch.h"
#include "CSetColorShader.h"

#include "CTexture.h"

CSetColorShader::CSetColorShader(UINT _iGroupPerThreadX, UINT _iGroupPerThreadY, UINT _iGroupPerThreadZ)
{
	m_iGroupPerThreadX = _iGroupPerThreadX;
	m_iGroupPerThreadY = _iGroupPerThreadY;
	m_iGroupPerThreadZ = _iGroupPerThreadZ;
}

CSetColorShader::~CSetColorShader()
{
}

void CSetColorShader::UpdateData()
{
	m_OutTex->UpdateData_CS(0);

	// 그룹 개수 계산
	m_iGroupX = m_OutTex->Width() / m_iGroupPerThreadX;
	m_iGroupY = m_OutTex->Height() / m_iGroupPerThreadY;
	m_iGroupZ = 1;
}

void CSetColorShader::Clear()
{
	m_OutTex->Clear();
}

CComputeShader 클래스를 부모 클래스로 설정하고

  • UpdateData
  • Clear

두 함수를 CComputeShader에선 가상함수로 설정하고, 자식 클래스에서 오버라이딩해서 구현해서 사용한다.

m_OutTex는 색칠할 텍스쳐, 출력 텍스쳐를 받는다.

UpdateData가 호출될 때는 CComputeShader에서 Execute함수가 호출 되었을 때 호출된다.

그리고 자식의 UpdataData 함수에서 해상도에 따른 그룹 개수 계산을 해준다.

원하는 색을 칠하도록 만들기


지금까지는 오직 한 가지의 색상만 칠할 수 있었는데, 어떻게 하면 원하는 색상을 골라서 칠할 수 있을까?

원하는 색상을 상수버퍼가 들고있는 재질을 통해서 전달해주는 점을 주목해서 ComputeShader에다가 재질에 관련된 변수를 추가해주면 된다.

그리고 자식클래스에서 SetColor함수를 설정해서 원하는 값의 RGB가 설정되도록 만들면 된다.

// CComputeShader의 Execute 함수 중에서

static CConstBuffer* pCB = CDevice::GetInst()->GetConstBuffer(CB_TYPE::MATERIAL);

상수버퍼를 불러와서 상수값 혹은 색깔 데이터를 세팅한다.

그러고나서 자식 클래스를 본격적으로 실행시킨다.

한편, setcolor.fx 파일에서 함수는 아래와 같이 바꾼다.


#include "value.fx"



// ComputeShader 입력 Sematic
// SV_GroupThreadID      : 그룹 내에서의 인덱스(3차원)
// SV_GroupID            : 그룹 인덱스
// SV_GroupIndex         : 그룹 내에서의 인덱스(1차원)
// SV_DispatchThreadID   : 전체 그룹 기준, 스레드의 인덱스(3차원)

RWTexture2D<float4> g_Output : register(u0);
#define Color       g_vec4_0 

// 스레드 그룹 개수(HLSL 5.0 기준 1024 제한)
[numthreads(32, 32, 1)]
void CS_SetColor(int3 _ID : SV_DispatchThreadID)
{    
    g_Output[_ID.xy] = float4(Color.xyz, 1.f);
}



#endif


적용해보기


// CLevelMgr.cpp init 함수 중에서

	// 텍스쳐 색칠하기
	// 텍스쳐 생성(UnorderedAccess)
	Ptr<CTexture> pCreateTex = CResMgr::GetInst()->CreateTexture(
								L"SampleTexture"
								, 1280, 768
								, DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM
								, D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS
								, D3D11_USAGE_DEFAULT);	
	
	// ComputeShader 로 텍스쳐 색 변경하기
	Ptr<CSetColorShader> pCS = (CSetColorShader*)CResMgr::GetInst()->FindRes<CComputeShader>(L"SetColorCS").Get();
	pCS->SetTargetTexture(pCreateTex);
	pCS->SetColor(Vec3(0.f, 1.f, 1.f));
	pCS->Execute();



© 2022.07. by Wookey_Kim

Powered by Hydejack v7.5.2