[DX11] 삼각형 띄우기

LeeTaes·2024년 1월 8일
0

DirectX

목록 보기
6/9

삼각형을 화면에 그리기

루키스님의 인프런 강의를 보고 정리한 내용입니다.
https://www.inflearn.com/course/directx11-%EA%B2%8C%EC%9E%84%EA%B0%9C%EB%B0%9C-%EB%8F%84%EC%95%BD%EB%B0%98/dashboard


  • DirectX 11 환경에서 삼각형을 화면에 그리는 과정에 대해 알아보겠습니다.

  • 기본적인 도형을 화면에 출력하기 위해서도 꼭 필요한 과정들을 거쳐야 합니다.

1. 기하학적인 도형 만들기


  • 기하학적인 도형을 만들기 위한 함수를 선언 & 정의합니다.
    • 정점이라는 정보가 필요하므로 Types.h 파일에서 구조체를 추가합니다.
    • 입력한 정점의 정보(_vertices)는 CPU의 메모리에 존재합니다.
  • CPU에서 사용하는 것이 아닌 GPU의 VRAM(메모리)에도 만들어줘야 합니다.
  • VertexBuffer를 만들어 정보를 IA에 넘겨줄 필요가 있습니다.

Types.h

// 정점에 대한 정보를 저장하기 위한 구조체를 선언합니다.
struct Vertex
{
	// 위치 좌표 (x, y, z)
	Vec3 position;
	// 색상 정보 (R, G, B, A)
	Color color;
};

Game.h

private:
	// 기하학적인 도형을 만들기 위한 함수를 선언합니다.
	void CreateGeometry()

private:
	// Geometry
	// * 정점들을 저장하기 위한 벡터 컨테이너를 선언합니다.
	vector<Vertex> _vertices;

	// _vertices의 데이터들은 CPU의 메모리에 존재합니다.
	// GPU의 메모리(VRAM)으로 옮겨줄 필요가 있습니다.
	
	// * 정점 정보들을 저장하기 위한 버퍼를 선언합니다.
	ComPtr<ID3D11Buffer> _vertexBuffer = nullptr;

Game.cpp

void Game::CreateGeometry()
{
	// 3각형을 위한 Vertex Data
	{
		// 사이즈 조정
		_vertices.resize(3);

		// 위치 정보와 색상 정보를 설정합니다.
		_vertices[0].position = Vec3(-0.5f, -0.5f, 0.0f);
		_vertices[0].color = Color(1.0f, 0.0f, 0.0f, 0.0f);		

		_vertices[1].position = Vec3(0.0f, 0.5f, 0.0f);
		_vertices[1].color = Color(1.0f, 0.0f, 0.0f, 0.0f);		

		_vertices[2].position = Vec3(0.5f, -0.5f, 0.0f);
		_vertices[2].color = Color(1.0f, 0.0f, 0.0f, 0.0f);
	}

	// VertexBuffer
	{
		// 버퍼 생성에 사용될 DESC를 생성합니다.
		D3D11_BUFFER_DESC desc;
		// 0으로 초기화해줍니다.
		ZeroMemory(&desc, sizeof(desc));
		// * D3D11_USAGE_IMMUTABLE : GPU만 읽을 수 있는 방식으로 사용하겠다고 설정
		desc.Usage = D3D11_USAGE_IMMUTABLE;
		// * D3D11_BIND_VERTEX_BUFFER : VertexBuffer를 바인딩하는 용도로 사용하겠다고 설정
		desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
		// * desc의 바이트 크기를 설정합니다. (데이터 유형의 크기 * 수)
		desc.ByteWidth = (uint32)sizeof(Vertex) * _vertices.size();

		D3D11_SUBRESOURCE_DATA data;
		ZeroMemory(&data, sizeof(data));
		// 첫 번째 데이터의 시작 주소를 저장합니다.
		data.pSysMem = _vertices.data();

		// 버퍼를 생성해줍니다. (_vertexBuffer에 결과물을 저장합니다.)
		_device->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf());
		
		// -> 위 과정을 거쳐 GPU쪽에 버퍼가 생성되며 초기에 _vertices 값들이 복사됩니다.
		// -> 즉, 이 과정 이후부터는 GPU만 Read Only로 사용하겠다는 의미입니다.
	}
}
  • 삼각형의 좌표 정보는 -1부터 1사이로 설정하면 됩니다.

2. InputLayout & Shader 생성


  • 위에서 만든 VertexBuffer의 정보들이 어떻게 되어있는지 묘사해야 하는 과정이 필요합니다.

  • 즉, 입력 버퍼 데이터를 설명하는 입력 레이아웃이 필요합니다.

    → 하지만 입력 레이아웃을 생성하기 위해서는 먼저 셰이더에 대한 정보가 필요합니다.

    → 꼭짓점 셰이더 파일을 생성합니다.

  • 셰이더를 사용하는 방법은 두 가지가 존재합니다.

    1. 컴파일한 결과물이 CSO라는 파일로 만들어지는데 그 결과물을 바로 로드해 사용
    2. 프로그램을 실행하는 순간 코드를 읽어 컴파일하여 동적으로 사용
  • 1번과 2번 방법을 모두 사용하도록 합니다.

    → 빌드하는 것을 체크하지 않으면 hlsl 파일의 오류를 컴파일 타임에서 잡아주지 않음

  • Vertax Shader, Pixel Shader를 한 파일에 묶어서 간단히 설명하겠습니다.

