DirectX

17. 프레임워크 제작_Graphics

devRiripong 2023. 12. 30.
반응형

2D 물체를 화면에 그릴 수 있으면 생각보다 프레임 워크를 만드는 건 어렵지 않다.

입력이나, 시간을 잰다거나 하는 각종 매니저, 씬 매니저 이런 것들을 하나씩 추가를 하고, 2D도 텍스쳐 하나 띄우고 끝나는 게 아니라 애니메이션을 돌려야 하기 때문에 여러 개의 텍스쳐를 교체하면서 보이게 만들어줄 수 있으면 그게 2D 게임이라고 볼 수 있었다. 

여기까지 했다면 거의 2D 포폴은 만들 준비가 끝났다. 

오늘 할 것은 Game 클래스에 만든 내용들을 클래스 별로 분할해서 엔진같은 구조를 만들 거야. 
언리얼 엔진을 분석해도 실제로 이런 식으로 되어 있다. 

재배치하면서 복습하는 느낌으로 한다.
여기에 애니메이션, 데이터 관리 붙이면 거의 완성된 2D 엔진이 된다. 

transform 이라고 어떤 오브젝트가 다른 오브젝트 산하에 있거나, 여러 개 child를 갖고 있을 때 딸려서 움직이는데 그걸 구현한다. 

진지하게 게임을 만들 때 어떻게 만들어야 할까? 

어떤 건 한번만 만들고 언제 어디서나 걔만 계속 반복적으로 사용하는 애들이 있는가 하면, 
아니면 어떤 애들은 특정 클래스에 종속적인 애들이 있을 거야. 
몬스터가 100마리 등장한다면 정점 정보들 _vertices, _indices 이런 애들은 해골이 100마리여도 100개를 만들 필요는 없다는 생각이 든다. 

어떤 게 리소스가 되고, 어떤 게 객체별로 종속적으로 되는지를 생각을 해봐야 한다. 

해골이 100마리 있을 때 100마리 각각 다른 게 뭐가 있을까? 
일반적인 리소스가 아니라 Position, Rotation, Scale 같은 transform 정보의 경우는 당연히 해골마다 위치나 이런 게 다를 수밖에 없다. 이런 애들은 객체마다 있는 게 당연하다. 

하지만 Device, DeviecContext, SwapChain 같은 경우는 당연히 해골 개수랑 상관 없이 한 번만 만들어 주고 시작하면 된다. 
그리고 일부 부분들은 객체 별로, 해골은 한번만 만들어주고, 해골 객체마다 각기 Position, Rotation, Scale 들고 있게 사용하고 
이런 식으로 분리를 할 준비를 끝내야 한다.

분리를 한 다음에 조립을 하면 어디가 잘 못 되었는지 찾기가 힘들 거야. 지난번에 완료되었던 프로젝트를 기반으로 내용들을 하나씩 드러내는 방식으로 작업을 해볼 것이다. 

 

1. 필터 생성

 

Source Files Filter에 00. Engine 필터를 생성한다. 01. Main 필터에 넣다 빼면 맨 위에 위치하게 된다. 

구조는 나중에 다시 잡으면 되기 때문에 임시적인 것이다. 렌더링과 관련된 부분들을 엔진이라고 해서 여기다가 몰빵해 넣을 것이다. 

Engine안에 Pipeline이라는 필터를 만든다. 여기에 렌더링 파이프라인 관련된 것들을 넣을 것이다. 

Pipeline 필터 안에 새로운 클래스를 만들 건데 이름을 Graphics라고 한다.

Pipeline 필터 안에 필터를 여러개 만들어 주는데, 
00. Geometry
01. InputAssembler
02. VertexShader
03. Rasterizer
04. PixelShader
05. OutputMerger
우리의 목적은 Game클래스 안에 있는 모든 물체들을 다 클래스 분할을 해서 이 여섯 군데 중 하나에다가 열심히 배치를 해서 그게 최종적으로 똑같이 돌아가면 목표는 완료가 되었다고 볼 수 있는 거다. 

 

2. 공용으로 사용하는 Graphics


그중에서 이 Graphics라는 애는 가장 공용적으로 사용했던 부분들이다.

Device, DeviceContext, SwapChain, RenderTargetrView, Viewport같이 공용적으로 화면에 그리기까지  맨 처음에 만들었던 것을 역순서로 하나씩 타고 가면서 분리한다고 보면 된다. 
그럼 이 부분을 통으로 잘라서 붙여 넣자. 

 

#pragma once
class Graphics
{
public: 
	Graphics(HWND hwnd); 
	~Graphics(); 

	void RenderBegin(); 
	void RenderEnd(); 

	ComPtr<ID3D11Device> GetDevice() { return _device;  }
	ComPtr<ID3D11DeviceContext> GetDeviceContext() { return _deviceContext;  }

private: 
	void CreateDeviceAndSwapChain();
	void CreateRenderTargetView();
	void SetViewport();

private:
	HWND _hwnd;
	uint32 _width = 0;
	uint32 _height = 0;

private:
	// Device & SwapChain
	ComPtr<ID3D11Device> _device = nullptr;
	ComPtr<ID3D11DeviceContext> _deviceContext = nullptr;
	ComPtr<IDXGISwapChain> _swapChain = nullptr;

