[목차]

 

- TEXCOORD(UV)

- 간단한 UV 테스트

- Sampler

- Sampler와 AddressUV 테스트

 

 

[TEXCOORD(UV)]

 

TEXCOORD는 DirectX 및 HLSL에서 텍스쳐 좌표(Texture Coordinate)를 전달하기 위한 시맨틱 중 하나의 명칭입니다. 보통 UV값이라고 하며, Vertex Shader에서 Pixel Shader로 텍스쳐 좌표를 넘길 때 주로 활용합니다. 범위는 0.0 ~ 1.0를 사용하며 (0, 0)이 텍스쳐의 왼쪽 위, (1, 1)이 텍스쳐의 오른쪽 아래를 의미합니다.

 

이를 활용하여 다음의 기능을 활용할 수 있습니다.

 

1. 텍스쳐 매핑

: 픽셀 위치에 따라 텍스쳐의 어떤 위치를 샘플링할지 결정

 

2. 애니메이션

: 좌표를 시간에 따라 변경하여 스크롤, 웨이브 등 효과 생성

 

3. 범위조정

: UV를 0~1로 넘어서면, 샘플러 주소 모드(Wrap, Clamp, Mirror)에 따라 처리방식 결정

 

 

[간단한 UV 테스트] 

 

현재의 상태에서 테스트해보겠습니다. 목표는 한 면에 대해 Textcoord값을 활용하는 것입니다. 현재는 모든 면에 Textcoord값이 전달되어 있는데, 아래처럼 한 면만 적용해보겠습니다.

	// 정육면체
	{
		std::vector<Ext_DirectXVertexData> Vertex =
		{
			// Front (+Z)
			{ {-0.5f,  0.5f,  0.5f, 1.0f}, {1, 0, 0, 1}, {0, 0}, {0, 0, 1} },
			{ { 0.5f,  0.5f,  0.5f, 1.0f}, {0, 1, 0, 1}, {1, 0}, {0, 0, 1} },
			{ { 0.5f, -0.5f,  0.5f, 1.0f}, {0, 0, 1, 1}, {1, 1}, {0, 0, 1} },
			{ {-0.5f, -0.5f,  0.5f, 1.0f}, {1, 1, 0, 1}, {0, 1}, {0, 0, 1} },

			// Back (-Z)
			{ { 0.5f,  0.5f, -0.5f, 1.0f}, {1, 0, 0, 1}, {0, 0}, {0, 0, -1} },
			{ {-0.5f,  0.5f, -0.5f, 1.0f}, {0, 1, 0, 1}, {1, 0}, {0, 0, -1} },
			{ {-0.5f, -0.5f, -0.5f, 1.0f}, {0, 0, 1, 1}, {1, 1}, {0, 0, -1} },
			{ { 0.5f, -0.5f, -0.5f, 1.0f}, {1, 1, 0, 1}, {0, 1}, {0, 0, -1} },

			// Left (-X)
			{ {-0.5f,  0.5f, -0.5f, 1.0f}, {1, 0, 0, 1}, {0, 0}, {-1, 0, 0} },
			{ {-0.5f,  0.5f,  0.5f, 1.0f}, {0, 1, 0, 1}, {1, 0}, {-1, 0, 0} },
			{ {-0.5f, -0.5f,  0.5f, 1.0f}, {0, 0, 1, 1}, {1, 1}, {-1, 0, 0} },
			{ {-0.5f, -0.5f, -0.5f, 1.0f}, {1, 1, 0, 1}, {0, 1}, {-1, 0, 0} },

			// Right (+X)
			{ { 0.5f,  0.5f,  0.5f, 1.0f}, {1, 0, 0, 1}, {0, 0}, {1, 0, 0} },
			{ { 0.5f,  0.5f, -0.5f, 1.0f}, {0, 1, 0, 1}, {1, 0}, {1, 0, 0} },
			{ { 0.5f, -0.5f, -0.5f, 1.0f}, {0, 0, 1, 1}, {1, 1}, {1, 0, 0} },
			{ { 0.5f, -0.5f,  0.5f, 1.0f}, {1, 1, 0, 1}, {0, 1}, {1, 0, 0} },

			// Top (+Y)
			{ {-0.5f,  0.5f, -0.5f, 1.0f}, {1, 0, 0, 1}, {0, 0}, {0, 1, 0} },
			{ { 0.5f,  0.5f, -0.5f, 1.0f}, {0, 1, 0, 1}, {1, 0}, {0, 1, 0} },
			{ { 0.5f,  0.5f,  0.5f, 1.0f}, {0, 0, 1, 1}, {1, 1}, {0, 1, 0} },
			{ {-0.5f,  0.5f,  0.5f, 1.0f}, {1, 1, 0, 1}, {0, 1}, {0, 1, 0} },

			// Bottom (-Y)
			{ {-0.5f, -0.5f,  0.5f, 1.0f}, {1, 0, 0, 1}, {0, 0}, {0, -1, 0} },
			{ { 0.5f, -0.5f,  0.5f, 1.0f}, {0, 1, 0, 1}, {1, 0}, {0, -1, 0} },
			{ { 0.5f, -0.5f, -0.5f, 1.0f}, {0, 0, 1, 1}, {1, 1}, {0, -1, 0} },
			{ {-0.5f, -0.5f, -0.5f, 1.0f}, {1, 1, 0, 1}, {0, 1}, {0, -1, 0} },
		};

		// 한면만 테스트///////////////////////////////////////////////////////////////////////////////////////
		// Front (+Z)
		Vertex[0].TEXCOORD = { 0.0f, 0.0f }; // LT
		Vertex[1].TEXCOORD = { 0.0f, 0.0f }; // RT
		Vertex[2].TEXCOORD = { 0.0f, 0.0f }; // RB
		Vertex[3].TEXCOORD = { 0.0f, 0.0f }; // LB
		// Back (-Z)
		Vertex[4].TEXCOORD = { 0.0f, 0.0f }; // LT
		Vertex[5].TEXCOORD = { 1.0f, 0.0f }; // RT
		Vertex[6].TEXCOORD = { 1.0f, 1.0f }; // RB
		Vertex[7].TEXCOORD = { 0.0f, 1.0f }; // LB

		// 나머지 면은 0으로 고정하거나 무시됨
		for (int i = 8; i < Vertex.size(); ++i)
		{
			Vertex[i].TEXCOORD = { 0.0f, 0.0f };
		}
		////////////////////////////////////////////////////////////////////////////////////////////////////////////

		std::vector<UINT> Index =
		{
			// Front (+Z)
			0, 1, 2,    0, 2, 3,

			// Back (-Z)
			4, 5, 6,    4, 6, 7,

			// Left (-X)
			8, 9, 10,   8, 10, 11,

			// Right (+X)
			12, 13, 14, 12, 14, 15,

			// Top (+Y)
			16, 17, 18, 16, 18, 19,

			// Bottom (-Y)
			20, 21, 22, 20, 22, 23
		};

		Ext_DirectXVertexBuffer::CreateVertexBuffer("Box", Vertex);
		Ext_DirectXIndexBuffer::CreateIndexBuffer("Box", Index);
		std::shared_ptr<Ext_DirectXMesh> Mesh = Ext_DirectXMesh::CreateMesh("Box");
	}

 