Default.hlsl

// VertexShader에 들어오는 구조체를 생성합니다.
struct VS_INPUT
{
	// 생성할 Input Layout을 보면
	// position은 POSITION으로, color는 COLOR로 읽기로 지정
	float4 position : POSITION;
	float4 color : COLOR;
};

// VertexShader에서 출력되는 구조체를 생성합니다.
struct VS_OUTPUT
{
	// SV : System Value - 무조건 있어야 하는 값
	float4 position : SV_POSITION;
	float4 color : COLOR;
};

// VertexShader의 메인 함수를 정의합니다.
// * VS_OUTPUT : 리턴 타입
// * VS		   : 함수 명
// * (VS_INPUT input) : 입력 매개변수
VS_OUTPUT VS(VS_INPUT input)
{
	// 복잡한 연산 과정이 필요하지만 지금은 간단히 return 시켜줍니다.
	// * 출력 타입의 구조체를 생성합니다.
	VS_OUTPUT output;

	// * 출력할 구조체의 정보를 채워줍니다.
	output.position = input.position;
	output.color = input.color;

	// * 만들어진 구조체를 반환합니다.
	return output;
}
// 렌더링 파이프라인이 시작되면 IA단계에서 VS로 넘어가게 되는데 이 때 위에서 선언한 메인 함수로 들어온다고 생각하면 됩니다.
// * VS 단계의 메인 함수에서는 무조건 정점 단위로 실행이 됩니다.

// 렌더링 파이프라인
// * IA - VS - RS - PS - OM (핵심단계들)
//  -> 위 단계들은 포맷(Input Layout)에 맞게 기하 도형들이 넘어온다고 생각하면 됩니다.

// IA : 입력
// VS : 정점 단위로 연산
// RS : 지금은 삼각형을 만들거라고 입력했기 때문에 3개의 정점 사이 영역만 인지하고, 아닌 영역은 제거해줍니다.
//      + 각 정점의 색상이 다르면 중앙 위치의 색은 기본적으로 정점들의 색을 섞어서 보간해 결과를 만들어줍니다.
// PS : RS로부터 받은 정보들 토대로 조명(색상)과 관련된 연산을 수행

// VS(정점) -> RS(처리) -> PS(픽셀)
// PixelShader의 메인 함수를 정의합니다.
// * (VS_OUTPUT input) : VS 단계에서 리턴해준 output을 입력 파라미터로 전달 받습니다.
// * SV_Target		   : PS의 결과물이 SV_Target(렌더 타겟)에 전달합니다.
float4 PS(VS_OUTPUT input) : SV_Target
{
	// 조명(색상)과 관련된 연산 처리 후 반환해주는 로직이 들어가는 자리입니다.
	// * 지금은 테스트로 빨간 삼각형을 그리기 때문에 빨간색을 반환한다고 가정합니다.
	return float4(1, 0, 0, 0);
}
  • 여기서 생성한 셰이더 정보들을 불러와 사용해야 합니다.
  • 셰이더 파일의 정보를 불러오기 위한 함수와 저장할 변수를 만들어줍니다.

Game.h

private:
	// 셰이더는 파일을 로드하는 방식으로 만들어줘야 합니다.
	// * 공용으로 셰이더를 파일로부터 로드하기 위한 함수를 선언합니다.
	// * path : 경로
	// * name : 이름
	// * version : 셰이더 버전
	// * Blob : 로드한 셰이더 결과물을 저장할 변수
	void LoadShaderFromFile(const wstring& path, const string& name, const string& version, ComPtr<ID3DBlob>& blob);

	// VS
	// * VS를 로드해 저장하기 위한 변수를 선언합니다.
	ComPtr<ID3D11VertexShader> _vertexShader = nullptr;
	// * 임의 길이 데이터를 반환하는 데 사용할 변수를 선언합니다.
	ComPtr<ID3DBlob> _vsBlob;

	// PS
	// * PS를 로드해 저장하기 위한 변수를 선언합니다.
	ComPtr<ID3D11PixelShader> _pixelShader = nullptr;
	// * 임의 길이 데이터를 반환하는 데 사용할 변수를 선언합니다.
	ComPtr<ID3DBlob> _psBlob;

Game.cpp

void Game::LoadShaderFromFile(const wstring& path, const string& name, const string& version, ComPtr<ID3DBlob>& blob)
{
	// 비트 플래그 설정 (Debug 모드, 최적화 건너 뛰기)
	const uint32 complieFlag = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;

	// HLSL(High Level Shader Language) 코드를 지정된 대상에 대한 바이트코드로 컴파일합니다.
	HRESULT hr = ::D3DCompileFromFile(
		path.c_str(),
		nullptr,
		D3D_COMPILE_STANDARD_FILE_INCLUDE,
		name.c_str(),
		version.c_str(),
		complieFlag,
		0,
		blob.GetAddressOf(),
		nullptr
	);

	CHECK(hr);
}
  • LoadShaderFromFile() 함수를 사용해 각각의 VS, PS를 만들어줍니다.

