Programming/D3D12

[책공부] Texturing 예제 + chap 9 연습 문제

Dorasima 2024. 1. 13. 15:19

 

(사실 이해했는지 모르겠다. 어렴풋이 느껴질 뿐... 그래도 그 느낌을 정리한다.)

 

1. D3D에서 Texture 입히기

- 점 하나에 색 하나면, 진짜 점을 말도 안되게 찍어야 할 것이다.

- 그 대신에 어떤 텍스쳐 이미지를 이용해서 Pixel을 찍는 Texturing에 대해서 연습했다.

- 방법은 Texture와 Vertex의 UV좌표고, 그걸 가능케 하는 DirectX 기능은 ID3DResource와 Sampler 이다.

(.... 그냥 생각나는 걸 적었는데, 당연히 중간중간 다른 기능들이 많이 쓰인다.)

- 일단 예제에서는 GPU 친화적인 이미지 파일 포멧인 DDS와 그것을 Resource로 로드 해주는 업로드 함수를 사용하였다.

(추후에 Toolkit이나 DirectXTex를 써서 다른 이미지 포멧도 사용할 수 있어야할 것이다...)

 

(음...  사용법을 차례대로 그냥 적어보면서 중간중간 내용을 끼워 넣는 식으로 해보겠다.)

- Texture Image를 ID3DResource로 로드 한다. 예제에서 사용하는 텍스쳐는 일반적인 이미지 이므로 Dimension은 D3D11_RESOURCE_DIMENSION_TEXTURE2D이다.

(D3D12는 온갖게 다 ID3DResource으로 사용하는 듯 하다. 리소스를 생성할 때 D3D12_RESOURCE_DESC에서 타입이니 차원이니를 다 설정하여서 사용하는 커스텀... 타입 느낌)

 

- 이제 리소스를 Shader에  넘겨주하는데...어떻게 넘겨줄까?

- 또 Shader는 이 Texture Resource를 이용해서 어떻게 화면에 점을 찍어야 하는데...Shader는 어떻게 작동할까?

 

- 일단 리소스를 넘겨주는 걸 먼저 보면Shader에서는 2d 텍스쳐에 관한 Type이 존재한다. -> Texture2D 

- 그리고 이것을 위한 레지스터 번호도 따로 존재한다. -> t[n]

-  예제에서는  Texture Resource를 Shader 에게  D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV 전용 힙을 만들어서 Table로 넘겨준다. (Buffer로 넘겨주려면 넘겨줄 수 있겠지만, 예제에서는 그렇게 하지 않았다.)

- D3D12_SHADER_RESOURCE_VIEW_DESC을 Texture2D에 맞게 맴버를 채워주고 Heap에 View를 만들어준다.

 

- 이제 shader가 Texture2D를 가지고 어떻게 화면을 찍는지 보자.

- UV 좌표가 왼쪽 위 가생이 부터 시작하는 정의역 [0, 1]을 가지고 있고, 해당 Vertex가 가지고 있는 UV값에 의해 Texture 위에 있는 색을 가져온다는 것은 그냥 믿음으로 넘어가자.

- 여기서 Sampler라는 친구가 나온다. 이 친구는 Texture의 색을 뽑아서 어떻게 화면이 찍을지 고민(?)해주는 친구이다. 예를 들면 Texture의 해상도가, 점으로 찍힐 화면보다 크거나 작을때 어떻게... 아니면 UV 좌표를 벗어나는 값이 나왔을 때 어떡할지 등등. 타입은 -> SamplerState을 가지게 된다.

- Sampler가 어떻게 작동할지는 D3D12_SAMPLER_DESC 구조체를 채워서 결정할 수 있다.

- 원칙 적으로 Sampler는 App에서 만들어준 다음에, 이 친구도 D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER 전용 힙을 만들어서 Table로서 Shader에 넘겨준다.

- 다만 예제에서는 Static Sampler라고 View Heap을 만들지 않고, 배열로써 바로 RootSignature에 연결 시킬 수 있는 방법을 사용하였다. (약간의 제한사항이 있지만, 비용이 매우 적고 간편하기 때문에 몇개를 미리 만들어 놓고 사용한다고 한다.)

 

- 이제 Root Signature를 만드는데... Sampler는 Static Sampler로 사용한다고 했다.

- 미리 App단에서 D3D12_SAMPLER_DESC 을 가지는 배열을 만들어서 CD3DX12_ROOT_SIGNATURE_DESC 구조체에 넣어준다.

- Texture를 받을 Table과 이전에 있었던 Constant Buffer를 받는 Root Constant를 가지는 Root Signature를 만든다.

 

- 이제 geometry를 생성하는데, Texture가 잘 입혀질 수있도록 UV 좌표 또한 Vertex 정보에 들어가도록 만들어야 한다.

 

- 예제에서 가지는 Material 구조,  Geometry, Texture View (Index)를 Render Item 마다 설정을 해주고

 