이러면 카메라가 바라보는 방향의 면인 Box의 -Z방향(뒷쪽)에만 UV 좌표가 적용되고, 나머지는 (0, 0)이 되기 때문에 적용되지 않는 것이나 마찬가지일 것입니다.

 

이제 Pixel Shader의 엔트리포인트에 다음과 같이 작성해줍니다.

struct PSInput
{
    float4 Position : SV_POSITION;
    float4 Color : COLOR;
    float2 TexCoord : TEXCOORD;
};

float4 Basic_PS(PSInput _Input) : SV_TARGET
{
    if (_Input.TexCoord.x > 0.5f)
    {
        return float4(1.0f, 1.0f, 0.0f, 1.0f);
    }
    else
    {
        return _Input.Color;
    }
}

 

Texcoord값이 있다면 0.5 이상일 경우 노란색으로 변경합니다. 없을 경우(값이 있지만, 0.0 ~ 0.0으로 매핑되서 안보임)는 그대로 출력합니다. 결과를 확인해보겠습니다.

 

 

[Sampler]

 

Sampler는 Direct3D에서 텍스쳐 좌표를 어떻게 해석하고 샘플링할지 정의하는 객체입니다. 단독으로는 사용되지 않고 텍스쳐와 함께 사용되며, 텍스쳐를 어떻게 읽을지 정하는 설명서라고 볼 수 있습니다.

 

