Post List

2017/11/27

DirectX11__Device_Setting

예전에 DirectX9을 배운 후 개인적으로 DirectX11을 공부하기 위해 여러 사이트나 블로그에서 튜토리얼을 보면서 나름 저만의 방식으로 따라 만들어보았었는데, 공부한 흔적을 남겨보고자 시작해보겠습니다 *_*

먼저 전체적으로 DirectX11 Device를 초기화하는 헤더파일을 보겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
namespace D3D11Engine {
    class _declspec(dllexport) CD3DDevice
    {
    public:
        DECLARE_SINGLETON(CD3DDevice)
    public:
        explicit CD3DDevice(void);
        ~CD3DDevice(void);
    public:
        HRESULT Init_Device(int screenWidth, int screenHeight, bool vsync, HWND hWnd, bool fullScreen, float Far, float Near);
    public:
        void BeginScene(float red, float green, float blue, float alpha);
        void EndScene(void);
        bool CheckDeviceLost(void);
    private:
        void Release(void);
    public:
        ID3D11Device* Get_Device(void);
        ID3D11DeviceContext* Get_DeviceContext(void);
    private:
        IDXGISwapChain*                m_pSwapChain;                    // 스왑 체인
        ID3D11Device*                   m_pDevice;                        // 디바이스
        ID3D11DeviceContext*             m_pDeviceContext;                // 디바이스 컨텍스트
        ID3D11RenderTargetView*        m_pRenderTargetView;
        ID3D11Texture2D*                 m_pDepthStencilBuffer;
        ID3D11DepthStencilView*        m_pDepthStencilView;
        ID3D11DepthStencilState*         m_pDepthStencilState;
        ID3D11RasterizerState*        m_pRasterState;
        ID3D11DepthStencilState*      m_pDepthDisabledStencilState;
        ID3D11BlendState*            m_pAlphaEnableBlendingState;
        ID3D11BlendState*            m_pAlphaDisableBlendingState;
    private:
        bool                            m_isVsync;                        // 수직동기화여부
        int                            m_iScreenWidth;
        int                            m_iScreenHeight;
    };
}
cs

CD3DDevice가 생성되고 바로 호출되는 함수는 Init_Device 입니다. 이 함수내에서 D3D에 대한 초기 설정이 이루어지는 함수로, 가장 중요하다고 볼 수 있습니다.
Init_Device에서 셋팅되는 변수들의 용도를 알아보겠습니다.

스왑체인에 대해 알아보기전 모니터 주사율에 대해 먼저 알고있어야합니다. 모니터를 구매할때 한번 쯤 '헤르츠' 또는 '60Hz' 이런 단어를 보았을겁니다.
헤르츠란 즉, 60Hz라는 모니터가 있을 때 이 모니터는 1초에 60번 새로운 화면을 띄우기 위해 깜빡거리는 횟수를 말합니다.
120Hz, 144Hz 숫자가 커질수록 1초에 갱신되어지는 횟수가 많아지며, 더욱 부드럽게 보이게 되지요.

하지만 무조건 모니터의 주사율만 가지고 부드러운 화면을 나타나기엔 어려움이 있습니다. 요즘엔 그래픽카드의 성능이 좋아져
120Hz의 프레임을 뽑아낼 경우 모니터가 60Hz의  주파수로밖에 성능을 내지 못한다면 오히려 화면이 찢겨버리는 티어링같은 현상이 발생된답니다.

이러한 현상을 개선하기 위한 방법이 '수직 동기화'라는 기능입니다. 수직 동기화라는 단어는 게임 옵션창에서 많이 보셨을겁니다.
수직 동기화는 자신의 모니터에 맞는 프레임을 유지하기 위해 일정 프레임 이상으로는 올라가지 않도록 제한을 두는 기능입니다.

모니터에 이러한 새로운 화면으로 다시 보여주는 버퍼를 프론트 버퍼라고 합니다. 프론트 버퍼 하나만 가지고 1초에 60번을 찍어내다보면
주사율보다 빠른 성능때문에 화면이 갱신되는 속도가 느리며, 화면 일부가 이전 장면이 남아있는 티어링 현상이 발생합니다.



그래서 이를 보완하기 위해 백버퍼라는 다른 버퍼에 새로 그려질 화면을 미리 준비한 후 프론트 버퍼가 화면에 그려질 준비가 완료되면 백버퍼를 프론트 버퍼로 전환하게 되는데,


