[DirectX] 타일맵(Tile Map)


타일조각들이 모여서 맵을 형성한다.

CTilemap 클래스의 구성


// 헤더파일

#pragma once

#include "CRenderComponent.h"

class CStructuredBuffer;

class CTileMap :
    public CRenderComponent
{
private:
    UINT                m_iTileCountX;  // 타일 가로
    UINT                m_iTileCountY;  // 타일 세로

    Vec2                m_vSliceSize;   // 타일 하나의 크기(UV 단위)


    vector<tTile>       m_vecTile;
    CStructuredBuffer*  m_Buffer;

public:
    virtual void finaltick() override;
    virtual void render() override;

    void UpdateData();

    void SetTileCount(UINT _iXCount, UINT _iYCount);
    void SetSliceSize(Vec2 _vSliceSize)  { m_vSliceSize = _vSliceSize; }

    CLONE(CTileMap);
public:
    CTileMap();
    ~CTileMap();
};



// cpp 파일

#include "pch.h"
#include "CTileMap.h"

#include "CResMgr.h"
#include "CTransform.h"

#include "CStructuredBuffer.h"

CTileMap::CTileMap()
	: CRenderComponent(COMPONENT_TYPE::TILEMAP)
	, m_iTileCountX(1)
	, m_iTileCountY(1)
{	
	SetMesh(CResMgr::GetInst()->FindRes<CMesh>(L"RectMesh"));
	SetMaterial(CResMgr::GetInst()->FindRes<CMaterial>(L"TileMapMtrl"));

	m_Buffer = new CStructuredBuffer;
	m_Buffer->Create(sizeof(tTile), m_iTileCountX * m_iTileCountY);
}

CTileMap::~CTileMap()
{
	if (nullptr != m_Buffer)
		delete m_Buffer;
}

void CTileMap::finaltick()
{
}

void CTileMap::render()
{
	if (nullptr == GetMesh() || nullptr == GetMaterial())
		return;

	// Transform 에 UpdateData 요청
	Transform()->UpdateData();

	// 재질 업데이트
	GetMaterial()->SetScalarParam(INT_0, &m_iTileCountX);
	GetMaterial()->SetScalarParam(INT_1, &m_iTileCountY);
	GetMaterial()->UpdateData();

	// 구조화버퍼 업데이트
	UpdateData();

	// 렌더
	GetMesh()->render();
}

void CTileMap::UpdateData()
{
	m_Buffer->SetData(m_vecTile.data(), sizeof(tTile) * m_vecTile.size());
	m_Buffer->UpdateData(20, PIPELINE_STAGE::PS_PIXEL);
}

void CTileMap::SetTileCount(UINT _iXCount, UINT _iYCount)
{
	m_iTileCountX = _iXCount;
	m_iTileCountY = _iYCount;

	m_vecTile.clear();
	m_vecTile.resize(m_iTileCountX * m_iTileCountY);

	if (m_Buffer->GetElementCount() < m_vecTile.size())
	{
		m_Buffer->Create(sizeof(tTile), (UINT)m_vecTile.size());
	}

	// 타일 세팅 테스트
	for (size_t i = 0; i < m_iTileCountY; ++i)
	{
		for (size_t j = 0; j < m_iTileCountX; ++j)
		{			
			m_vecTile[i * m_iTileCountX + j].vLeftTop.x = m_vSliceSize.x * j;
			m_vecTile[i * m_iTileCountX + j].vLeftTop.y = 0.f;
			m_vecTile[i * m_iTileCountX + j].vSlice = m_vSliceSize;
		}
	}

	m_vecTile[0].vLeftTop = Vec2(m_vSliceSize.x * 7.f, m_vSliceSize.y * 5.f);
}



CGameObject 클래스의 구성


그리고 새로운 컴포넌트가 나왔으므로 GameObject에서도 Tilemap을 연동을 해준다.

// 헤더파일

public:
    void AddComponent(CComponent* _Component);
    void AddChild(CGameObject* _Object);

    CComponent* GetComponent(COMPONENT_TYPE _ComType) { return m_arrCom[(UINT)_ComType]; }
    const vector<CGameObject*> GetChild() { return m_vecChild; }

    CGameObject* GetParent() const { return m_Parent; }

    GET_COMPONENT(Transform, TRANSFORM);
    GET_COMPONENT(MeshRender, MESHRENDER);
    GET_COMPONENT(Camera, CAMERA);
    GET_COMPONENT(Collider2D, COLLIDER2D);
    GET_COMPONENT(Light2D, LIGHT2D);
    GET_COMPONENT(TileMap, TILEMAP);

    CRenderComponent* GetRenderComponent() const {  return m_RenderCom; }


CLevelMgr.cpp 중에서


