04
26

기본적으로 맵에 있는 텍스쳐 한 장, 멀티 텍스쳐를 적용할 4개의 텍스쳐를 준비한다.

왜 4개로 레이어를 구성하냐면, 한 개의 텍스쳐에서

RGBA값으로 각각의 텍스쳐의 비율을 담아 놓기 때문이다.

그 텍스쳐는 멀티 마스크 텍스쳐 역활을 한다.  

추가적으로 텍스쳐를 붙이고 싶다면, 추가적인 마스크 텍스쳐를 만들면 된다.

 

마스크 텍스쳐에 따라 적용되는 멀티 텍스쳐링

 

맵툴에 적용하기 위해 브러시를 사용해서 마스크 텍스쳐를 수정해야 한다.

여러 방법이 있겠지만, Compute Shader를 사용하면 GPU를 사용해

브러시 연산을 빠르게 처리할 수 있다. (구조체 상수버퍼로 넘겨서 연산)

또한, 마스크 텍스쳐도 원본 이미지, 복사될 이미지를 받아

수정을 한뒤에 다시 UNMAP하여, 누적되게 텍스쳐에 그릴 수 있게 구현한다.

 

KMapSprite.cpp

bool KMapSprite::Init(ID3D11DeviceContext* pContext, KMap* pMap)
{
	if (pMap == nullptr)return false;
	if (pContext == nullptr)return false;
	//브러쉬 버퍼
	m_pMap = pMap;
	m_pContext = pContext;
	m_Pickbuffer.fRadius = 20.0f;
	m_Pickbuffer.iIndex = 0;
	m_Pickbuffer.vPickPos = KVector3(0, 0, 0);
	m_Pickbuffer.vRect[0] = KVector3(-0, 0, 0);
	m_Pickbuffer.vRect[1] = KVector3(0, 0, 0);
	m_Pickbuffer.vRect[2] = KVector3(0, -0, 0);
	m_Pickbuffer.vRect[3] = KVector3(-0, 0, 0);
	//
	//Structed Buffer, SRV 생성 : cs에 보내는 버퍼
	CreateStructuredBuffer(g_pd3dDevice, sizeof(PICKBUFFER), 1, &m_Pickbuffer, m_pPickBuffer.GetAddressOf());
	CreateBufferSRV(g_pd3dDevice, m_pPickBuffer.Get(), m_pPickBufferSRV.GetAddressOf());
	//unorder access view 생성 : SRV 순서와 상관없이 다른 리소스에 대한 읽기 및 쓰기 허용
	CreateBufferUAV(g_pd3dDevice, m_pMap->m_BoxCollision.size.x, m_pMap->m_BoxCollision.size.z, m_pResultUAV.GetAddressOf());
	//CS 쉐이더 생성

	KShader* pCS = g_ShaderManager.CreateComputeShader(L"../../data/shader/CS_Terrian.hlsl");
	m_pCS = pCS->m_pComputeShader;

	//기존텍스쳐가 아닌 맵의 텍스쳐를 복사되어지는 텍스쳐로 바꾼다.
	pMap->m_pMapAlphaResultSRV = m_pTextureCopySRV.Get();
	return true;
}

 

맵 스프라이팅 클래스의 초기화 함수이다. 

1. CreateStructedBuffer()와 CreateBufferSRV()는 CS에 보낼 구조체 버퍼를 만드는 함수로

구조체의 사이즈 맞게 버퍼를 생성

2. CreateBufferUAV() Unorder Access View 버퍼를 생성하는 함수로 

순서와 상관없이 다른 리소스에 대한 읽기 및 쓰기를 허용하는 버퍼 생성,

3. ComputeShader를 생성한다.

 

KMapSprite::CreateBufferUAV()

