공부중

[DirectX12]Hello Wolrd Sample - D3D12HelloTriangle - 3 본문

Programing/DirectX

[DirectX12]Hello Wolrd Sample - D3D12HelloTriangle - 3

곤란 2018. 5. 22. 18:42
반응형

이번에 볼 코드는 아래와 같다

 

// Create the pipeline state, which includes compiling and loading shaders.
	{
		ComPtr<ID3DBlob> vertexShader;
		ComPtr<ID3DBlob> pixelShader;

#if defined(_DEBUG)
		// Enable better shader debugging with the graphics debugging tools.
		UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
		UINT compileFlags = 0;
#endif

		ThrowIfFailed(D3DCompileFromFile(GetAssetFullPath(L"shaders.hlsl").c_str(), 
						 nullptr, 
						 nullptr, 
						 "VSMain", 
						 "vs_5_0", 
						 compileFlags, 
						 0, 
						 &vertexShader, 
						 nullptr));
		ThrowIfFailed(D3DCompileFromFile(GetAssetFullPath(L"shaders.hlsl").c_str(), 
						 nullptr, 
						 nullptr, 
						 "PSMain", 
						 "ps_5_0", 
						 compileFlags, 
						 0, 
						 &pixelShader, 
						 nullptr));

		// Define the vertex input layout.
		D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
		{
			{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
			{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
		};

		// Describe and create the graphics pipeline state object (PSO).
		D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
		psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
		psoDesc.pRootSignature = m_rootSignature.Get();
		psoDesc.VS = CD3DX12_SHADER_BYTECODE(vertexShader.Get());
		psoDesc.PS = CD3DX12_SHADER_BYTECODE(pixelShader.Get());
		psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
		psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
		psoDesc.DepthStencilState.DepthEnable = FALSE;
		psoDesc.DepthStencilState.StencilEnable = FALSE;
		psoDesc.SampleMask = UINT_MAX;
		psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
		psoDesc.NumRenderTargets = 1;
		psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
		psoDesc.SampleDesc.Count = 1;
		ThrowIfFailed(m_device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&m_pipelineState)));
	}

파이프라인 상태 생성 및 셰이더 컴파일 하고 로딩하는것까지의 코드이다.

 

 

		ComPtr<ID3DBlob> vertexShader;
		ComPtr<ID3DBlob> pixelShader;


#if defined(_DEBUG)
		// Enable better shader debugging with the graphics debugging tools.
		UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
		UINT compileFlags = 0;
#endif

ComPtr로 버텍스 셰이더와 픽셀 셰이더를 저장할 변수를 만들고

디버그 모드일때 셰이더를 컴파일할때 필요한 플래그를 설정해 준다. 디버그 모드가 아닌경우에는 0으로 처리.

각각의 플래그들은 MSDN을 살펴보자. (링크)

 

현재 디버그일시 주어진 플래그들은

D3DCOMPILE_DEBUG = 디버그 파일 / 행 / 형식 / 기호 정보를 출력 코드에 삽입하도록 컴파일러에 지시합니다.

D3DCOMPILE_SKIP_OPTIMIZATION = 코드 생성 중에 최적화 단계를 건너 뛰도록 컴파일러에 지시합니다. 

    디버그 전용으로이 상수를 설정하는 것이 좋습니다.

 

 

 

		ThrowIfFailed(D3DCompileFromFile(GetAssetFullPath(L"shaders.hlsl").c_str(), 
						 nullptr, 
						 nullptr, 
						 "VSMain", 
						 "vs_5_0", 
						 compileFlags, 
						 0, 
						 &vertexShader, 
						 nullptr));
		ThrowIfFailed(D3DCompileFromFile(GetAssetFullPath(L"shaders.hlsl").c_str(), 
						 nullptr, 
						 nullptr, 
						 "PSMain", 
						 "ps_5_0", 
						 compileFlags, 
						 0, 
						 &pixelShader, 
						 nullptr));

위에서 넣어준 두 개의 플래그를 가지고 다음 라인인 D3DCompileFromFile 함수를 통해서 셰이더(shader)파일을 불러오고 

셰이더의 시작함수들(임의로 설정 가능 위의 코드에서 VertexShader는 VSMain PixelShader는 PSMain)과 