이 방법을 스왑체인이라고 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
        DXGI_SWAP_CHAIN_DESC sd;
        ZeroMemory(&sd, sizeof(sd));
        sd.BufferCount = 1;                                    // 백버퍼 개수
        sd.BufferDesc.Width = screenWidth;                    // 백버퍼 가로                
        sd.BufferDesc.Height = screenHeight;                // 백버퍼 세로
        sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;    // 백버퍼 포맷 : RGBA 8비트이며 값의 범위 0.0 ~ 1.0
        sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;    // 백버퍼를 어떤 용도로 사용할 것인지
        sd.OutputWindow = hWnd;                                // 버퍼를 출력할 윈도우
        sd.SampleDesc.Count = 1;                            // 멀티 샘플링 수
        sd.SampleDesc.Quality = 0;                            // 멀티 샘플링 퀄리티
        sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
        sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; 
        sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; // 출력 후 백버퍼를 비운다
        sd.Flags = 0;
cs


스왑체인을 이용하기 위해 구조체를 먼저 채워 넣는 일을 하겠습니다. 스왑체인 구조체에 대한 변수를 선언하고 초기화 해줍니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
        if (m_isVsync)
        {
            // 수직동기화를 설정한다.
            // 백버퍼의 새로고침 비율을 설정합니다.
            sd.BufferDesc.RefreshRate.Numerator = numerator;
            sd.BufferDesc.RefreshRate.Denominator = denominator;
        }
        else
        {
            // 기본값으로 들어간다.
            sd.BufferDesc.RefreshRate.Numerator = 0;            
            sd.BufferDesc.RefreshRate.Denominator = 1;
        }
cs

아까 위에서 설명한 내용처럼 '수직동기화' 기능을 사용할시 백버퍼를 프론트 버퍼와 초당 몇회 전환을 할지 지정해 주는 부분입니다.

m_isVsync 값이 false 경우 최대한 많은 화면을 그려내게 될겁니다. 만약 모니터가 성능을 따라가지 못한다면 잔상이 남게 되겠죠.

1
2
3
4
5
6
7
8
9
        if (fullScreen)
        {
            // 풀모드로 설정한다.
            sd.Windowed = false;
        }
        else
        {
            sd.Windowed = true;
        }
cs

윈도우를 풀스크린 모드로 띄울것인지에 대한 설정도 해줍니다.

스왑체인 구조체에 대한 셋팅이 되었으니 스왑체인을 생성해보겠습니다.


1
2
3
4
5
6
7
8
        D3D_DRIVER_TYPE        driverType    = D3D_DRIVER_TYPE_HARDWARE;
        D3D_FEATURE_LEVEL    featureLevel    = D3D_FEATURE_LEVEL_11_0;
        hr = D3D11CreateDeviceAndSwapChain( nullptr, driverType,    nullptr, 0,    &featureLevel, 1, D3D11_SDK_VERSION, &sd, 
                                            &m_pSwapChain, &m_pDevice, nullptr, &m_pDeviceContext);
        if (FAILED(hr))
            return hr;
cs
featureLevel이란 어느 버전의 DirectX 사용할지에 대한 값을 가진 변수이며, 저는 DirectX11 사용할것이기 때문에 D3D_FEATURE_LEVEL_11_0 값을 지정해주었습니다.
D3D11CreateDeviceAndSwapChain 함수를 통해 스왑체인은 물론, 디바이스, 디바이스 컨텍스트까지 생성이 됩니다.

DirectX9에서는 디바이스 컨텍스트라는 부분을 따로 본적이 없지만 11 버전이 바뀌면서 기존 DirectX9에서 하나로 사용되던 Device DirectX11에서 Device, Device Context 두개로
기능이 분리되었다고 합니다.


여기까지 스왑체인, 디바이스, 디바이스 컨텍스트를 생성하는 부분이며 스왑체인을 만들었으니 스왑체인에 사용될 백버퍼를 만들어 보겠습니다.
1
2
3
4
5
6
7
8
9
10
        //-------------------------------------------------------------------------//
        // __백버퍼 설정_
        // 스왑체인으로부터 백버퍼를 취하여 디바이스의 렌더타켓으로 설정해야 한다.
        //-------------------------------------------------------------------------//
        // 스왑체인으로부터 백버퍼를 얻어온다.
        ID3D11Texture2D* pBackBuffer = nullptr;
        hr = m_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
        if (FAILED(hr))
            return hr;
cs

스왑체인으로 부터 GetBuffer 호출해 백버퍼를 얻어옵니다.


1
2
3
4
5
6
7
8
        // 텍스처는 파이프라인으로부터 뷰를 통하여 액세스하며 렌더타켓에는 렌더타켓 뷰를 사용한다.
        // 따라서 백버퍼를 얻어 왔으면 렌더 타켓 뷰를 생성한다.
        hr = m_pDevice->CreateRenderTargetView(pBackBuffer, nullptr, &m_pRenderTargetView);
        if (FAILED(hr))
            return hr;
        pBackBuffer->Release();