그리고 레벨 매니저의 init 함수에서 타일맵 역할을 할 오브젝트를 추가해준다.


	// TileMap Object
	CGameObject* pTileMap = new CGameObject;

	pTileMap->AddComponent(new CTransform);
	pTileMap->AddComponent(new CTileMap);

	pTileMap->Transform()->SetRelativePos(Vec3(0.f, 0.f, 600.f));
	pTileMap->Transform()->SetRelativeScale(Vec3(500.f, 500.f, 1.f));

	pTileMap->TileMap()->GetMaterial()->SetTexParam(TEX_0, CResMgr::GetInst()->FindRes<CTexture>(L"TileAtlasTex"));

	m_pCurLevel->AddGameObject(pTileMap, L"Tile", false);


TileMap 전용 셰이더 파일 구성


타일맵이 쓸 HLSL파일을 새로 만들어준다.

#ifndef _TILEMAP
#define _TILEMAP

#include "value.fx"
#include "func.fx"


struct tTile
{
    float2 vLeftTop;
    float2 vSlice;
};

// ============================
// TileMap Shader
// 
// RS_TYPE : CULL_NONE
// DS_TYPE : LESS
// BS_TYPE : MASK

// Parameter
// g_int_0 : Tile X Count
// g_int_1 : Tile Y Count
// g_tex_0 : Tile Atlas Texture
StructuredBuffer<tTile> TileBuffer : register(t20);
// ============================
struct VTX_TILEMAP_IN
{
    float3 vPos : POSITION;
    float2 vUV : TEXCOORD;
};

struct VTX_TILEMAP_OUT
{
    float4 vPosition : SV_Position;
    float2 vUV : TEXCOORD;
};

VTX_TILEMAP_OUT VS_TileMap(VTX_TILEMAP_IN _in)
{
    VTX_TILEMAP_OUT output = (VTX_TILEMAP_OUT) 0.f;
    
    output.vPosition = mul(float4(_in.vPos, 1.f), g_matWVP);
    output.vUV = _in.vUV * float2(g_int_0, g_int_1); // 전체 UV 를 타일 개수만큼 확장 
    
    return output;
}

float4 PS_TileMap(VTX_TILEMAP_OUT _in) : SV_Target
{
    float4 vOutColor = float4(1.f, 0.f, 1.f, 1.f);
    
    // 소수파트, frac(_in.vUV) : 타일 한칸 내에서 픽셀의 상대적인 위치 (0 ~ 1)
    // 정수파트, floor(_in.vUV): 전체 타일 중에서 인덱스(행, 열)   
    
    int2 TileIdx = floor(_in.vUV);
    int BufferIdx = g_int_0 * TileIdx.y + TileIdx.x;
    float2 vUV = TileBuffer[BufferIdx].vLeftTop + (TileBuffer[BufferIdx].vSlice * frac(_in.vUV));    
    
    if(g_btex_0)
    {
        vOutColor = g_tex_0.Sample(g_sam_1, vUV);
    }
    
    return vOutColor;
}

#endif


CResMgr.cpp파일


그리고 리소스 매니저에서 타일맵에 사용할 셰이더를 생성시키고, 타일에 쓸 아틀라스 이미지를 로딩해준다.

// CResMgr.cpp 중에서

// ============================
	// TileMap Shader
	// 
	// RS_TYPE : CULL_NONE
	// DS_TYPE : LESS
	// BS_TYPE : MASK

	// Parameter
	// g_tex_0 : Tile Atlas Texture
	// ============================
	pShader = new CGraphicsShader;
	pShader->SetKey(L"TileMapShader");
	pShader->CreateVertexShader(L"shader\\tilemap.fx", "VS_TileMap");
	pShader->CreatePixelShader(L"shader\\tilemap.fx", "PS_TileMap");
	
	pShader->SetRSType(RS_TYPE::CULL_NONE);
	pShader->SetDSType(DS_TYPE::LESS);
	pShader->SetBSType(BS_TYPE::MASK);

	pShader->SetDomain(SHADER_DOMAIN::DOMAIN_MASK);

	AddRes(pShader->GetKey(), pShader);


Tile 하나를 불러오기


타일 하나를 자르기 위해서는 해당 타일의 좌상단과 자를 크기를 알아야한다.

이를 구조체로 만들면

// TileMap
struct tTile
{
	Vec2 vLeftTop;
	Vec2 vSlice;
};

위와 같다.

// PS_TileMap 함수 중에서

float vLT = float2(0.f, 0.f);
float vUV = vLT + float2(0.125f, 0.166f) * _in.vUV;

그리고 tilemap.fx에서 원하는 부분의 좌표를 받아와서 UV좌표를 설정하면 된다.