HRESULT KMapSprite::CreateBufferUAV(ID3D11Device* pDevice, int iWidth, int iHeight, ID3D11UnorderedAccessView** ppUAVOut)
{
	HRESULT hr = S_OK;
	//before dispatch
	D3D11_TEXTURE2D_DESC textureDesc;
	ZeroMemory(&textureDesc, sizeof(textureDesc));
	textureDesc.Width = iWidth;
	textureDesc.Height = iHeight;
	textureDesc.MipLevels = 1;
	textureDesc.ArraySize = 1;
	textureDesc.SampleDesc.Count = 1;
	textureDesc.SampleDesc.Quality = 0;
	textureDesc.Usage = D3D11_USAGE_DEFAULT;
	textureDesc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
	textureDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; DXGI_FORMAT_R32G32B32A32_FLOAT;
	hr = pDevice->CreateTexture2D(&textureDesc, NULL, m_pTexture.GetAddressOf());

	D3D11_UNORDERED_ACCESS_VIEW_DESC viewDescUAV;
	ZeroMemory(&viewDescUAV, sizeof(viewDescUAV));
	viewDescUAV.Format = textureDesc.Format;
	viewDescUAV.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D;
	viewDescUAV.Texture2D.MipSlice = 0;
	hr = pDevice->CreateUnorderedAccessView(m_pTexture.Get(), &viewDescUAV, ppUAVOut);

	D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
	ZeroMemory(&srvDesc, sizeof(srvDesc));
	srvDesc.Format = textureDesc.Format;
	srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
	srvDesc.Texture2D.MipLevels = 1;
	hr = pDevice->CreateShaderResourceView(m_pTexture.Get(), &srvDesc, m_pTextureSRV.GetAddressOf());

	// 복사본
	hr = pDevice->CreateTexture2D(&textureDesc, NULL, m_pTextureCopy.GetAddressOf());
	hr = pDevice->CreateShaderResourceView(m_pTextureCopy.Get(), &srvDesc, m_pTextureCopySRV.GetAddressOf());
	return hr;
}

 

텍스쳐의 BindFlags를 D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE로 설정한다.

CreateUnorderedAccessView()로 m_pTexture를 UnorderedAccessView 버퍼로 만들며,

이 텍스쳐가 수정이 되면 복사되어 m_pTextureCopy를 얻고 이를 가지고 맵이 이 텍스쳐를 사용하면 된다.

pMap->m_pMapAlphaResultSRV = m_pTextureCopySRV.Get();

 

그러면 마우스가 눌렀을 때 컴퓨트 쉐이더를 수행하여 브러시에 맞게 그려지게 하면 된다.

매프레임 마다 호출되는 Frame()함수이다.

bool KMapSprite::Frame()
{
	if (g_InputData.bMouseState[0])
	{
		m_pContext->UpdateSubresource(m_pPickBuffer.Get(), 0, NULL, &m_Pickbuffer, 0, 0);
		//맵의 텍스쳐를 복사해온다.
		ID3D11ShaderResourceView* aRViews[3] = { m_pMap->m_pTexture_Diffuse->m_pSRVTexture.Get(), m_pTextureCopySRV.Get(), m_pPickBufferSRV.Get()};
		RunComputeShader(m_pContext, m_pCS.Get(), 3, aRViews, NULL, NULL, 0,
			m_pResultUAV.GetAddressOf(),
			m_pMap->m_BoxCollision.size.x/32, m_pMap->m_BoxCollision.size.z / 32, 1);

		m_pContext->CopyResource(m_pTextureCopy.Get(), m_pTexture.Get());
	}
	return true;
}

 

아래는 ComputeShader 수행 함수이다.

void KMapSprite::RunComputeShader(ID3D11DeviceContext* pContext, ID3D11ComputeShader* pComputeShader, UINT nNumViews, ID3D11ShaderResourceView** pShaderResourceViews, ID3D11Buffer* pCBCS, void* pCSData, DWORD dwNumDataBytes, ID3D11UnorderedAccessView** pUnorderedAccessView, UINT X, UINT Y, UINT Z)
{
	pContext->CSSetShader(pComputeShader, NULL, 0);
	pContext->CSSetShaderResources(0, nNumViews, pShaderResourceViews);
	pContext->CSSetUnorderedAccessViews(0, 1, pUnorderedAccessView, NULL);
	if (pCBCS)
	{
		D3D11_MAPPED_SUBRESOURCE MappedResource;
		pContext->Map(pCBCS, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedResource);
		memcpy(MappedResource.pData, pCSData, dwNumDataBytes);
		pContext->Unmap(pCBCS, 0);
		ID3D11Buffer* ppCB[1] = { pCBCS };
		pContext->CSSetConstantBuffers(0, 1, ppCB);
	}

	pContext->Dispatch(X, Y, Z);

	pContext->CSSetShader(NULL, NULL, 0);

	//다시 널로 바꿔주는 작업
	ID3D11UnorderedAccessView* ppUAViewNULL[1] = { NULL };
	pContext->CSSetUnorderedAccessViews(0, 1, ppUAViewNULL, NULL);

	ID3D11ShaderResourceView* ppSRVNULL[2] = { NULL, NULL };
	pContext->CSSetShaderResources(0, 2, ppSRVNULL);

	ID3D11Buffer* ppCBNULL[1] = { NULL };
	pContext->CSSetConstantBuffers(0, 1, ppCBNULL);
}

기존 텍스쳐 복사 텍스쳐, 구조체 상수 버퍼, UAV 버퍼를 설정하고