	// RTV
	ComPtr<ID3D11RenderTargetView> _renderTargetView; // 후면 버퍼를 묘사하는 존재

	// Misc
	D3D11_VIEWPORT _viewport = { 0 }; // 화면의 크기를 묘사
	float _clearColor[4] = { 0.f, 0.f, 0.f, 0.f };
};

 

이렇게 핵심 기능들을 옮겨 오자. 
각 함수들을 Create definition을 해준다. 
그리고 채워준다.
일부분은 그냥 Game.cpp에서 복붙해도 되고, 일부분은 수정을 해야 한다. 

 

CreateDeviceAndSwapChain은 그대로 복붙 할 것인데 Width와 Height는 전역 변수로 수정해 준다.

void Graphics::CreateDeviceAndSwapChain()
{
	DXGI_SWAP_CHAIN_DESC desc; // 안에 또 구조체가 있기 때문에 복잡하다. Device and SwapChain을 하고 있다에 집중하는게 좋다.
	ZeroMemory(&desc, sizeof(desc)); // desc를 0으로 밀어 버린다. ::memset(&desc, 0, sizeof(desc))이랑 마찬가지. 0으로 민 다음 필요한 걸로 채워줘야 해
	{
		desc.BufferDesc.Width = GWinSizeX;
		desc.BufferDesc.Height = GWinSizeY; // 화면이 800x600이니 버퍼도 똑같이 800x600으로 만들어 주는 거
		desc.BufferDesc.RefreshRate.Numerator = 60; // 화면 주사율
		desc.BufferDesc.RefreshRate.Denominator = 1; // 이름으로 때려 맞춰 보는 연습도 재밌다. 
		desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // 8비트x4짜리로 만들어 주겠다
		desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
		desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; // 지금 단계에선 신경 안써도 되는 옵션.
		desc.SampleDesc.Count = 1; // 계단 현상 줄일 때 숫자 늘일 수 있어.
		desc.SampleDesc.Quality = 0;
		desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 최종 결과물을 그려주는 역할로 사용하겠다는 옵션
		desc.BufferCount = 1; // 후면 버퍼 개수
		desc.OutputWindow = _hwnd; // 윈도우 핸들
		desc.Windowed = true;
		desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	}

	//_device.Get();  // ComPtr내부에서 관리하는 ID3D11Device를 꺼내고 싶을 때, 즉 ID3D11Device* 얘를 꺼내고 싶을 때 
	//_device.GetAddressOf(); //ID3D11Device*의 주소값 즉 ID3D11Device**를 얻고 싶을 때 

	HRESULT hr = ::D3D11CreateDeviceAndSwapChain(
		nullptr,
		D3D_DRIVER_TYPE_HARDWARE,	// 그래픽 카드 사용하겠다는 내용, 그래픽 카드가 없으면 다른 거 선택하게끔 하는 옵션 있어.
		nullptr,
		0,
		nullptr, // feature level 에 대한 배열을 만들어 건내줘야 한다. 몇 DX 버전 지원할것인지, 안채워도 지원 가능한 상위 버전 골라준다.
		0, // 배열크기, nullptr로 했으니 0개
		D3D11_SDK_VERSION, // 11버전에서 세부 버전이 몇번인지 매크로로 정의되어 있어.
		&desc, // 스왑체인 디스크립션을 위에서 만들어서 넣어준다.
		_swapChain.GetAddressOf(), // _device에다가 결국 결과물을 받아주는 거 
		_device.GetAddressOf(),
		nullptr,
		_deviceContext.GetAddressOf()
	);

	//assert(SUCCEEDED(hr)); // 실패하면 크래시 나게 유도할 수 있다.
	CHECK(hr);
}

이렇게 한번 이전 시키면서, 다시 한 번 순서를 머릿속으로 그려 보면서 복습을 하는 것을 권장한다. 

그다음엔 CreateRenderTargetView를 복붙 한다. 

void Graphics::CreateRenderTargetView()
{
	HRESULT hr;

	ComPtr<ID3D11Texture2D> backBuffer = nullptr;
	hr = _swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)backBuffer.GetAddressOf()); // 후면 버퍼를 Texture2D라는 티입으로 반환해 주는 거 
	// Texture는 png같은 건데 앞으로 어떻게 분석할지 모르는 거. RenderTargetView 용도로 사용할 것이라는 걸 명시해 줘야 해. 
	CHECK(hr);

	_device->CreateRenderTargetView(backBuffer.Get(), nullptr, _renderTargetView.GetAddressOf());
	CHECK(hr);
	// 흐름: SwapChain에서 후면 버퍼에 해당하는 리소스를 ComPtr<ID3D11Texture2D> backBuffer로 뱉어주고, 
	// 그거를 CreateRenderTargetView를 통해서 backBuffer를 묘사하는 _renderTargetView라는 형태로 만들어 줬고, 
	// GPU와 통신하면서 _renderTargetView를 건내주면 알아먹고, backBuffer에 그림을 그려주는 식으로 동작을 한다.
	// 일종의 스페셜한 포인터
}

 