- 텍셀 사이를 어떻게 계산할 것인가(Point, Linear, Anisotropic)

- 텍스쳐 바깥은 어떻게 처리할 것인가(0 ~ 1 범위 바깥), (Wrap, Clamp, Mirror, Border)

- MipMap 수준 정의

- 텍스쳐 왜곡 보정 on/off(Anisotropy Level)

 

샘플러 규칙은 다른 State와 마찬가지로 SamplerState를 통해 정해줄 수 있습니다. 디폴트 SamplerState는 따로 없고, 필요에 따라 만들어서 사용해야합니다. 그나마 대표적으로 많이 활용되는 Sampler가 LinearClamp Sampler입니다.

// 샘플러
{
	D3D11_SAMPLER_DESC SamperInfo = {};

	SamperInfo.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	SamperInfo.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
	SamperInfo.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
	SamperInfo.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
	SamperInfo.MipLODBias = 0.0f;
	SamperInfo.MaxAnisotropy = 1;
	SamperInfo.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
	SamperInfo.MinLOD = -FLT_MAX;
	SamperInfo.MaxLOD = FLT_MAX;
	// <<설명>>
	/*1. Filter : MIN_MAG_MIP_LINEAR(축소, 확대, 밉맵) 모두 선형 보간으로 부드럽게 처리한다는 뜻의 D3D11_FILTER_MIN_MAG_MIP_LINEAR 전달*/
	/*2~4. AddressUVW : D3D11_TEXTURE_ADDRESS_CLAMP는 UV가 0~1 범위를 넘을 경우, 가장자리 색상을 계속 사용한다는 뜻*/
	/*5. MipLODBias : 밉맵 LOD를 얼마나 조정할지 정하기 위함, 0.0f로 전달하면 기본 수준 사용 */
	/*6. MaxAnisotropy : 애니소트로픽 필터링 여부, 1은 비활성화이며 4 ~ 16을 보통 사용한다.*/
	/*7. ComparisonFunc : 그림자 맵 등에서 비교 연산 시 항상 통과한다는 설정, 일반 텍스쳐는 보통 ALWAYS를 설정한다.*/
	/*8~9. MinLOD, MaxLOD : 밉맵 레벨 범위 제한, -FLT_MAX ~ FLT_MAX면 전체를 허용하는 것*/

	Ext_DirectXSampler::CreateSampler("BaseSampler", SamperInfo);
    // ....
}

 

이렇게 CreateSampler()를 호출하면 아래의 함수들이 호출됩니다.

// 위의 CreateSampler로 호출, 이후 픽셀 셰이더 리플렉션 시 사용되기 위해 BaseSampler 이름으로 저장
void Ext_DirectXSampler::CreateSampler(const D3D11_SAMPLER_DESC& _Desc)
{
	if (nullptr != SmplerState)
	{
		SmplerState.Reset(); // 기존 샘플러 있을 경우, 해제한 뒤 다시 사용
	}

	SamplerInfo = _Desc;

	// GPU에 실제 샘플러 상태 객체 생성, SmplerState에 저장
	if (S_OK != Ext_DirectXDevice::GetDevice()->CreateSamplerState(&SamplerInfo, SmplerState.GetAddressOf()))
	{
		MsgAssert("샘플러 생성에 실패했습니다.");
		return;
	}
}

/////// 렌더링 파이프라인에서 사용하는 두 종류

// 정점 셰이더에 슬롯별로 샘플러 바인딩, 정점 셰이더 샘플러 바인딩은 거의 사용하지 않고 픽셀 셰이더에서만 일어난다.
void Ext_DirectXSampler::VSSetting(UINT _Slot)
{
	Ext_DirectXDevice::GetContext()->VSSetSamplers(_Slot, 1, SmplerState.GetAddressOf());
}

// 픽셀 셰이더에 슬롯별로 샘플러 바인딩(register(b0)에 바인딩됨) -> Tex.Sample(Sampler, uv)에 사용
void Ext_DirectXSampler::PSSetting(UINT _Slot)
{
	Ext_DirectXDevice::GetContext()->PSSetSamplers(_Slot, 1, SmplerState.GetAddressOf());
}

 

Sampler는 Pixel Shader에서 선언하고 사용하는 방식이기 때문에, 다른 State와 달리 Maetrial 클래스에 바인딩해주지 않고 Constant Buffer 처럼 리플렉션 시 버퍼를 세팅하도록 했습니다.