버전(vs_5_0과 ps_5_0)을 넘겨주어서 컴파일한다.

 

 

		// Define the vertex input layout.
		D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
		{
			{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
			{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
		};

 

 

Vertex(버텍스, 정점) 입력 레이아웃을 정의한다고 주석에 적혀 있는데...

코드 문법만 해석한다면 D3D12_INPUT_ELEMENT_DESC 라는 형의 구조체 배열을 채우고 있을 뿐이다.

 

저 D3D12_INPUT_ELEMENT_DESC라는 구조체를 설명하기 전에 

Direct3D의 Vertex에 공간적 위치 이외에 추가적인 자료를 부여할수가 있다.

이런 Custom Vertex Format을 만들려면 어떤 자료를 담을지 구조체를 정의해야 하는데...

그 사용자 정의 구조체는 D3D12HelloTriangle.h에 있는 Vertex라는 이름의 구조체이다.

 

	// ====================
	// D3D12HelloTriangle.h
	// ====================

class D3D12HelloTriangle : public DXSample
{

// 중략...

private:
	struct Vertex
	{
		XMFLOAT3 position;
		XMFLOAT4 color;
	};

// 중략...

 

위치와 컬러로 구성된 Vertex 구조체가 선언되어 있다.

이러한 Vertex구조체를 정의한 다음에 각 성분으로 무엇을 해야하는지 Direct3D에게 알려주어야 하는데.

이러한 정보를 Direct3D에게 알려주는 수단으로쓰이는것이 입력 배치 서술(input layout description)이라고 한다.

이 서술은 D3D12_INPUT_LAYOUT_DESC라는 구조체로 쓰인다.

 

이 D3D12_INPUT_LAYOUT_DESC를 보면....

//
// filename : d3d12.h
//

// 중략....

typedef struct D3D12_INPUT_LAYOUT_DESC
    {
    _Field_size_full_(NumElements)  const D3D12_INPUT_ELEMENT_DESC *pInputElementDescs;
    UINT NumElements;
    } 	D3D12_INPUT_LAYOUT_DESC;

// 중략...

 

이렇게 되어있다. 위에서 보이듯이 그냥 D3D_INPUT_ELEMENT_DESC 형식의 원소들을 담은 배열과 그 원소의 갯수이다.

그래서 D3D_INPUT_ELEMENT_DESC의 구조체가 뭐냐면 저 Vertex구조체의 각 성분을 서술하는것이다.

 

//
// filename : d3d12.h
//

// 중략....

typedef struct D3D12_INPUT_ELEMENT_DESC
    {
    LPCSTR SemanticName;
    UINT SemanticIndex;
    DXGI_FORMAT Format;
    UINT InputSlot;
    UINT AlignedByteOffset;
    D3D12_INPUT_CLASSIFICATION InputSlotClass;
    UINT InstanceDataStepRate;
    } 	D3D12_INPUT_ELEMENT_DESC;

// 중략...

 

* SemanticName : 성분에 부여된 문자열 이름. VertexShader에서 semantic 이름으로 쓰이므로 반드시 유요한 변수 이름이어야 한다.

                      여기에 입력한 semantic은 VertexShader의 입력 부분과 대응하게 된다.

 

 

 

//================================
// filename : D3D12HelloTriangle.h
//================================

	//중략....

	struct Vertex
	{
		XMFLOAT3 position;
		XMFLOAT4 color;
	};

	//중략....

//================================
// filename : D3D12HelloTriangle.cpp
//================================

		// 중략...

		// Define the vertex input layout.
		D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
		{
			{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
			{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
		};

		// 중략...

//================================
// filename : shaders.hlsl
//================================

// 중략...

PSInput VSMain(float4 position : POSITION, float4 color : COLOR)
{
	PSInput result;

	result.position = position;
	result.color = color;

	return result;
}

// 중략...


D3D12HelloTriangle.cpp에서 POSITION과 COLOR 이라고 문자열을 초기화를 해주고 있는데 이는

D3D12HelloTriangle.h에서의 Vertext의 XMFLOAT3 position이 

shaders.hlsl의 VSMain( float4 position : POSITION , float4 color : COLOR ) 에서 float4 position : POSITION과 매칭이 되는거다.

COLOR도 위와같은 방식으로 매칭이 된다.

 

* SemanticIndex : 위의 코드에서는 나타나지 않았지만 만약에 텍스쳐가 여러개가 필요한 경우가 생길것이다.

                        이때 개별적인 이름을 부여하는대신 그냥 index를 통해서 구별해도 되므로 이 index가 존재 한다.

                        따로 index가 지정되지 않으면 0번으로 간주.

 

* Format : DXGI_FORMAT 열거형의 멤버로 이 정점 성분의 자료 형식을 Direct3D에 알려주는 역할을 하게 됨.

              어떤것들이 있는지는 MSDN을 보자 ( 링크 ) 

 

* InputSlot : 이 성분의 자료를 가져올 VertexBuffer 슬롯의 index이다.

                 Direct3D에서는 총 16개의 VertexBuffer 슬롯을 통해서 자료를 공급 할 수 있다.(0~15)

 

* AlignedByteOffset : 지정된 InputSlot에서 해당 정점성분의 시작 위치의 거리를 나타내는 Offset(byte 단위)이다.

 

예1 )

 

struct Vertex { XMFLOAT3 position; //sizeof(XMFLOAT3) == 12byte XMFLOAT4 color; //sizeof(XMLOAT4) == 16byte };

위의 코드와 같은 Vertex 구조체 이다.

첫번째 XMFLOAT3 position 의 사이즈는 12byte(float x 3)지만 구조체에서 제일 앞에서 있으므로 오프셋은 0 바이트 만큼 떨어져 있다.

두번째 XMFLOAT4 color 의 사이즈는 16byte(float x 4)이나 구조체에서 XMFLOAT3의 뒤에 위치하므로 12byte만큼 떨어져 있다.

 

예2 )

 

	struct Vertex
	{
		XMFLOAT3 position;	//sizeof(XMFLOAT3) == 12byte
		XMFLOAT4 color;		//sizeof(XMFLOAT4) == 16byte
		XMFLOAT2 texture0;	//sizeof(XMFLOAT2) == 8byte
		XMFLOAT2 texture1;	//sizeof(XMFLOAT2) == 8byte
	};

 

총 4개의 멤버가 있는 구조체 이다.

첫번째 XMFLOAT3 position 의 사이즈는 12byte(float x 3)지만 구조체에서 제일 앞에서 있으므로 오프셋은 0 바이트 만큼 떨어져 있다.

두번째 XMFLOAT4 color의 사이즈는 16byte(float x 4)이나 구조체에서 XMFLOAT3의 뒤에 위치하므로 12byte만큼 떨어져 있다.

세번째 XMFLOAT2 texture0 의 사이즈는 8byte(float x 2)이나 구조체에서 XMFLOAT3와 XMFLOAT4의 합만큼 뒤에 위치하므로 

총 24byte(12+16)만큼 떨어져있다.

네번째 XMFLOAT2 texture1 의 사이즈는 8byte(float x 2)이나 구조체에서 XMFLOAT3와 XMFLOAT4, XMFLOAT2의 세합만큼 뒤에 위치하므로 

총 32byte(12+16+8)만큼 떨어져 있다.

 

* InputSlotClass : D3D12_INPUT_CLASSIFICATION 이라는 형의 enum값을 받는데 입력 슬롯에 포함 된 데이터 유형을 식별한다고 한다.

                        자세한것은 MSDN을 참조하자. (링크)

 

* InstanceDataStepRate : 하나의 요소만큼 버퍼에서 진행하기 전에 동일한 인스턴스 별 데이터를 사용하여 그릴 인스턴스 수 라고 한다

 

 

 

		// Describe and create the graphics pipeline state object (PSO).
		D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
		psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
		psoDesc.pRootSignature = m_rootSignature.Get();
		psoDesc.VS = CD3DX12_SHADER_BYTECODE(vertexShader.Get());
		psoDesc.PS = CD3DX12_SHADER_BYTECODE(pixelShader.Get());
		psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
		psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
		psoDesc.DepthStencilState.DepthEnable = FALSE;
		psoDesc.DepthStencilState.StencilEnable = FALSE;
		psoDesc.SampleMask = UINT_MAX;
		psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
		psoDesc.NumRenderTargets = 1;
		psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
		psoDesc.SampleDesc.Count = 1;
		ThrowIfFailed(m_device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&m_pipelineState)));

 

다음은 그래픽 파이프라인 상태 객체(PSO - pipeline state object)를 기술하고 생성하는 코드이다.

D3D12_GRAPHICS_PIPELINE_STATE_DESC 라는 형의 구조체를 생성해주고 값들을 대입해 주고있다.

InputLayout에는 위에서 설명한 D3D12_INPUT_LAYOUT_DESC형이다 

D3D12_INPUT_LAYOUT_DESC형은 D3D12_INPUT_ELEMENT_DESC의 배열과 그 원소의 갯수만 저장하므로 그대로 넘겨서 주고 있다.

RootSignature는 위에서 생성한 m_rootSignature을 Get 함수를 통해서 넘겨주고 있다.

 

VS와 PS는 각각 VertexShader와 PixelShader이므로 위에서 컴파일한것을 넘겨주고 있다.

RasterizerState와 BlendState는 기본으로 하고 있고

DepthStencilState는 모두 FALSE로 해주고 있다.

 

SampleMask는 블렌드 상태의 샘플 마스크라고 한다. 멀티샘플링(Multisampling - 다중 표본화)는 최대 32개의 표본을 취할수가 있는데

이 32bit 정수필드의 각각의 비트가 각각 표본의 활성화/비활성화 여부를 결정한다. 

일반적으로 기본값은 0xffffffff(#define UINT_MAX 0xffffffff)이다.

 

PrimitiveTopologyType은 기본 도형의 위상 구조 종류를 지정한다.

점, 선, 삼각형, 등등등 (patch라는것도 있는데 뭐라고 번역을 해야 할지 모르겠다. 이것은 테셀레이션에서 쓰인다고 한다.)

 

NumRenderTargets는 동시에 사용할 렌더 대상 갯수.

 

RTVFormats는 렌더 대상 형식들이다. 동시에 여러 랜더 대상에 Scene을 그릴수 있도록 배열을 지정한다.

 

SampleDesc는 멀티샘플링(Multisampling - 다중 표본화)의 표본 갯수와 품질 수준을 서술하는데 여기서는 갯수만 적어 주었다.

 

마지막으로 디바이스(m_device에서 CreateGraphicsPipelineState를 통해서 그래픽 파이프라인 상태 객체를 만들어 주었다.

 

으으 적다보니 하루만에 못적을 정도로 엄청 길어졌다 ㅂㄷㅂㄷ...

다음은 VertexBuffer 생성에 관련된 설명이 주가 될것 같다.

 

 

다음글에서 ......

 

 

 

 

 

 

 

반응형