그다음엔 SetViewport를 복붙 한다 

void Graphics::SetViewport()
{
	_viewport.TopLeftX = 0.f;
	_viewport.TopLeftY = 0.f;
	_viewport.Width = static_cast<float>(_width);
	_viewport.Height = static_cast<float>(_height);
	_viewport.MinDepth = 0.f;
	_viewport.MaxDepth = 1.f;
}

RenderBegin, RenderEnd도 복붙 한다.

void Graphics::RenderBegin()
{
	_deviceContext->OMSetRenderTargets(1, _renderTargetView.GetAddressOf(), nullptr);
	_deviceContext->ClearRenderTargetView(_renderTargetView.Get(), _clearColor);
	_deviceContext->RSSetViewports(1, &_viewport);
}
void Graphics::RenderEnd()
{// RenderBegin에서 후면 버퍼에 그려달라 요청한 거고
	// 여기서는 전면 버퍼에 복사를 해달라, 그리고 화면에 출력을 해달라 
	// [ ] <- [ ]
	HRESULT hr = _swapChain->Present(1, 0); // 그린 거를 제출하겠다.
	CHECK(hr);
}

 

나중에 가면 입맛에 맞게 고쳐야 할 필요가 생길 수가 있다. 

반대로 Game에서 옮긴 부분들을 제거해 준다.


그리고 Game.h에서 Graphics 클래스를 사용할 때에는 

private:
	HWND _hwnd;
//	uint32 _width = 0;
//	uint32 _height = 0;

	shared_ptr<Graphics> _graphics;

원래라면 Graphics를 전방선언 하고, cpp에서 헤더 추가하고 하겠지만 
자주 사용할 것이기 때문에 pch.h에 엔진과 관련된 모든 래퍼 클래스들을 만들어준다.

// Engine
#include "Graphics.h"

이런 느낌으로 추가해 준다.

주의 할 점은 #include <d3d11.h>, #include <wrl.h>보다 아래에 추가해야 한다. 위에 추가하면 error가 발생한다.

 

shared_ptr <Graphics> _graphics;를 사용할 때 Game.cpp에서 불필요한 내용들을 싹 제거해 준다.

Game::Init에서  _graphics = make_shared <Graphics>(hwnd); 를 호출하고, 

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

를 제거한다.

 

다만 Graphics에서 생성자를 호출할 때 hwnd를 받아서 위 삼총사를 알아서 세팅하게끔 되어 있다. 

이제 Game.cpp에서 에러 나는 부분을 찾아서 수정하면 된다. 

 

_deviceContext부분은 _graphics->GetDeviceContext()로 바꿔서 임시로  처리한다. 
나중에 가면 또 위치가 옮겨질 수 있다. 

 

에러 나는 부분은 _graphics-> 이렇게 붙여서 수정할 수 있다. 

 

Game::Render()에 _deviceContext가 너무 많이 나오는데

auto _deviceContext = _graphics->GetDeviceContext();

이 코드를 임시로  넣어주면 된다. 
Render의 이 부분들도 나중에 Pipeline이라는 새로운 클래스로  옮겨 갈 것이다. 

 

Game::Render에서
RenderBegin, RenderEnd 사이에 그려줄 물체를 정해주고 있는데, 물체마다 이 작업을  반복해야 하기 때문에 Game.cpp에 몰아넣어 두는 건 옳은 방향이 아니다. 물체마다 깔끔하게 관리할 수 있는 방법을 언젠가는 찾아야 한다. 

 

Game::RenderBegin, Game::RenderEnd 구현부는 삭제해주고, 

_device는 _graphics->GetDevice()로 수정한다.

 

빌드를 해보면 문제가 있다. shared_ptr에 문제가 발생하는데 일단 일반 Graphics를 일반 포인터로 해서 진행을 해보자. 

 

Game.h에서 shared_ptr <Graphics> _graphics;를 삭제하고,

Graphics* _graphics;

 

Game::Init에서는 

// _graphics = make_shared<Graphics>(hwnd); 
	_graphics = new Graphics(hwnd);

이렇게 바꿔준다. 

 

빌드를 해보면 빌드가 된다. 

근데 이대로 실행하면 Viewport 사이즈가 0되어서 이미지가 안 보이기 때문에 

Graphics::Graphics(HWND hwnd)
{
	_hwnd = hwnd; 

	_width = GWinSizeX; 
	_height = GWinSizeY;
    
  	CreateDeviceAndSwapChain(); 
	CreateRenderTargetView(); 
	SetViewport(); 
}

이렇게 값을 넣어 주고 함수들도 호출해 었다. 

수정 전과 똑같이 나온다. 

반응형

'DirectX' 카테고리의 다른 글

19. 프레임워크 제작_Geometry  (0) 2024.01.02
18. 프레임워크 제작_InputAssembler  (0) 2024.01.01
16. SimpleMath 실습  (0) 2023.12.29
15. Screen 변환 행렬  (0) 2023.12.22
14. Projection 변환 행렬  (0) 2023.12.22

댓글