[DirectX 11로 Voxel Engine 만들기] 1일차

MS Choi·2022년 1월 6일
0

VoxelEngine

목록 보기
1/4
post-thumbnail

1일차에는 다음과 같은 내용을 진행했다.

  • 프로젝트 생성과 디렉토리정리및 프로젝트설정
  • DirectX 11 Device Class제작
  • Engine Core Class 기본 골격 제작
  • Sigleton Template Class 제작
  • 화면 클리어 작업 진행

프로젝트 관련 작업

일단 작업을 시작해야하니 프로젝트를 생성했다.
Client 프로젝트는 어플리케이션으로, Engine은 정적 라이브러리로 만들었다.
그후 각각 파일들을 출력할 디렉토리를 정리했다.
자세히 적을까 하다가, 사실 이건 사람마다 개인취향이라고 생각해 굳이 정리하는 의미가 없다는 생각이 들었다.

Sigleton Template Class 제작

main.cpp에서 게임루프를 만들어주고 나서 한 일은 Sigleton 클래스를 만드는 일이었다.

매니저급 클래스에서는 대부분 사용하기때문에 굉장히 자주 이용하는 디자인패턴중 하나이다. 그래서 상속을 이용해 만들수 있도록
sigleton패턴을 가진 부모클래스를 구현하기로 했다.

template<typename T>
class Singleton
{
public:
	static T& Get()
	{
		static T instance;
		return instance;
	}
private:
	Singleton()
	{}
	~Singleton() {}
};

여기서 템플릿으로 구현한 이유는 singleton의 패턴의 특징때문이다. private나 내가 작성한 방식으로 instance를 캡슐화 해버리기 때문에 일반적인 클래스로 구현하면 객체를 가져올 수가 없게된다.또 다형성이있다고해도 상속하고 실제 객체를 가져오려면 매번 다운캐스팅을 해줘야한다는 문제점이있다.

DirectX11 Device Class 제작

위에서 만든 singleton 클래스를 상속받아 제작을 했다.
풀코드를 올리기에는 너무 코드량이 많아서 헤더파일만 적어 구조에 대해 설명하겠다.

#include "singleton.h"
class DirectDevice :
    public Singleton<DirectDevice>
{
    SINGLE(DirectDevice);

public:
    HRESULT InitDevice(HWND _mainHwnd, Vec2 _resolution);
    void ClearTarget();
    void Present();
private:
    HRESULT CreateSwapChain();
    HRESULT CreateView();
    HRESULT CreateRasterizerState();
    HRESULT CreateDepthStencilState();
    HRESULT CreateSampler();

private:
	ComPtr<IDXGIAdapter> adapter_;
	ComPtr<ID3D11Device> device_;
	ComPtr<ID3D11DeviceContext> context_;

    ComPtr<IDXGISwapChain> swap_chain_;
    ComPtr<ID3D11Texture2D> depth_stencil_buffer_;

    ComPtr<ID3D11Texture2D> render_target_texture_;
    ComPtr<ID3D11Texture2D> depth_stencil_texture_;

    ComPtr<ID3D11RenderTargetView> render_target_view_;
    ComPtr<ID3D11DepthStencilView> depth_stencil_view_;
   
    D3D11_VIEWPORT view_port_;

    Vec2 resolution_;
    HWND hWnd_;
    bool window_;

};

나중에 좀더 최적화가 필요하다고 생각하긴 하지만, 일단 화면을 띄울수 있는 수준의 초기화를 목적으로 두고 구현했다.

아직 블렌드나 깊이평가 상태, 샘플러같은것도 구현이 안되어있어서 추후 구현할 예정이다.

여기서 주목해야할것은 ComPtr이다.

ComPtr은 Microsoft에서 제공해주는 스마트포인터로 std::shared_ptr와 매우 유사한 성질을 가지고 있다. 해당 변수를 참조하게되면, 참조 카운트가 증가하게 되고, 참조를 마치면 다시 감소하게된다. 이 참조 카운트가 0이 되어야지만 소멸자가 작동을 한다.

Direct X를 설정할때 사용되는 클래스들은 참조과정중 지워지거나 삭제되는 일이 발생하면 안되기 때문에 Smart Pointer로 관리하는게 여러므로 좋다.

Core Class

Engine의 전체적은 루프를 담당하는 코어클래스 이다. 마찬가지로 싱클턴으로 제작했다.

#include "singleton.h"
class EngineCore :
    public Singleton<EngineCore>
{
    SINGLE(EngineCore);

public:
    HRESULT Init(HWND  _hWnd, UINT _resolutionX, UINT _resolutionY);
    void Progress();
public:
    void ChangeWindowSize(UINT _x, UINT _y);
private:
    HWND main_hWnd_;
    Vec2 window_resolution_;
};

현재는 아주 간단한 구조를 가지고 있지만, 나중에 많은 매니저들의 초기화와 업데이트가 진행될 중요한 중추 역할을 한다.

여기까지 보면 작업이 오래 걸릴리가 없는데 생각보다 오래걸린 이유는 오랜만에 설정을 하다보니 DirectX 의 설정을 잘못진행해 조금 꼬였기 때문이었다. 그걸 해결하고 난다음에 테스트 하면서 한가지 오류를 겪었는데, 바로 RenderTarget과 DepthStecil의 해상도가 달라서 clear가 되지 않는 문제였다.

이를 해결하기 위해, 윈도우가 초기화되고나서 다시 화면크기를 내가 정한 해상도에 맞도록 설정해주는 작업을 진행했다. 그 작업은 ChangeWindowSize()를 확인해보면 된다.

main loop

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
...
    // 애플리케이션 초기화를 수행합니다:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }
    //Engine Initialize

    if (FAILED(EngineCore::Get().Init(g_hwnd, 1600, 900)))
    {
        MessageBox(nullptr, L"Core 초기화 실패", L"Engine 초기화 오류", MB_OK);
        return FALSE;
    }


HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_VOXELENGINE));

    MSG msg;

    // 기본 메시지 루프입니다:
    while (true)
    {
    //게임 루프
        if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
        {
            if (WM_QUIT == msg.message)
            {
                break;
            }
			if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
        }
        else
        {
            EngineCore::Get().Progress();
        }

    }
    return (int)msg.wParam;

   
}

메인 게임 루프 같은경우 기존 프로젝트를 생성했을때와 조금 다르게 만들어야한다.
게임 루프의 경우 Windows 작업이 진행되지 않는 시간동안 처리가 되기 때문에 PeekMessage값이 false일때 동작하도록 위와 같이 만든다.

이렇게 만드는 이유는 프로그램의 대부분이 windows 작업과 상관없시 사용되는 시간들이기 때문에 해당시간 동안 게임루프를 돌려 게임을 진행하고 이벤트가 발생하면 멈추고 끝나면 다시 게임이 진행된다.

작업 진행 결과

소감

오랜만에 다시 DirectX를 설정하려고 하니 머리가 하얗게 백지가 되어가는 경험을 느겼지만 하나하나 차근차근 하다보니 어느새 화면이 멋지게 나타났고, 그동안 그냥 지나쳤던 설정값하나하나의 의미를 다시한번 머릿속에 새길 수 있었던 좋은 경험이었다.

profile
다양한 경험을 하는걸 좋아하는 개발자입니다.

0개의 댓글