case D3D_SIT_SAMPLER:
{
	// 바인딩 슬롯 이름과 동일한 샘플러를 찾지 못하면, 기본 Base 샘플러로 변경
	std::shared_ptr<Ext_DirectXSampler> SamplerResource = Ext_DirectXSampler::Find(UpperName);
	if (nullptr == SamplerResource) SamplerResource = Ext_DirectXSampler::Find("LinearClampSampler");

	// 샘플러 세터 데이터 입력 후 저장
	SamplerSetter Set;
	Set.OwnerShader = GetSharedFromThis<Ext_DirectXShader>();
	Set.Name = UpperName;
	Set.BindPoint = ResDesc.BindPoint; // GPU에 바인딩할 슬롯 정보 register(s0)
	Set.Sampler = SamplerResource; // 샘플러 저장

	BufferSetter.InsertSamplerSetter(Set);

	break;
}

 

이렇게 해주면 MeshComponentUnit이 Initialize를 수행할 때, Copy를 통해 자신의 BufferSetter에 값을 복사해서 가져갈 것입니다.

 

세팅이 완료됐으면 바로 사용할 수 있습니다. Pixel Shader에서 다음과 같이 선언해주면 됩니다.

Texture2D BaseColorTex : register(t0); // 텍스처 자원
SamplerState Sampler : register(s0); // 샘플러

struct PSInput
{
    float4 Position : SV_POSITION;
    float4 Color : COLOR;
    float2 TexCoord : TEXCOORD;
    float4 Normal : NORMAL;
};

float4 Basic_PS(PSInput _Input) : SV_TARGET
{
    float4 Color = BaseColorTex.Sample(Sampler, _Input.TexCoord);
    
    return Color;
}

 

[BaseColorTex.Sample(Sampler, _Input.TexCoord)]는 "BaseColorTex 텍스쳐에서 _Input.TexCoord 위치의 텍셀을 Sampler 설정 방식에 따라 읽어서 Color에 저장하라"는 뜻입니다. 말이 조금 어려울 수 있는데, 단순하게 말하자면 Mesh의 UV 영역에 Texture를 복붙하라는 뜻입니다. 이러면 둘의 크기가 달라도 샘플링을 통해 Mesh UV값에 따라 Texture가 입혀집니다.

 

[Sampler와 AddressUV 테스트]

 

현재 UV값을 (0. 0), (1. 1)로 사용하면서, CLAMP를 활용하고 있습니다. 좌표값을 임의로 변경하여 테스트해보겠습니다. 먼저 TEXCOORD로 전달될 값을 최소 -0.5, 최대 0.5로 변경해보겠습니다.

// Back (-Z) 변경전
{ { 0.5f,  0.5f, -0.5f, 1.0f}, {1, 0, 0, 1}, {1, 0}, {0, 0, -1} },
{ {-0.5f,  0.5f, -0.5f, 1.0f}, {0, 1, 0, 1}, {0, 0}, {0, 0, -1} },
{ {-0.5f, -0.5f, -0.5f, 1.0f}, {0, 0, 1, 1}, {0, 1}, {0, 0, -1} },
{ { 0.5f, -0.5f, -0.5f, 1.0f}, {1, 1, 0, 1}, {1, 1}, {0, 0, -1} },

// Back (-Z) 변경후
{ { 0.5f,  0.5f, -0.5f, 1.0f}, {1, 0, 0, 1}, {0.5, -0.5}, {0, 0, -1} },
{ {-0.5f,  0.5f, -0.5f, 1.0f}, {0, 1, 0, 1}, {-0.5, -0.5}, {0, 0, -1} },
{ {-0.5f, -0.5f, -0.5f, 1.0f}, {0, 0, 1, 1}, {-0.5, 0.5}, {0, 0, -1} },
{ { 0.5f, -0.5f, -0.5f, 1.0f}, {1, 1, 0, 1}, {0.5, 0.5}, {0, 0, -1} },

 

1. Clamp : 0~1 범위를 벗어나도 가장자리값 유지(끝 색상이 계속 늘어나면서 유지)

 

2. Wrap : 1을 넘어가면 반복됨(텍스쳐 타일링)

 

3. Mirror : 반복되지만 짝수는 그대로, 홀수는 반전(반전 반복, 마치 거울처럼)

 

 

 

+ Recent posts