cs

// 백버퍼를 렌더타겟뷰로 생성하는것에 대한 부연설명 추가 ----!



깊이 버퍼와 스텐실 버퍼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
        D3D11_TEXTURE2D_DESC depthBufferDesc;
        ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));
        depthBufferDesc.Width = screenWidth;
        depthBufferDesc.Height = screenHeight;
        depthBufferDesc.MipLevels = 1;
        depthBufferDesc.ArraySize = 1;
        depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
        depthBufferDesc.SampleDesc.Count = 1;
        depthBufferDesc.SampleDesc.Quality = 0;
        depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
        depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
        depthBufferDesc.CPUAccessFlags = 0;
        depthBufferDesc.MiscFlags = 0;
        hr = m_pDevice->CreateTexture2D(&depthBufferDesc, nullptr, &m_pDepthStencilBuffer);
cs


렌더 타겟에 렌더링을 하려면 뷰포트의 설정이 필요합니다. 뷰포트는 렌더타겟의 렌더링 영역에 관한 설정이며,

렌더타겟당 설정해주어야 합니다.

1
2
3
4
5
6
7
8
        D3D11_VIEWPORT vp;
        vp.Width = static_cast<float>(screenWidth);
        vp.Height = static_cast<float>(screenHeight);
        vp.MinDepth = 0.f;
        vp.MaxDepth = 1.f;
        vp.TopLeftX = 0;
        vp.TopLeftY = 0;
        m_pDeviceContext->RSSetViewports(1&vp);        // 래스터라이저에 뷰 포트 설정
cs

기본적인 디바이스 셋팅을 마쳤습니다.

1
2
        void BeginScene(float red, float green, float blue, float alpha);
        void EndScene(void);
cs

마지막으로 함수에 대해 소개하겠습니다. 지금까지 화면에 그려지는 방법과 그려지기 위해서 필요한 값들을 설정하였는데요,

위에서 작업을 함수로 제어하게 됩니다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    void CD3DDevice::BeginScene(float red, float green, float blue, float alpha)
    {
        //-------------------------------------------------------------------------//
        // __렌더링 시작 설정_
        // 기존의 DirectX9와 비슷하지만 렌더타겟과 깊이, 스텐실 값을 Clear함수의 flag를 넣는 
        // 형태로 수행하던 형태가 아닌 따로 클리어 한다.
        //-------------------------------------------------------------------------//
        //Clear the Back Buffer
        float clearColor[4= { red, green, blue, alpha };
        // 클리어 할 렌더 타켓, 클리어 할 값
        m_pDeviceContext->ClearRenderTargetView(m_pRenderTargetView, clearColor);
        m_pDeviceContext->ClearDepthStencilView(m_pDepthStencilView,        // 클리어할 깊이, 스텐실 뷰
            D3D11_CLEAR_DEPTH,                                                // 깊이값만을 클리어 한다.
            1.0f,                                                            // 깊이 버퍼를 클리어 할 값
            0);                                                                // 스탠실 버퍼를 클리어 할 값
    }
cs

BeginScene 함수는 화면에 그리기 시작할 호출하는 함수로, 버퍼를 비우고 새롭게 다시 그려지도록 해주는 함수입니다.
인자로 RGBA 값을 받는데 인자로 받은 컬러값은 백버퍼를 어떤색으로 채울것인지에 대한 컬러이다.

예를 들어 (1,0,0,1) 값을 받는다면 아무것도 그려지지않은 백버퍼는 빨간색인셈이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    void CD3DDevice::EndScene(void)
    {
        //-------------------------------------------------------------------------//
        // __렌더링 종료 설정_
        // 렌더링 처리가 끝나면 스왑체인이 있는 백버퍼에 렌더링 처리를 수행한 후 렌더링 결과를 화면에 표시하려면
        // 스왑체인의 Present함수 호출한다.
        // 첫번째 인자는 화면을 갱신할 타이밍을 지정
        // 두번째 인자는 0을 지정
        // 갱신하는 타이밍에 0을 지정하면 즉시 화면이 갱신되며
        // 1 ~ 4의 값을 설정하면 설정한 값만큼 시연 간격을 가져 동기화처리를 수행한다.
        //-------------------------------------------------------------------------//
        m_pSwapChain->Present(00);
    }
cs

EndScene 함수는 화면에 그린 마지막에 호출하는 함수로, 백버퍼에 그린 화면을 프론트 버퍼로 전환하기 위한 함수입니다.