위에서 0.125f와 0.166f가 나온 이유는 타일의 배열이 6행 8열 구조이기 때문에 1을 각각 8과 6으로 나눈 값을 넣은 것이다.

같은 타일을 여러개 설정하기


그리고 TileMap의 render함수에서 재질에다가 값을 세팅해놓고 상수버퍼에 전달해야한다.

	// 재질 업데이트
	GetMaterial()->SetScalarParam(INT_0, &m_iTileCountX);
	GetMaterial()->SetScalarParam(INT_1, &m_iTileCountY);
	GetMaterial()->UpdateData();

원하는 타일 하나를 골라 개별적으로 배치하기


구조화 버퍼를 사용한다.

구조화 버퍼에다가 타일 데이터를 전송해서 사용하도록 한다.

// CTileMap 헤더파일 중에서

vector<tTile>   m_vecTile; 
CStructuredBuffer* m_Buffer;

그리고 타일의 개수를 변경함과 동시에 구조화 버퍼를 변경하고 벡터의 사이즈를 늘리는 작업을 한다. 이 역할을 하는 함수가 SetTileCount이다.


void CTileMap:SetTileCount(UINT _iXcount, UINT _iYCount)
{
    m_iTileCountX = _iXCount;
    m_iTileCountY = _iYCount;

    m_vecTile.clear();
    m_vecTile.resize(m_iTileCountX * m_iTileCountY);

    if (m_Buffer->GetElementCount() < m_vecTile.size())
    {
        m_Buffer->Create(sizeof(tTile), (UINT)m_vecTile.size());
    }
}

그리고 이제 LevelMgr에서 타일맵을 만들 때 타일의 개수를 설정해준다.

// CLevelMgr.cpp 중에서

// TileMap Object
	CGameObject* pTileMap = new CGameObject;

	pTileMap->AddComponent(new CTransform);
	pTileMap->AddComponent(new CTileMap);

	pTileMap->Transform()->SetRelativePos(Vec3(0.f, 0.f, 600.f));
	pTileMap->Transform()->SetRelativeScale(Vec3(500.f, 500.f, 1.f));

	pTileMap->TileMap()->GetMaterial()->SetTexParam(TEX_0, CResMgr::GetInst()->FindRes<CTexture>(L"TileAtlasTex"));
	pTileMap->TileMap()->SetSliceSize(Vec2(0.125f, 0.166f));
	pTileMap->TileMap()->SetTileCount(8, 8);

그리고 CTileMap 파일의 UpdataData함수가 구조화 버퍼 업데이트를 하는 역할을 한다.

// CTileMap.cpp 중에서


void CTileMap::UpdateData()
{
	m_Buffer->SetData(m_vecTile.data(), sizeof(tTile) * m_vecTile.size());
	m_Buffer->UpdateData(20, PIPELINE_STAGE::PS_PIXEL);
}

임의로 m_Buffer의 20번 레지스터와 바인딩을 했으므로 20번 레지스터에 업데이트를 한다.

그리고 이제는 구조화 버퍼에 있는 각 타일마다의 정보에 있는 슬라이스 크기를 적용하기 위해서 tilemap.fx 코드 중 일부를 아래와 같이 바꿔줄 수 있다.

이 때, 자신이 몇 행 몇 열의 정보를 가지고 있는지에 접근 할 수 있어야 한다.
이를 어떻게 할 수 있을까?

이번에는 정수부를 활용한다.

floor(_in.vUV)를 통해서 몇 행 몇 열인지 정보를 가져올 수 있다.

다시 적자면

  • frac(_iv.vUV) : 타일 한칸 내에서 픽셀의 상대적인 위치 (0 ~ 1)
  • floor(_iv.vUV) : 전체 타일 중에서 인덱스(행, 열)의 값
  • BufferIdx : 버퍼 인덱스
float4 PS_TileMap(VTX_TILEMAP_OUT _in) : SV_Target
{
    float4 vOutColor = float4(1.f, 0.f, 1.f, 1.f);
    
    // 소수파트, frac(_in.vUV) : 타일 한칸 내에서 픽셀의 상대적인 위치 (0 ~ 1)
    // 정수파트, floor(_in.vUV): 전체 타일 중에서 인덱스(행, 열)   
    
    int2 TileIdx = floor(_in.vUV);
    int BufferIdx = g_int_0 * TileIdx.y + TileIdx.x;
    float2 vUV = TileBuffer[BufferIdx].vLeftTop + (TileBuffer[BufferIdx].vSlice * frac(_in.vUV));    
    
    if(g_btex_0)
    {
        vOutColor = g_tex_0.Sample(g_sam_1, vUV);
    }
    
    return vOutColor;
}


© 2022.07. by Wookey_Kim

Powered by Hydejack v7.5.2