Game.h

private:
	// VS를 생성하기 위한 함수를 선언합니다.
	void	CreateVS();
	// PS를 생성하기 위한 함수를 선언합니다.
	void	CreatePS();

Game.cpp

void Game::CreateVS()
{
	// 만든 셰이더 파일로부터 _vsBlob에 임시 데이터를 불러와 로드합니다.
	LoadShaderFromFile(L"Default.hlsl", "VS", "vs_5_0", _vsBlob);

	// VS를 생성합니다.
	HRESULT hr = _device->CreateVertexShader(_vsBlob->GetBufferPointer(), _vsBlob->GetBufferSize(), nullptr, _vertexShader.GetAddressOf());
	CHECK(hr);
}

void Game::CreatePS()
{
	// 만든 셰이더 파일로부터 _psBlob에 임시 데이터를 불러와 로드합니다.
	LoadShaderFromFile(L"Default.hlsl", "PS", "ps_5_0", _vsBlob);

	// PS를 생성합니다.
	HRESULT hr = _device->CreatePixelShader(_psBlob->GetBufferPointer(), _psBlob->GetBufferSize(), nullptr, _pixelShader.GetAddressOf());
	CHECK(hr);
}
  • 셰이더 정보가 생성되었으므로 InputLayout을 만들어줍니다.

Game.h

private:
	// 생성한 기하학적인 도형이 어떻게 되어있는지 묘사해야 합니다.
	// * input layout을 생성하기 위한 함수를 선언합니다.
	void	CreateInputLayout();

	// * 입력 레이아웃을 저장하기 위한 변수를 선언합니다.
	ComPtr<ID3D11InputLayout> _inputLayout = nullptr;

Game.cpp

void Game::CreateInputLayout()
{
	// 입력 요소에 대한 정보를 생성합니다.
	// * Vertex 구조체의 내부 요소들에 대해 묘사합니다.
	D3D11_INPUT_ELEMENT_DESC layout[] =
	{
		{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
	};

	// layout의 개수를 저장합니다.
	const int32 count = sizeof(layout) / sizeof(D3D11_INPUT_ELEMENT_DESC);

	// 입력 버퍼 데이터를 설명하는 입력 레이아웃 개체를 만듭니다.
	_device->CreateInputLayout(layout, count, _vsBlob->GetBufferPointer(), _vsBlob->GetBufferSize(), _inputLayout.GetAddressOf());
}

3. 모든 과정 연결하기 (초기화)


  • 위에서 거친 모든 과정들을 하나로 연결해줍니다.
  • 실제로 렌더링하기 위한 설정들을 진행해줍니다.

Game.cpp

void Game::Init(HWND hwnd)
{
	// 멤버 변수 초기화
	_hwnd = hwnd;
	_width = GWinSizeX;
	_height = GWinSizeY;

	CreateDeviceAndSwapChain();
	CreateRenderTargetView();
	SetViewport();

	// 삼각형 출력하기 실습
	CreateGeometry();
	CreateVS();
	CreateInputLayout();
	CreatePS();
}

void Game::Render()
{
	// 렌더를 위한 준비 작업
	RenderBegin();

	// TODO : 렌더
	// IA - VS - RS - PS - OM
	{
		// IA
		{
			// * stride : Vertex 구조체의 크기
			uint32 stride = sizeof(Vertex);
			// * offset : 보간 수치
			uint32 offset = 0;

			// 디바이스 컨텍스트를 이용해 IA에 정점 버퍼를 연결시켜줍니다.
			_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(), &stride, &offset);
			// 디바이스 컨텍스트를 이용해 IA에 InputLayout 정보를 연결시켜줍니다.
			_deviceContext->IASetInputLayout(_inputLayout.Get());
			// 삼각형(대부분 모든 사물은 삼각형으로 표현)을 그리는 과정에서 전달한 정점들을 어떤 순서로 이어 붙일 것인지에 대한 정보를 지정합니다. (topology)
			_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
		}

		// VS
		{
			// 디바이스 컨텍스트를 이용해 VS에 만든 셰이더를 연결시켜줍니다.
			_deviceContext->VSSetShader(_vertexShader.Get(), nullptr, 0);
		}

		// RS
		{

		}

		// PS
		{
			// 디바이스 컨텍스트를 이용해 PS에 만든 셰이더를 연결시켜줍니다.
			_deviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
		}

		// OM
		{

		}

		// 디바이스 컨텍스트를 이용해 해당 물체를 그려달라고 요청합니다.
		_deviceContext->Draw(_vertices.size(), 0);
	}

	// 최종 렌더 정보를 제출
	RenderEnd();
}

실행 결과 (좌 : 기본 테스트 | 우 : RS에 의한 색상 혼합 테스트)

profile
클라이언트 프로그래머 지망생

0개의 댓글