- 루프를 돌면서, ID3D12CommandList::SetGraphicsRootDescriptorTable / ID3D12CommandList:: ::SetGraphicsRootConstantBufferView을 RenderItem에 등록된 대로 bind를 해주면서 ID3D12CommandList ::DrawIndexedInstanced 을 걸면 된다.

 

2. 쉐이더에서 텍셀(Texel) 가져와서 화면에 찍기

- Shader에 Texture2D 타입의 텍스쳐 리소스와, SamplerState 타입의 샘플러가 넘어온다고 했다.

- 여기서 텍스쳐 리소스의 값을 이용해서, Vertex에 맞게, View에 맞게, Camera에 맞게 화면에 찍어야 한다.

- Texture2D  의 Sample 함수를 이용해서 UV 좌표에 맞는, 혹은 그 값에 맞게 Sampler가 고민(보간, mipmap 등등) 해서 가져온 색을 가져올 수 있다.

 

3.언급할 만한 예제에서 사용한 방법(구조)

- 이전과 비슷하다. RenderItem 구조와 Material 구조를 이용해서 리소스는 공유하면서, 기록은 편하게 하고, 사용도 편하게 한다.

 

 

4. 연습 문제

(클릭하면 커집니다.)

 

연습 문제 1:

Wrap                                                                                                                    Clamp
Mirror                                                                                                                       Border
Anisotropic Wrap                                                                                                       Linear Wrap

 

연습 문제 2 :

LOD 0                                                                                                                LOD3
Point Sampler                                                                                                   Linear Sampler

- D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER 으로 힙을 만들고, Sampler View를 채운다.

- 그리고 CD3DX12_DESCRIPTOR_RANGE에서 D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER을 성분으로 넣어주고, Shader 레지스터 번호에 맞게  table을 채워준다.

- Heap Increment는 ID3D12Device:: GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER) 으로 얻는다.

 

연습 문제 3 : 

 

 

연습 문제 4: 

UV 좌표가 시작되는 부분이 어딘지 생각하고 조심해야한다.

 

연습 문제 6 : 

(책에서 제공하는 코드에 있는 내용은 따로 캡쳐해서 올리지 않겠다.)

(이미 있는 Skull을 또 없애는건 또 가오가 상해서)

- 해골이 대충 원형으로 생겼다고 스스로에게 최면을 건 다음에, 점 위치를 대충 원으로 근사한다고 가정하고, 데카르트 좌표계를 구하는 식을 뒤집어서 Phi(피)와 Theta(세타) 값을 각각 U와 V로 넣어주었다.

- Get UV from Vertex Approximatly (using Spherical Coordinate System)

namespace Prac3
{
	XMFLOAT2 VertexToApproxSphericalRadian(const XMFLOAT3& _ver)
	{
		XMFLOAT2 uv = { 0.f, 0.f };

		XMVECTOR vec = { _ver.x, _ver.y, _ver.z, 0.f };
		vec = XMVector3Normalize(vec);
		XMFLOAT3 ver;
		XMStoreFloat3(&ver, vec);

		float theta = acosf(ver.y);
		if (theta > 0.0f)
		{
			float sinPhi = ver.z / sin(theta);
			float phi = asinf(sinPhi);

			uv.x = phi;
			uv.y = theta;
		}
		
		return uv;
	}

	void Prac3VerticesNIndicies(const wstring& _Path, vector<Vertex>& _outVertices, vector<uint32_t>& _outIndices)
	{
		ifstream fin;
		fin.open(_Path);

		string TrashBin;
		UINT VertexCount;
		UINT IndicesCount;

		float px, py, pz, nx, ny, nz;
		uint32_t index;

		if (fin.is_open())
		{
			fin >> TrashBin;
			fin >> VertexCount;

			fin >> TrashBin;
			fin >> IndicesCount;

			IndicesCount *= 3;

			_outVertices.reserve(VertexCount);
			_outIndices.reserve(IndicesCount);

			getline(fin, TrashBin);
			getline(fin, TrashBin);
			fin >> TrashBin;
			for (UINT i = 0; i < VertexCount; i++)
			{
				fin >> px >> py >> pz >> nx >> ny >> nz;
				XMFLOAT3 pos(px, py, pz);
				XMFLOAT3 norm(nx, ny, nz);
				XMFLOAT2 uv = VertexToApproxSphericalRadian(pos);
				Vertex skullVert = { pos, norm, uv };
				_outVertices.push_back(skullVert);
			}
			fin >> TrashBin;
			fin >> TrashBin;
			fin >> TrashBin;

			for (UINT i = 0; i < IndicesCount; i++)
			{
				fin >> index;
				_outIndices.push_back(index);
			}
			fin >> TrashBin;
		}

		fin.close();
	}
}

 

더보기

책 : DirectX 12를 이용한 3D 게임 프로그래밍 입문