Map은 인터락, 크리티컬 섹션처럼 공유 자원의 독점, 안전하게 수행할 수 있다.

 

CS_Terrian.hlsl

Texture2D<float4>		InputMap : register(t0);
Texture2D<float4>		CopyMap : register(t1);
RWTexture2D<float4>		OutputMap : register(u0);

struct CBuf_Brush
{
	float3 vPickPos;
	float3 vRect[4];
	float  g_fRadius;
	int    iIndex;
};
StructuredBuffer<CBuf_Brush> Buffer0 : register(t2);
// Group size
#define size_x 32
#define size_y 32
[numthreads(size_x, size_y, 1)]
void CS(uint3 GroupID : SV_GroupID, uint3 DispatchThreadID : SV_DispatchThreadID, uint3 GroupThreadID : SV_GroupThreadID, uint GroupIndex : SV_GroupIndex)
{
	int3 texturelocation = int3(0, 0, 0);
	// 0 ~ 1024, 1024
	texturelocation.x = GroupID.x * size_x + GroupThreadID.x; // u
	texturelocation.y = GroupID.y * size_y + GroupThreadID.y; // v
	//texturelocation.x = DispatchThreadID.x; //위랑 같음
	//texturelocation.y = DispatchThreadID.y;

	float4 Color = InputMap.Load(texturelocation);
	// 0 ~1 
	float2 uv = float2(texturelocation.x / 1280.0f, //현재 지형 크기 1280 고정
		texturelocation.y / 1280.0f);
	// vRect[0]   ~   vRect[1]  
	float1 width = (Buffer0[0].vRect[1].x - Buffer0[0].vRect[0].x) / 2.0f;
	//
	// vRect[3]   ~   vRect[2]  
	float1 height = (Buffer0[0].vRect[0].y - Buffer0[0].vRect[3].y) / 2.0f;
	float3 vPos = float3((uv.x * 2 - 1.0f) * width,
		-(uv.y * 2 - 1.0f) * height,
		0.0f);

	float fRadius = distance(vPos.xyz, Buffer0[0].vPickPos.xyz);
	//텍스쳐 복사한것
	float4 fAlpha = CopyMap.Load(texturelocation);
	float fDot = 1.0f - (fRadius / Buffer0[0].g_fRadius);

	//4개의 텍스쳐의 알파값을 저장한 텍스쳐
	switch (Buffer0[0].iIndex)
	{
		case 0: fAlpha.x = max(fAlpha.x, fDot); break;
		case 1: fAlpha.y = max(fAlpha.y, fDot); break;
		case 2: fAlpha.z = max(fAlpha.z, fDot); break;
		case 3: fAlpha.w = max(fAlpha.w, fDot); break;
	}
	OutputMap[texturelocation.xy] = float4(fAlpha.xyzw);
}

 

상수 버퍼로 받아온 브러시의 형태에 따라, 원 영역을 계산하고

브러쉬의 iIndex에 따라 스위치 조건문으로 각 색 영역에 알파 값을 넣는다.

그리고 최종적으로 완성된 출력된 리소스를 쓴다. 

 

VSPS_Terrain.hlsl

  float4 albedo = g_txDiffuse.Sample(g_Sample, Input.t * 4); //알베도 기본 색상 텍스쳐
   float4 mapMask = g_txMapMask.Sample(g_Sample, Input.t ); // 맵 마스크 작업

   float4 subTexture1 = g_txSubTex1.Sample(g_Sample, Input.t * 4); // 맵 서브 텍스쳐
   float4 subTexture2 = g_txSubTex2.Sample(g_Sample, Input.t * 4); // 맵 서브 텍스쳐
   float4 subTexture3 = g_txSubTex3.Sample(g_Sample, Input.t * 4); // 맵 서브 텍스쳐
   float4 subTexture4 = g_txSubTex4.Sample(g_Sample, Input.t * 4); // 맵 서브 텍스쳐
   
   albedo = lerp(albedo, subTexture1, mapMask.r); // 첫번째 레이어 작업
   albedo = lerp(albedo, subTexture2, mapMask.g); // 두번째 레이어 작업
   albedo = lerp(albedo, subTexture3, mapMask.b); // 세번째 레이어 작업
   albedo = lerp(albedo, subTexture4, mapMask.a); // 네번째 레이어 작업

 

맵의 쉐이더의 한 부분이다. 

각각 텍스쳐를 불러오고, 마스크 텍스쳐에 적용된 RGBA 값을 활용해

기본 텍스쳐 알베도 위에 색을 Lerp() 내장함수로 보간한다. 

 

결과

 

왼쪽 지형 최종 텍스쳐, 오른쪽 마스크 텍스쳐

 

COMMENT