본문 바로가기
DirectX

[Direct x 11] 스터디 2일 Directx 3D초기화

by Minok_nok 2020. 1. 19.

Direct x 11 초기화

 

참고 블로그

https://copynull.tistory.com/

 

Thinking Different

Thread-safe 보다는 Reentrant 하게

copynull.tistory.com

이 블로그를 보며 작성한 글입니다.

이 게시글을 기준으로 보는 걸 추천드립니다.

프레임 워크 확장

1주 차의 프레임 워크를 확장하여 새로운 클래스를 추가 해 줍시다.

 

GraphicsClass가 컨트롤하는 D3DClass가 생겼습니다.


COM(Component Object Model)

이번에 Win API를 넘어 Directx에 관한 메서드와 클래스를 사용합니다.

Directx는 독립적으로 구성요소들을 COM이라는 인터페이스 형식으로 제공됩니다.

 

COM 객체는 c++의 new로 생성할 수 없으며, COM의 특정한 함수를 호출하여 얻어야 됩니다.

 

삭제할 때도 delete가 아닌 COM의 Release라는 메서드를 호출하여 삭제해야 합니다.

이는 모든 COM 인터페이스가 IUnknown이라는 COM인터페이스 기능을 상속받는데, 여기서 해당 메서드를 제공해야 하기 때문이고, 이 객체들이 고유한 방식으로 메모리를 관리하기 때문입니다.

 

앞으로의 Directx 관련 클래스 이름에 ‘I클래스 이름’ 형식으로 표현이 되는 경우도 이와 같습니다.

 

https://zerapix.tistory.com/entry/1-DirectX-DirectX%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-1

 

1. DirectX - DirectX란 무엇인가?

현재 DirectX를 공부하는 중이라, 공부하는 겸 나중에도 적당히(?) 써먹고자 쓰고 있습니다. 부족한 점은 피드백 부탁드립니다. ---------------------------------------- 1. DirectX란? 혹시 Unity나 언리얼을..

zerapix.tistory.com

에서 자세한 정보를 볼 수 있습니다.


DxDefine.h 업데이트

전에는 쓰이지 않던 DxDefine헤더를 정의해 줍니다.

이 안에는 Directx에 관한 헤더들을 참조합니다.

 

#pragama once

/////////////
// LINKING //
/////////////
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3dcompiler.lib")

//////////////// INCLUDES ////////////////
#include <d3d11.h>
#include <d3dcompiler.h>
#include <directxmath.h>
using namespace DirectX;
/////////////////////////// warning C4316 처리용 ///////////////////////////
#include "AlignedAllocationPolicy.h"

DxDefine.h 업데이트: pragma comment

pragma commnet는 명시적인 라이브러리의 링크의 목적으로 사용이 됩니다.

 

라이브러리 파일, 즉 lib확장자의 파일은 IDE 설정에서 링크를 할 수 있지만, 코드에서 보여주기 위해 pragma comment를 사용하기도 합니다.

 

프로젝트>설정에서 링크할 수 있다.

자세한 사항은 https://semoa.tistory.com/987 에서도 확인할 수 있다.

 

[펌] pragma에 관한 사용법

아래 내용중에서 필요한 것은, #pragma comment() 이 중 가장 대표적인 사용법은 명시적인 라이브러리의 링크이다. #pragma comment(lib, "xxxx.lib") 와 같이 사용하여 해당 라이브러리를 링크시켜 준다. 여러사..

semoa.tistory.com

 

DxDefine.h에서 쓰인 뜻은 d3d11, dxgi, d3dcompiler 라이브러리를 쓸 예정이니 링크를 시켜달라는 뜻으로 작성되었습니다.


DxDefine.h 업데이트: Directx관련 헤더 추가 및 namespace 생략

 
#include <d3d11.h>
#include <d3dcompiler.h>
#include <directxmath.h>
using namespace DirectX;

 

위에서 추가시켜주었던 라이브러리 안에 있는 헤더 파일을 추가시킨 후, DirectX 관련 요소들이 DirectX namespace 안에 있기 때문에 미리 DirectX namespace를 생략시켜주었습니다. 


AlignedAllocationPolicy.h

#pragma once

// warning C4316 처리용
template<size_t Alignment>
class AlignedAllocationPolicy
{
  public:
  static void* operator new(size_t size)
  {
  return _aligned_malloc(size, Alignment);
  }

  static void operator delete(void* memory)
  {
  _aligned_free(memory);
  }
};

 

이는 혹시 모를 오류를 막기 위한 용도로 사용이 됩니다.

자세한 사항은 링크를 참조해 주세요

https://copynull.tistory.com/241

 

[DirectX11] Warning - warning C4316 에러 문제

DirectX SDK 듀토리얼을 진행하면서 컴파일 과정에서 문제점을 하나 발견하게 되었습니다. 아래와 같이 컴파일시에 warning C4316 을 뱉어낸다는 문제점입니다. alignment error(C4316) 는 무엇인가? (MSDN 내용..

copynull.tistory.com

 


GraphicsClass 헤더 파일

#pragma once

/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


class D3DClass;

class GraphicsClass
{
  public:
  GraphicsClass();
  GraphicsClass(const GraphicsClass&);
  ~GraphicsClass();

  bool Initialize(int, int, HWND);
  void Shutdown();
  bool Frame();

  private:
  bool Render();

  private:
  D3DClass* m_Direct3D = nullptr;
};


멤버 함수가 비어 있던 GraphicsClass.h의 내용을 추가했습니다.


const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;

 

여러 멤버 함수의 추가입니다.

 

이의 사용처는 cpp파일을 보며 알아갑시다.

 

class D3DClass;

private:
D3DClass* m_Direct3D = nullptr;

 

GraphicsClass가 관장할 D3DClass를 미리 선언시켜 줍니다.



GraphicsClass 소스파일

#include "stdafx.h"
#include "d3dclass.h"
#include "graphicsclass.h"


GraphicsClass::GraphicsClass()
{
}


GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}


GraphicsClass::~GraphicsClass()
{
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
  // Direct3D 객체 생성
  m_Direct3D = new D3DClass;
  if(!m_Direct3D)
  {
  	return false;
  }

  // Direct3D 객체 초기화
  if(!m_Direct3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR))
  {
    MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
    return false;
  }

  return true;
  }


  void GraphicsClass::Shutdown()
  {
  // Direct3D 객체 반환
    if(m_Direct3D)
    {
      m_Direct3D->Shutdown();
      delete m_Direct3D;
      m_Direct3D = 0;
    }
}


bool GraphicsClass::Frame()
{
  // 그래픽 랜더링 처리
  return Render();
}


bool GraphicsClass::Render()
{
  // 씬을 그리기 위해 버퍼를 지웁니다
  m_Direct3D->BeginScene(0.5f, 0.5f, 0.5f, 1.0f);

  // 버퍼의 내용을 화면에 출력합니다
  m_Direct3D->EndScene();

  return true;
}


많은 일들이 D3DClass에서 일어나기 때문에 돌아가는 순서만 보도록 하겠습니다.


먼저 SystemClass에서 호출되는 초기화입니다.

bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
  // Direct3D 객체 생성
  m_Direct3D = new D3DClass;
  if(!m_Direct3D)
  {
  	return false;
  }

  // Direct3D 객체 초기화
  if(!m_Direct3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR))
  {
    MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
    return false;
  }

  return true;
}


매개 변수로 화면의 해상도 및 윈도우 핸들을 받습니다.

그 후 D3DClass를 생성 및 초기화를 해줍니다.

 

해상도와 핸들 말고도 처음 보는 변수들은 나중에 확인해 봅시다.


bool GraphicsClass::Frame()
{
  // 그래픽 랜더링 처리
  return Render();
}


bool GraphicsClass::Render()
{
  // 씬을 그리기 위해 버퍼를 지웁니다
  m_Direct3D->BeginScene(0.5f, 0.5f, 0.5f, 1.0f);

  // 버퍼의 내용을 화면에 출력합니다
  m_Direct3D->EndScene();

  return true;
}

SystemClass에서 입력이 없을 때 호출되는 Frame함수입니다.

Frame함수에서는 일단 Render함수를 호출하고, D3DClass 형태의 변수인 m_Direct3D에게 씬 시작과 종료에 관한 함수를 호출합니다.


void GraphicsClass::Shutdown()
{
  // Direct3D 객체 반환
  if(m_Direct3D)
  {
    m_Direct3D->Shutdown();
    delete m_Direct3D;
    m_Direct3D = 0;
  }
}

SystemClass가 소멸할 때 호출되는 Shutdown함수입니다.

m_Direct3D가 생성되어 있을 때 Shutdown함수를 호출하고 삭제를 해 줍니다.

 

GraphicsClass는 상당히 간결한 내용으로 구성되어 있습니다.

 

이제 DirectX의 기능을 사용하기 시작하는 D3DClass를 파헤쳐 봅시다.

 


D3DClass 헤더파일

본격적으로 DirectX 관련 요소들이 들어가 있는 D3DClass를 알아봅시다.

#pragma once

class D3DClass
{
  public:
    D3DClass();
    D3DClass(const D3DClass&);
    ~D3DClass();

    bool Initialize(int, int, bool, HWND, bool, float, float);
    void Shutdown();

    void BeginScene(float, float, float, float);
    void EndScene();

    ID3D11Device* GetDevice();
    ID3D11DeviceContext* GetDeviceContext();

    void GetProjectionMatrix(XMMATRIX&);
    void GetWorldMatrix(XMMATRIX&);
    void GetOrthoMatrix(XMMATRIX&);

    void GetVideoCardInfo(char*, int&);

  private:
    bool m_vsync_enabled = false;
    int m_videoCardMemory = 0;
    char m_videoCardDescription[128] = { 0, };
    IDXGISwapChain* m_swapChain = nullptr;
    ID3D11Device* m_device = nullptr;
    ID3D11DeviceContext* m_deviceContext = nullptr;
    ID3D11RenderTargetView* m_renderTargetView = nullptr;
    ID3D11Texture2D* m_depthStencilBuffer = nullptr;
    ID3D11DepthStencilState* m_depthStencilState = nullptr;
    ID3D11DepthStencilView* m_depthStencilView = nullptr;
    ID3D11RasterizerState* m_rasterState = nullptr;
    XMMATRIX m_projectionMatrix;
    XMMATRIX m_worldMatrix;
    XMMATRIX m_orthoMatrix;
};


처음보는 클래스들이 많습니다.

하나 하나씩 파헤쳐보면서 Directx의 구성요소에 대해 알아봅시다.


D3DClass 헤더파일: IDXGI와 IDXGISwapChain

IDXGISwapChain에 대해 알아보기 위해서 먼저 DXGI에 대해 알아 보아야 합니다.

 

DXGI

DXGI는 Direct 11등 여러 그래픽스 기능이나 어플리케이션으로부터 오는 표시를 받아 커널 모드 드라이버나 하드웨어와 주고받는 역할을 합니다.

 

 

더보기

유저 모드와 커널모드

커널 모드에서는 중요한 자원을 관리하기 때문에 사용자가 자원에 접근하지 못하도록 모드를 나누어 놨습니다.

 

유저모드

유저가 접근할 수 있는 영역을 제한적으로 두고, 프로그램의 자원에 침범을 못합니다.

보통 코드를 작성하고, 프로세스를 실행합니다.

 

커널모드

모든 자원(드라이버, 메모리, CPU 등)에 접근, 명령을 할 수 있습니다.

DirectX에서도 자원을 할당하고 받아올 때 커널모드를 통해 자원을 받아옵니다.

DXGI를 직접 조작할 필요가 있는 경우는 다음과 같다.

 

  • 그래픽스 카드 선택
  • 윈도우 사이즈 갱신 시 대응(백버퍼 사이즈 갱신)
  • 윈도우 사이즈 갱신 (디스플레이 모드 갱신)
  • 필요없는 화면 렌더링 제어
  • 화면 모드 전환( 풀 스크린 모드 <-> 윈도우 모드)
  • 톤 커브에 의한 계조 보정 등등

DXGI에서 사용하는 인터페이스는 크게 4가지로 나눠집니다.

IDXGIFactory

DXGI를 사용하기 위해 필요한 각종 인터페이스를 얻어오는 인터페이스

DXGI의 기능을 사용하는 경우는 우선 CreateDXGIFactory() 함수를 사용하여 인터페이스를 얻어옵니다.

IDXGIAdapter

그래픽스 카드 관련 기능을 IDXGIAdapter인터페이스에서 다룹니다.

이것은 반드시 독립된 카드가 아닌 칩셋에 내장된 그래픽스 기능인 경우도 있습니다.

IDXGIOutput

그래픽스 카드에 연결된 디스플레이 관련 기능을 IDXGIOutput 인터페이스에서 다룹니다.

이 인터페이스를 사용함으로써 대응하고 있는 디스플레이모드(해상도나 Refresh 모드) 또는 감마 설정 등을 할 수 있습니다.

IDXGISwapChain

화면을 표시하는 스왑체인의 기능을 다룹니다.

이 인터페이스는 D3D11CreateDeviceAndSwapChain 함수로 DirectX11에 ID3D11Device 인터페이스를 얻을 때에 같이 얻을 수 있습니다.

 

자세한 설명은

https://m.blog.naver.com/PostView.nhn?blogId=sorkelf&logNo=40161605210&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 

DirectX 11 기초 (Tutorial) - DXGI 기능 ( 윈도우 사이즈 갱신, 화면 전환, 그래픽 환경 조사)

DXGI DXGI : DirectX Graphic Infrastructure 특징편에서 간단히 언급하였지만 DirectX 11은...

blog.naver.com

이곳에서 볼 수 있습니다.

 

SwapChain

엔진에서 GPU가 프레임 버퍼에 이미지를 그리는 동안 비디오 컨트롤러가 GPU가 이미지를 그리고 있는 프레임 버퍼를 참조하여 화면에 출력 할 때 덜 그려져 화면이 깜빡거리는 현상이 발생하게 된다.

 

이를 해결하기 위해 GPU가 그릴 프레임 버퍼(후면 버퍼)와 화면에 출력할 프레임 버퍼(전면 버퍼)를 두 개를 만들어 두고, 계속 이 둘을 교체하는 방식을 SwapChain이라고 한다.

 

SwapChain의 순서는

  1. GPU는 후면 버퍼에 이미지를 그린다.

  2. 후면 버퍼에 그리기가 완료되면 전면 버퍼와 후면 버퍼를 교환한다.

  3. 비디오 컨트롤러가 해당 전면 버퍼를 참조해 스크린을 갱신한다.

이를 이중 버퍼링이라고도 하며, 이 버퍼의 개수를 늘려도 되지만 보통 2개의 버퍼만 준비해도 깜빡거림 현상이 해결된다.

 

https://hoodymong.tistory.com/51 이곳에서 자세한 설명을 볼 수 있습니다.

 

[DirectX] 더블 버퍼링(Double Buffering)과 스왑 체인(Swap Chain)

컴퓨터 디스플레이에서 이미지를 출력할 때에는 일반적으로 이전 프레임을 모두 지우고(clear) 새로이 다시 그리게 된다. 이전 프레임과 현재 프레임의 차이를 계산해서 해당 부분만 갱신하는 것이 비효율적이기..

hoodymong.tistory.com


D3DClass 헤더파일: ID3D11Device

 

ID3D11Device는 GPU를 제어할 수 있는 인터페이스라고 보면 됩니다.

 

ID3D11Device 인터페이스는 기능 지원 점검과 자원 할당에 쓰입니다.

 

D3D11CreateDevice 또는  D3D11CreateDeviceAndSwapChain으로 생성 할 수 있습니다.

 

https://docs.microsoft.com/en-us/windows/win32/api/d3d11/nn-d3d11-id3d11device

 

ID3D11Device (d3d11.h) - Win32 apps

The device interface represents a virtual adapter; it is used to create resources.

docs.microsoft.com

에서 자세한 설명을 볼 수 있습니다.


D3DClass 헤더파일: ID3D11DeviceContext

 

ID3D11DeviceContext도 GPU를 제어할 수 있는 인터페이스입니다.

 

렌더 대상을 설정하고, ID3D11Device로 할당된 자원을 그래픽 파이프 라인에 묶고, GPU가 렌더링 명령을 지시하도록 합니다.

 

D3D11CreateDevice 또는  D3D11CreateDeviceAndSwapChain으로 생성 할 수 있습니다.

 

https://docs.microsoft.com/en-us/windows/win32/api/d3d11/nn-d3d11-id3d11devicecontext

 

ID3D11DeviceContext (d3d11.h) - Win32 apps

The ID3D11DeviceContext interface represents a device context which generates rendering commands.

docs.microsoft.com

에서 자세한 설명을 볼 수 있습니다.


D3DClass 헤더파일: ID3D11RenderTargetView

 

IDXGISwapChain에서 SwapChain을 통해 렌더 버퍼를 교체한다고 했습니다.

이 때 특정 버퍼를 렌더를 하도록 타게팅을 합니다

그럴 때 ID3D11RenderTargetView를 사용합니다.

 

https://vsts2010.tistory.com/517

 

[알콜코더의 미리 배워보는 DX11-입문편] 1.튜터리얼01:백버퍼의 설정 #1

참고 소스 : DirectX SDK – DirectX 11 Tutorial 02 위 소스를 기반으로 연재를 진행합니다. 연재외에 자세한 내용은 소스와 DX SDK 문서를 참조하시면 도움이 됩니다. 안녕하세요. 알콜코더 민군입니다. ^^ 지..

vsts2010.tistory.com

이곳에서 자세한 설명이 들어가며, 이를 사용할 때 더욱 자세하게 알아봅시다.


D3DClass 헤더파일: XMVECTOR / XMMATRIX

 

Directx에서는 XMVECTOR이라는 벡터형식을 씁니다.

SIMD 하드웨어 레지스터에 대응됩니다.

더보기

SIMD

SIMD(Single Instruction Multiple Data)는 하나의 명령어로 여러개의 데이터를 한번에 처리하는 기법입니다.

 

일반적인 프로그래밍 방식인 SISD(Single Instruction Single Data)보다 특정 데이터 형식에서는 빠르게 연산을 할 수 있습니다.

 

https://stonzeteam.github.io/SIMD-%EB%B3%91%EB%A0%AC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/

 

SIMD 병렬 프로그래밍

SIMD 병렬 프로그래밍이 무엇인지 알아봅시다.

stonzeteam.github.io

 

XMMATRIX는 XMVECTOR로 이루어져 있는 행렬입니다.

 

이 행렬들은 세상을 보여주기 위한 요소들중 하나입니다.


D3DClass 클래스파일 초기화

 

D3DClass의 코드 양이 많은데 그 중 초기화인 Initialize에 코드가 집중되어 있기때문에 초기화 부터 봅시다.

 

D3DClass 클래스파일 초기화: 수직 동기화

//수직 동기화 상태를 저장합니다
m_vsync_enabled = vsync


Initialize함수에서 바로 보이는 수직동기화 상태 저장 부분입니다.
 

 

수직동기화는 그래픽 카드의 프레임 생성과 모니터의 프레임 출력 타이밍을 맞추도록 하는 설정입니다.

 

이가 서로 맞지 않을 때엔 테어링이라는 화면 깨짐 현상이 발생하며, 보통 프로그램의 유저가 설정합니다.

 

https://namu.wiki/w/%EC%88%98%EC%A7%81%EB%8F%99%EA%B8%B0%ED%99%94

 

수직동기화 - 나무위키

다음과 같은 특징을 보고 필요한 쪽을 선택하면 된다. 장점단점수직동기화테어링 제거, GPU 사용량 제한, 스터터링 감소인풋랙 증가테어링 제거, 프레임 드랍 완화, 스터터링 완화인풋랙 추가 증가테어링 제거, 수직동기화에 비해 인풋랙 완화GPU 사용량 제한 불가프레임제한GPU 사용량 제한, 스터터링 없음, 인풋랙 적음테어링 발생, 마이크로 스터터링동적 프레임제한[17]GPU 사용량 제한, 스터터링 없음, 제한적인 마이크로스터터링감소, 인풋랙 없음테어링 제거

namu.wiki

 

// 백버퍼의 새로고침 비율을 설정합니다
if (m_vsync_enabled)
{
  swapChainDesc.BufferDesc.RefreshRate.Numerator = numerator;
  swapChainDesc.BufferDesc.RefreshRate.Denominator =     denominator;
}
else
{
  swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
  swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
}

수직동기화를 사용하면 이곳에서 사용이 되는데 이는 후에 더 알아 봅시다.


D3DClass 클래스파일 초기화: IDXGIFactroy설정

 

위에서 설명했듯이 Directx로 커널모드로 자원을 가져오기 위해서는 DXGI를 사용해야 합니다.

// DirectX 그래픽 인터페이스 팩토리를 생성합니다
IDXGIFactory* factory = nullptr;
if (FAILED(CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory)))
{
	return false;
}


DXGI중 IDXGIFactory입니다.
 

 

CreateDXGIFactory / __uuidof

CreateDXGIFactory 함수로 초기화를 시켜줍니다

HRESULT CreateDXGIFactory(
  REFIID riid,
  void   **ppFactory
);

CreateDXGIFactroy의 형태입니다.

CreateDXGIFactory함수의 인자를 보면  __uuidof(IDXGIFactoy) 부분이 생소합니다.

 

IDXGIFactory의 GUID를 반환한다는 뜻인데, 마이크로 소프트의 COM에서는 GUID를 인터페이스들을 구별하기 위해 사용됩니다.

 

즉 서로 호환되지 않을 수 있는 두개의 컴포넌트가 동일한 인터페이스 이름을 사용하더라도, 각각의 인터페이스는 언제나 고유한 GUID를 갖기 때문에 구별할 수 있게 됩니다.

 

https://ko.wikipedia.org/wiki/%EC%A0%84%EC%97%AD_%EA%B3%A0%EC%9C%A0_%EC%8B%9D%EB%B3%84%EC%9E%90

 

전역 고유 식별자 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 전역 고유 식별자(全域固有識別子, 영어: Globally Unique Identifier, GUID)는 응용 소프트웨어에서 사용되는 유사난수이다. GUID는 생성할 때 항상 유일한 값이 만들어진다는 보장은 없지만, 사용할 수 있는 모든 값의 수가 2128 = 3.4028×1038개로 매우 크기 때문에, 적절한 알고리즘이 있다면 같은 숫자를 두 번 생성할 가능성은 매우 적다. GUID는 오라클 데이터베이스 등 많은 곳에서

ko.wikipedia.org

에서 GUID에 대한 설명을 볼 수 있습니다.

 

CreateDXGIFactroy의 인자 중 REFIID는 GUID 중 인터페이스 식별자인 IID를 COM에서는 REFIID형태로 받습니다.

 

그 뒤에 CreateDXGIFactory로 생성된 IDXGIFactroy를 받아 올 포인터를 void 이중 포인터로 받아옵니다.

 

반환 될 팩토리가 IDXGIFactroy가 아닌 다른 형태일 경우가 있기 때문에 void 이중 포인터로 받습니다.

 

https://m.blog.naver.com/PostView.nhn?blogId=skout123&logNo=50131421237&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 

[스카웃 C언어 강좌] 23-3 void 이중 포인터

[스카웃 C언어 강좌] 23-3 void 이중 포인터 머리말안녕하세요. Programog를 운...

blog.naver.com

void 이중 포인터에 관한 글입니다.

 

FAILED

이 함수가 정상적으로 작동했는지 FAILED매크로로 판단을 합니다.

 

CreateDXGIFactroy같은 COM관련 함수들은 HRESULT를 반환하는데 이를 FAILED매크로로 체크 할 수 있습니다.

 

https://yoshiki.tistory.com/entry/HRESULT-%EC%97%90-%EB%8C%80%ED%95%9C-%EB%82%B4%EC%9A%A9

 

HRESULT 에 대한 내용

COM을 사용하는 대부분의 함수들과 마찬기지로 CoInitializeEx() 함수는 HRESULT 값을 리턴한다. HRESULT는 함수의 호출 결과를 나타내는 32비트의 값을 표현하며 함수가 성공적으로 호출된 경우 S_OK를 리턴하는..

yoshiki.tistory.com

여기서 자세한 설명을 볼 수 있습니다.


D3DClass 클래스파일 초기화: 디스플레이 설정

IDXGIAdapter

// 팩토리 객체를 사용하여 첫번째 그래픽 카드 인터페이스 어댑터를 생성합니다
IDXGIAdapter* adapter = nullptr;
if (FAILED(factory->EnumAdapters(0, &adapter)))
{
	return false;
}

그래픽스 카드 관련된 정보를 사용하기 위해 IDXGIAdapter 인터페이스를 사용합니다. 

생성한 IDXGIFactroy 인터페이스의 EnumAdapters함수로 어댑터를 생성하여 받아옵니다.

 

첫번째 매개변수는 0부터 시작하는 인덱스 번호를 지정할 수있습니다.

만약 지정한 인덱스에서 사용가능한 어댑터가 없는 경우 실패합니다.

 

IDXGIOutput

// 출력(모니터)에 대한 첫번째 어댑터를 지정합니다.
IDXGIOutput* adapterOutput = nullptr;
if (FAILED(adapter->EnumOutputs(0, &adapterOutput)))
{
	return false;
}

먼저 디스플레이 관련정보를 얻기 위해 IDXGIOutput을 초기화를 시켜줍니다. 

EnumOutputs도 EnumAdapters와 비슷한 형식의 메서드를 사용합니다.

 

이렇게 초기화 된 IDXGIOutput을 이용하여 디스플레이 관련정보를 얻을 수 있습니다.

 

그 후 디스플레이 모드의 정보들을 가져올건데

모드 수 가져오기 -> 모드 수에 맞게 모드 리스트 선언 -> 모드 리스트 초기화의 순서로 가져옵니다.

// 출력 (모니터)에 대한 DXGI_FORMAT_R8G8B8A8_UNORM 표시 형식에 맞는 모드 수를 가져옵니다
unsigned int numModes = 0;
if (FAILED(adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL)))
{
	return false;
}

IDXGIOutput의 GetDisplayModeList를 이용하여 디스플레이의 모드 수를 가져옵니다. 

 

첫번째 인자는 열거시킬 색상 포맷입니다.

두번째는 디스플레이 모드를 열거하기 위한 옵션입니다.

 

DXGI_ENUM_MODES_INTERLACED는 인터레이스 모드를 포함하라는 뜻이며, 인터레이스 모드는 이쪽에 설명이 되어 있다.

 

https://macinjune.com/all-posts/mac/tip/final-cut/1080i-vs-1080p-%EC%9D%B8%ED%84%B0%EB%A0%88%EC%9D%B4%EC%8A%A4%EB%93%9C%EC%99%80-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%A0%88%EC%8B%9C%EB%B8%8C-%EB%B0%A9%EC%8B%9D%EC%9D%B4%EB%9E%80/

 

1080i vs 1080p? 인터레이스드와 프로그레시브 방식이란? - Mac In June

동영상 해상도를 이야기할 때 인터레이스드(i)와 프로그레시브(p) 방식에 대한 차이에 관한 내용입니다. 요즘은 누구나 다 고화질의 동영상 컨텐츠를 어렵지 않게 제작할 수 있는 시대이며, 인터넷 방송 혹은 유튜브와 같은 온라인 동영상 매체들은 해를 거듭해 성장하고 있습니다. ‘1080i’ 혹은 ‘1080p’ 등의 해상도를 나타내는 용어는 어디서나 쉽게 확인이 가능하죠. 여기서 사용되는 기호 ‘i’와 ‘p’의 차이를 알고 계시면 후에 …

macinjune.com

 

세번째는 첫번째와 두번째에서 지정했던 디스플레이 포맷과 옵션을 기준으로 디스플레이 모드의 개수를 반환해 줍니다.

반환 할 변수가 없다면 NULL로 넣어줍니다.

 

네번째는 디스플레이 모드의 리스트를 가져옵니다.

반환 할 변수가 없다면 NULL로 넣어줍니다.

// 가능한 모든 모니터와 그래픽카드 조합을 저장할 리스트를 생성합니다
DXGI_MODE_DESC* displayModeList = new DXGI_MODE_DESC[numModes];
if (!displayModeList)
{
	return false;
}

// 이제 디스플레이 모드에 대한 리스트를 채웁니다
if (FAILED(adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList)))
{
	return false;
}

그 후 GetDisplayModeList에서 네번째 인자로 넣을 displayModeList의 크기를 정해주고 다시 한번 GetDisplayModeList를 이용하여 displayModeList를 초기화 시켜줍니다.

// 이제 모든 디스플레이 모드에 대해 화면 너비/높이에 맞는 디스플레이 모드를 찾습니다.
// 적합한 것을 찾으면 모니터의 새로고침 비율의 분모와 분자 값을 저장합니다.
unsigned int numerator = 0;
unsigned int denominator = 0;
for (unsigned int i = 0; i<numModes; i++)
{
  if (displayModeList[i].Width == (unsigned int)screenWidth)
  {
    if (displayModeList[i].Height == (unsigned int)screenHeight)
    {
    numerator = displayModeList[i].RefreshRate.Numerator;
    denominator =                   displayModeList[i].RefreshRate.Denominator;
    }
  }
}


포맷과 옵션에 맞는 디스플레이 모드를 받아오고 그 중에 SystemClass에서 설정했던 screenWidth와 screenHeight이 같은 디스플레이 모드를 찾습니다.
 

그리고 나서는 모니터의 새로고침 비율(FPS)의 분모(Denominoator)와 분자값(numerator)을 저장합니다.

 

이 새로고침 비율은 나중에 쓰입니다.


D3DClass 클래스파일 초기화: 비디오 카드 구조체 받아오기

// 비디오카드의 구조체를 얻습니다
DXGI_ADAPTER_DESC adapterDesc;
if (FAILED(adapter->GetDesc(&adapterDesc)))
{
	return false;
}

말이 필요없습니다. 

그래픽(비디오)카드의 정보를 받아올 수 있는 IDXGIAdapter를 이용하여 그래픽카드의 정보를 갖는 DXGI_ADAPTER_DESC구조체를 초기화 합니다.

 

https://docs.microsoft.com/en-us/windows/win32/api/dxgi/ns-dxgi-dxgi_adapter_desc

 

DXGI_ADAPTER_DESC (dxgi.h) - Win32 apps

Describes an adapter (or video card) by using DXGI 1.0.

docs.microsoft.com

DXGI_ADAPTER_DESC의 정보입니다.

// 비디오카드 메모리 용량 단위를 메가바이트 단위로 저장합니다
m_videoCardMemory = (int)(adapterDesc.DedicatedVideoMemory / 1024 / 1024);

// 비디오카드의 이름을 저장합니다
size_t stringLength = 0;
if (wcstombs_s(&stringLength, m_videoCardDescription, 128, adapterDesc.Description, 128) != 0)
{
	return false;
}

바이트로 저장되어 있던 비디오카드 메모리를 2^10씩 나누어 킬로바이트로, 메가바이트로 변환을 시켜줍니다. 

 

그 후 비디오카드의 설명을 담을 m_videoCardDescription 변수가 char배열이기 때문에 wcstombs함수를 사용하여 와이드 문자열(유니코드)에서 싱글바이트 문자열(아스키코드)로 바꾸어줍니다.

 

함수에 대한 설명은 https://cinrueom.tistory.com/76 여기서 확인 할 수 있습니다.

 

wcstombs_s 함수란 무엇인가?

errno_t wcstombs_s( size_t *pReturnValue, char *mbstr, size_t sizeInBytes, const wchar_t *wcstr, size_t count ); 해당 함수는 와이드 문자열(유니코드)을 싱글바이트 문자열(아스키코드)로 바꿔줍니다 MFC..

cinrueom.tistory.com


D3DClass 클래스파일 초기화: 어댑터 및 팩토리관련 변수 해제

 

디스플레이 모드 및 비디오카드의 정보를 받아왔기 때문에

필요없는 변수들은 메모리 해제를 시켜줍니다.

// 디스플레이 모드 리스트를 해제합니다
delete[] displayModeList;
displayModeList = 0;

// 출력 어뎁터를 해제합니다
adapterOutput->Release();
adapterOutput = 0;

// 어뎁터를 해제합니다
adapter->Release();
adapter = 0;

// 팩토리 객체를 해제합니다
factory->Release();
factory = 0;

new키워드를 사용하여 생성한 일반 변수는 delete를 사용합니다. 

 

COM은 Release를 시켜줍니다.


D3DClass 클래스파일 초기화: 스왑체인 구조체 초기화

 

화면깜빡임 현상을 없애기 위해 스왑체인을 해야합니다.

 

그러기 위해서는 스왑체인의 설정을 담고 있는 스왑체인 구조체를 초기화 시켜주어야 합니다.

DXGI_SWAP_CHAIN_DESC swapChainDesc;
ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));

// 백버퍼를 1개만 사용하도록 지정합니다
swapChainDesc.BufferCount = 1;

// 백버퍼의 넓이와 높이를 지정합니다
swapChainDesc.BufferDesc.Width = screenWidth;
swapChainDesc.BufferDesc.Height = screenHeight;

// 32bit 서페이스를 설정합니다
swapChainDesc.BufferDesc.

 

스왑체인 구조체인 DXGI_SWAP_CHAIN_DESC를 선언 후 ZeroMemory를 사용하여 초기화를 시켜줍니다.

그 뒤 더블 버퍼링을 사용하기 위해 백버퍼의 개수를 한개로 대입합니다.

그 후 백버퍼의 해상도와 색상 포맷을 설정합니다.

if (m_vsync_enabled)
{
  swapChainDesc.BufferDesc.RefreshRate.Numerator = numerator;
  swapChainDesc.BufferDesc.RefreshRate.Denominator = denominator;
}
else
{
  swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
  swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
}

그리고 수직 동기화를 설정해주는데, 만약 수직동기화를 켜 놓았다면 백 버퍼의 새로고침 비율을 모니터의 새로고침 비율과 똑같이 해줍니다.

수직 동기화를 켜 놓지 않았다면 기본값으로 초기화를 합니다.

// 백버퍼의 사용용도를 지정합니다
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

// 랜더링에 사용될 윈도우 핸들을 지정합니다
swapChainDesc.OutputWindow = hwnd;

그 후 백버퍼의 사용 용도를 출력 렌더 타겟으로 설정합니다. 

https://m.blog.naver.com/PostView.nhn?blogId=jins8611&logNo=220186622585&categoryNo=21&proxyReferer=&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 

DXGI_SWAP_CHAIN_DESC 구조체.

DX11에서 스왑체인을 생성하기 위한 정보들을 담은 구조체. DXGI_SWAP_CHAIN_DESC sd; ...

blog.naver.com

다른 세팅은 여기서 볼 수 있습니다.

 

그 뒤 렌더링을 하기 위해 출력할 윈도우 핸들을 지정합니다.

// 멀티샘플링을 끕니다
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;

그 후 멀티샘플링을 설정합니다. 

 

멀티샘플링은 선이 픽셀 단위로 쪼개져 나오는 효과인 계단현상을 해결하기 위한 기법으로,

count는 픽셀 당 추출할 표본의 개수, Quality는 원하는 품질 수준을 설정할 수 있습니다.

 

일단은 멀티샘플링을 기본값으로 설정을 해 주었습니다.

 

https://hannom.tistory.com/159

 

[DirectX12]기본지식 - 다중표본화(multisampling)의 이론

모니터의 픽셀들이 무한하게 작지는 않기 때문에 모니터 화면에 임의의 선을 완벽하게 나타내는것은 불가능하다 위의 사진에서 보이다 싶이 흔히 계단 현상이라고 하는 앨리언싱(aliasing) 효과가 보인다. 모니터..

hannom.tistory.com

이곳에서 자세한 설명을 볼 수 있습니다.

// 창모드 or 풀스크린 모드를 설정합니다
if (fullscreen)
{
	swapChainDesc.Windowed = false;
}
else
{
	swapChainDesc.Windowed = true;
}

별 말이 필요없습니다. 

창 모드 또는 풀스크린 모드를 설정합니다.

// 스캔 라인 순서 및 크기를 지정하지 않음으로 설정합니다.
swapChainDesc.BufferDesc.ScanlineOrdering =DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;


swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

// 출력된 다음 백버퍼를 비우도록 지정합니다
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

// 추가 옵션 플래그를 사용하지 않습니다
swapChainDesc.Flags = 0;

스캔라인 순서는 출력인 인터레이스인지 프로그레시브인지 설정하고, 인터레이스인 경우 짝수라인 또는 홀수라인이 각 프레임을 시작할지 여부를 지정합니다. 

 

스케일링은 이미지의 크기가 디스플레이와 다른 경우 출력 이미지를 전체 디스플레이를 채울지, 원본 크기로 배치할지 여부를 설정합니다.

원본크기로 배치할 시 나머지 부분은 검은색의 레터박스로 채워집니다.

 

https://macinjune.com/all-posts/mac/tip/final-cut/1080i-vs-1080p-%EC%9D%B8%ED%84%B0%EB%A0%88%EC%9D%B4%EC%8A%A4%EB%93%9C%EC%99%80-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%A0%88%EC%8B%9C%EB%B8%8C-%EB%B0%A9%EC%8B%9D%EC%9D%B4%EB%9E%80/

 

1080i vs 1080p? 인터레이스드와 프로그레시브 방식이란? - Mac In June

동영상 해상도를 이야기할 때 인터레이스드(i)와 프로그레시브(p) 방식에 대한 차이에 관한 내용입니다. 요즘은 누구나 다 고화질의 동영상 컨텐츠를 어렵지 않게 제작할 수 있는 시대이며, 인터넷 방송 혹은 유튜브와 같은 온라인 동영상 매체들은 해를 거듭해 성장하고 있습니다. ‘1080i’ 혹은 ‘1080p’ 등의 해상도를 나타내는 용어는 어디서나 쉽게 확인이 가능하죠. 여기서 사용되는 기호 ‘i’와 ‘p’의 차이를 알고 계시면 후에 …

macinjune.com

 

Flag는 추가 옵션의 대한 설정입니다.

DXGI_SWAP_CHAIN_FLAG를 or연산을 통해 설정할 수 있습니다.


D3DClass 클래스파일 초기화: 스왑체인 생성

// 피처레벨을 DirectX 11 로 설정합니다
D3D_FEATURE_LEVEL featureLevel = D

 

D3D11CreateDeviceAndSwapChain함수를 통해 스왑체인을 생성하고, ID3D11Device와 ID3D11DeviceContext를 생성하기 위해 D3D_FEATURE_LEVEL이라는 enum값이 필요합니다.

 

이는 유저가 사용할 환경에 맞춰 GPU에게 기능을 지원하라고 지정을 해 주는 역할을 한다.

https://hannom.tistory.com/161

 

[DirectX12]기본지식 - 기능 수준(Feature Level)

기능 수준(Feature Level)이라는 개념은 Direct3D 11에서 도입된 것으로 코드에서는 D3D_FEATURE_LEVEL이라는 enum으로 대표된다. D3D_FEATURE_LEVEL enumeration - MSDN typedef enum D3D_FEATURE_LEVEL { D3D_FE..

hannom.tistory.com

// 스왑 체인, Direct3D 장치 및 Direct3D 장치 컨텍스트를 만듭니다.
if (FAILED(D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1,
D3D11_SDK_VERSION, &swapChainDesc, &m_swapChain, &m_device, NULL, &m_deviceContext)))
{
	return false;
}

스왑체인 생성함수 부분만 따로 봅시다.

코드에 사용된 함수는 이와 같으며,

D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1,D3D11_SDK_VERSION, &swapChainDesc, &m_swapChain, &m_device, NULL,

 

함수의 형태는 이와 같습니다.

HRESULT D3D11CreateDeviceAndSwapChain(
  IDXGIAdapter               *pAdapter,
  D3D_DRIVER_TYPE            DriverType,
  HMODULE                    Software,
  UINT                       Flags,
  const D3D_FEATURE_LEVEL    *pFeatureLevels,
  UINT                       FeatureLevels,
  UINT                       SDKVersion,
  const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc,
  IDXGISwapChain             **ppSwapChain,
  ID3D11Device               **ppDevice,
  D3D_FEATURE_LEVEL          *pFeatureLevel,
  ID3D11DeviceContext        **ppImmediateContext
);

첫번째 인자인 IDXGIAdapter는 스왑체인 장치를 생성할 때 사용할 어댑터를 보내주면 됩니다. 

NULL을 사용할 경우 자동으로 첫번째 어댑터에 연결합니다.

 

두번째 인자인 D3D_DRIVER_TYPE은 스왑체인을 생성 할 수 있는 드라이버의 유형을 지정합니다.

https://docs.microsoft.com/ko-kr/windows/win32/api/d3dcommon/ne-d3dcommon-d3d_driver_type

 

D3D_DRIVER_TYPE (d3dcommon.h) - Win32 apps

Driver type options.

docs.microsoft.com

 

세번째 인자는 소프트웨어 레스터라이저를 구현하는 DLL의 핸들입니다.

드라이버 타입이 D3D_DRIVER_TYPE_SOFTWARE이 아닌경우는 NULL을 넣어야합니다.

 

네번째 인자는 활성화 할 런타임 계층값을 OR연산을 해서 넣습니다.

https://docs.microsoft.com/ko-kr/windows/win32/api/d3d11/ne-d3d11-d3d11_create_device_flag

 

D3D11_CREATE_DEVICE_FLAG (d3d11.h) - Win32 apps

Describes parameters that are used to create a device.

docs.microsoft.com

 

다섯번째 인자는 지원하는 기능레벨의 배열포인터입니다

NULL을 대입할 경우 기본값으로 이 배열을 넣습니다..

{
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_10_0,
    D3D_FEATURE_LEVEL_9_3,
    D3D_FEATURE_LEVEL_9_2,
    D3D_FEATURE_LEVEL_9_1,
};

여섯번째 인자 FeatureLevels는 다섯번째 인자인 D3D_FEATURE_LEVEL의 요소 수입니다. 

 

일곱번째 인자는 SDK버전입니다.

 

여덟번째는 위에서 설정했던 스왑체인 구조체인 DXGI_SWAP_CHAIN_DESC를 넣습니다.

 

아홉번째는 헤더파일에서 선언했던 IDXGISwapchain을 넣어 초기화 해 줍니다.

 

열번째는 기능지원과 자원 할당을 해 주는  ID3D11Device를 초기화 해주기 위해 넣습니다.

 

열한번째는 디바이스에서 지원되는 수준을 결정합니다. 결정 할 필요가 없으면 NULL을 입력합니다.

 

열두번째는 렌더 대상 설정, 자원 관리, 렌더링 명령등을 처리하는 ID3D11DeviceContext를 초기화 해주기위해 넣습니다.

 

이렇게 하면 스왑체인 생성과 렌더를 지원하는 Device관련 클래스들을 초기화가 되었습니다.


D3DClass 클래스파일 초기화: RenderTargetView 초기화

 

스왑체인에 필요한 요소들을 설정했다면 다음에는 백 버퍼에 실제 렌더링할 렌더 타겟뷰를 생성하여 바인딩을 생성합니다.

 

이때 실제 게임에서는 매 프레임마다 렌더 타겟 뷰에 게임화면을 렌더링을 하여 출력합니다.

 

렌더링은 RenderTargetView에 하고 디바이스로 보여주는 것은 Back Buffer로 보여준다고 생각하면 됩니다.

 

https://m.blog.naver.com/PostView.nhn?blogId=agebreak&logNo=60114584311&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 

[알콜코더의 미리 배워보는 DirectX11-입문편] 1.튜터리얼 01 : 다이렉트 3D 기초 #1

안녕하세요. 알콜코더 민군입니다. ^^ 이제부터 본격적으로 연재를 시작하게 되었습니다. 많은 응원 부탁드...

blog.naver.com

// 백버퍼 포인터를 얻어옵니다
ID3D11Texture2D* backBufferPtr = nullptr;
if (FAILED(m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr)))
{
	return false;
}

// 백 버퍼 포인터로 렌더 타겟 뷰를 생성한다.
if (FAILED(m_device->CreateRenderTargetView(backBufferPtr, NULL, &m_renderTargetView)))
{
	return false;
}

// 백버퍼 포인터를 해제합니다
backBufferPtr->Release();
backBufferPtr = 0;

먼저 IDXGISwapChain의 GetBuffer를 통해 SwapChain에 사용되는 백 버퍼의 포인터를 가져옵니다. 

 

맨 처음 0은 버퍼가 여러개일때 인덱스입니다.

 

백 버퍼를 받아왔다면 렌더링 관련 자원 할당을 해주는 ID3D11Dvice를 통해 CrateRenderTargetView를 할당 시켜줍니다.

 

첫번째는 바인딩 할 백 버퍼 입니다.

 

두번째는 렌더 타겟 뷰를 사용하여 엑세스 할 수 있는 리소스들을 지정하는 구조체입니다.

지정하지 않을려면 NULL을 넣어줍니다.

 

세번째로는 초기화 해 출 렌더 타켓뷰를 넣어줍니다.

 

RendeTargetView 설정을 완료했다면, Release로 메모리를 해제해 줍니다.


D3DClass 클래스파일 초기화: 깊이 버퍼 텍스쳐 생성

 

물체를 렌더링 할 때 다른 물체에 가려지면 렌더링을 되지 않도록 해야 합니다.

다른 물체에 가려졌다는걸 판단하기 위해 깊이 버퍼를 사용합니다.

 

깊이 버퍼는 이미지를 담지 않으며, 각각의 픽셀의 depth정보를 담습니다.

범위는 0.0~1.0값이며, 1.0이 제일 먼 물체입니다.

 

이를 통해 가장 가까운 픽셀을 렌더링 합니다.

 

https://ko.wikipedia.org/wiki/Z_%EB%B2%84%ED%8D%BC%EB%A7%81

 

Z 버퍼링 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 둘러보기로 가기 검색하러 가기 컴퓨터 그래픽스에서 Z 버퍼링(z-buffering)은 3차원 그래픽스의 이미지 심도 좌표 관리 방식이며, 일반적으로는 하드웨어적으로 처리되나 이따금은 소프트웨어로 처리되기도 한다. 어느 물체를 보이게 할지 말아야 할지에 대한 가시도 문제의 한 해결책으로 쓰이기도 한다. 화가 알고리즘은 덜 효율적이긴 하나 또다른 보편적 해결책으로 쓰이고 불투명 화면 요소를 관리할 수도 있다. 어떤 물체가 그

ko.wikipedia.org

 

깊이 버퍼는 물체를 정렬할때 쓰이기 때문에 필요합니다.

 

// 깊이 버퍼 구조체를 초기화합니다
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;

// 설정된 깊이버퍼 구조체를 사용하여 깊이 버퍼 텍스쳐를 생성합니다
if (FAILED(m_device->CreateTexture2D(&depthBufferDesc, NULL, &m_depthStencilBuffer)))
{
	return false;
}

텍스쳐 생성은 전에 했던 SwapChain의 백버퍼 생성과 비슷하기 때문에 건너가겠습니다. 

 

생성된 깊이버퍼는 m_depthStencilBuffer에 들어갑니다.


D3DClass 클래스파일 초기화: 스텐실 버퍼 상태 저장

 

특정상황이 왔을때는 깊이 버퍼로만 물체를 렌더링할건지 판단하기엔 한계가 있기때문에 스텐실 버퍼라는 버퍼도 사용합니다.

 

스텐실 버퍼는 특정 영역을 렌더링 하는것을 막기위한 용도로 사용됩니다.

스텐실 버퍼값이 채워져 있으면 그려지고, 비워져 있으면 그리지 않습니다.

 

예를 들면, 거울이 있는 방을 만든다고 생각했을 때 거울부분의 스텐실 버퍼를 비우면 됩니다.

 

깊이 버퍼와 스텐실 버퍼는 같이 사용되기 때문에 깊이/스텐실 버퍼로 묶어서 사용합니다.

 

아래에는 스텐실 버퍼를 생성하기 전, 설정을 하는 구조체입니다.

// 스텐실 상태 구조체를 초기화합니다
D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));

// 스텐실 상태 구조체를 작성합니다
depthStencilDesc.DepthEnable = true;
depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;

depthStencilDesc.StencilEnable = true;
depthStencilDesc.StencilReadMask = 0xFF;
depthStencilDesc.StencilWriteMask = 0xFF;

// 픽셀 정면의 스텐실 설정입니다
depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

// 픽셀 뒷면의 스텐실 설정입니다
depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

// 깊이 스텐실 상태를 생성합니다
if (FAILED(m_device->CreateDepthStencilState(&depthStencilDesc, &m_depthStencilState)))
{
	return false;
}


https://docs.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_depth_stencil_desc


 

D3D11_DEPTH_STENCIL_DESC (d3d11.h) - Win32 apps

Describes depth-stencil state.

docs.microsoft.com

 

 

간단한 기본 설정과, 픽셀 정면의 스텐실 방법, 픽셀 후면의 스텐실 방법을 세팅합니다.

 

그 뒤 ID3D11Device의 CreateDepthStencilStage를 통해 ID3D11DepthStencilState형식의 깊이 스텐실 상태를 저장합니다.

// 깊이 스텐실 상태를 설정합니다
m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1);

렌더 관련한 기능을 담당하는 ID3D11DeviceContext를 통해 깊이 스텐실 상태를 설정합니다. 


D3DClass 클래스파일 초기화: 깊이/스텐실뷰 바인딩

 

깊이/스텐실 뷰는 RenderTargetView처럼 직접 이미지를 보여주는 View와 달리, 이미지를 미리 보여주기 위한 과정인 파이프라인에 연결시켜 과정에 포함시켜줍니다.

 

이런 파이프 라인은 추후 더 설명하겠습니다.

 

이 코드는 깊이 스텐실 뷰를 사전에 만들었던 깊이 버퍼를 사용하여 생성합니다.

// 깊이 스텐실 뷰의 구조체를 초기화합니다
D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));

// 깊이 스텐실 뷰 구조체를 설정합니다
depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
depthStencilViewDesc.Texture2D.MipSlice = 0;

// 깊이 스텐실 뷰를 생성합니다
if (FAILED(m_device->CreateDepthStencilView(m_depthStencilBuffer, &depthStencilViewDesc, &m_depthStencilView)))
{
	return false;
}

// 렌더링 대상 뷰와 깊이 스텐실 버퍼를 출력 렌더 파이프 라인에 바인딩합니다
m_deviceContext->OMSetRenderTargets(1, &m_renderTargetView, m_depthStencilView);

깊이 스텐실 뷰를 설정할 구조체인 D3D11_DEPTH_STENCIL_VIEW_DESC를 미리 설정합니다. 

 

그 뒤 렌더링 관련 자원할당을 담당하는 ID3D11Device의 CreateDepthStencilView를 사용합니다.

전에 만들었던 깊이 스텐실 버퍼, 깊이 스텐실 뷰 설정을 이용하여 깊이 스텐실 뷰를 만듭니다.

 

깊이 스텐실 뷰와 렌더 타겟뷰를 OMSetRenderTargets를 이용하여 렌더링 파이프 라인에 바인딩합니다.


D3DClass 클래스파일 초기화: 레스터 라이저 설정

 

버텍스 데이터를 기준으로 기본 도형을 만들고, 이를 화면에 픽셀로 그려주는 도구를레스터 라이저라고 합니다.

// 그려지는 폴리곤과 방법을 결정할 래스터 구조체를 설정합니다 D3D11_RASTERIZER_DESC rasterDesc;
rasterDesc.AntialiasedLineEnable = false;
rasterDesc.CullMode = D3D11_CULL_BACK;
rasterDesc.DepthBias = 0;
rasterDesc.DepthBiasClamp = 0.0f;
rasterDesc.DepthClipEnable = true;
rasterDesc.FillMode = D3D11_FILL_SOLID;
rasterDesc.FrontCounterClockwise = false;
rasterDesc.MultisampleEnable = false;
rasterDesc.ScissorEnable = false;
rasterDesc.SlopeScaledDepthBias = 0.0f;

// 방금 작성한 구조체에서 래스터 라이저 상태를 만듭니다
if (FAILED(m_device->CreateRasterizerState(&rasterDesc, &m_rasterState)))
{
	return false;
}

// 이제 래스터 라이저 상태를 설정합니다
m_deviceContext->RSSetState(m_rasterState);

레스터 라이저 설정은 위와같이 합니다. 

 

https://m.blog.naver.com/PostView.nhn?blogId=sorkelf&logNo=40162947758&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 

DirectX 11 기초 (Tutorial) - 래스터라이저 스테이지 Rasterizer Stage

지오 메트리 셰이더로부터 출력된 프리미티브 데이터는 래스터라이저(이하 RS) 스테이지에 보내져래스터...

blog.naver.com

 

다음에 렌더 파이프라인 설명할때 같이 하도록 하겠습니다.


D3DClass 클래스파일 초기화: 뷰 포트 생성

 

게임 화면을 표시하는 윈도우 화면이있다고 생각해봅시다.

 

만약 이 게임 화면의 일정영역만 렌더링만 하겠다면

뷰 포트의 범위를 설정하여 뷰 포트 바깥에는 렌더링이 되지 않도록 할수 있습니다.

 

예를 들면 뷰 포트의 크기를 작게 지정했다면, 저런식으로 초록색이 채워지고 바깥은 렌더링이 되지 않습니다.

게임에서는 웬만하면 윈도우 전체를 채우는 화면이기 때문에 뷰포트의 넓이는 윈도우 넓이와 같게 설정합니다.

// 렌더링을 위해 뷰포트를 설정합니다
D3D11_VIEWPORT viewport;
viewport.Width = (float)screenWidth;
viewport.Height = (float)screenHeight;
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
viewport.TopLeftX = 0.0f;
viewport.TopLeftY = 0.0f;

// 뷰포트를 생성합니다
m_deviceContext->RSSetViewports(1, &viewport);

D3D11_VIEWPORT구조체를 이용하여, 뷰포트를 설정합니다. 


D3DClass 클래스파일 초기화: 투영 행렬 설정

 

투영과 월드 행렬은 지금은 쓰이진 않지만 미리 설정해줍니다.

 

투영행렬은 3차원의 물체를 2차원으로 표현하기 위해 쓰는 행렬입니다.

 

투영과정을 거쳐야 2D 모니터에 출력이 가능하기 때문에 필요한 과정입니다.

// 투영 행렬을 설정합니다
float fieldOfView = XM_PI / 4.0f;
float screenAspect = (float)screenWidth / (float)screenHeight;


// 3D 렌더링을위한 투영 행렬을 만듭니다
m_projectionMatrix = XMMatrixPerspectiveFovLH(fieldOfView, screenAspect, screenNear, screenDepth);

투영행렬은 원래라면 공식을 사용하여 생성하지만, XMMatrix에서 투영행렬을 만들어주기 때문에 인자만 파헤쳐 봅시다. 

 

FOV(Field Of View)

 

FOV는 화면에 표현할 시야의 각도를 표현하는 값으로, 보여주는 각도만큼 투영시킬 화면의 범위를 넓혀줍니다.

 

 

FOV가 높을수록 보여지는 비율이 높습니다.

 

screen

screenAspect는 스크린의 비율입니다. 이게 있어야 FOV에 따라 얼마만큼의 영역을 투영할지 계산이 됩니다.

 

screenNear과 screenFar는 투영을 깊이를 정하는 요소입니다.


D3DClass 클래스파일 초기화: 월드 행렬 설정

// 세계 행렬을 항등 행렬로 초기화합니다
m_worldMatrix = XMMatrixIdentity();

월드 행렬은 모델링을 월드 좌표로 옮겨줄때 사용하는 행렬입니다. 

이는 나중에 더 알아보도록 하겠습니다.


D3DClass 클래스파일 초기화: 직교 투영 행렬

 

위에서 만든 투영행렬은 멀리있을수록 원근감이 적용되는 행렬입니다.

 

만약 UI처럼 원근감이 필요없는 물체가 있다면 일반적인 투영행렬이 아닌 직교 투영행렬이 필요합니다.

 

직교투영행렬은 일반적인 투영행렬에서 쓰이는 함수에서 FOV만 빼 놓은 형태입니다.

// 2D 렌더링을위한 직교 투영 행렬을 만듭니다
m_orthoMatrix = XMMatrixOrthographicLH((float)screenWidth, (float)screenHeight, screenNear, screenDepth);

이렇게 D3DClass의 초기화가 마무리가 되었습니다.

아직은 쓰이지 않아서 제대로 설명하지 않은 부분도 있으니, 나중에 더 자세히 알아봅시다.


D3DClass Scene

GraphicClass에서 프레임마다 실행되는 Render함수입니다.

bool GraphicsClass::Render()
{
  // 씬을 그리기 위해 버퍼를 지웁니다
  m_Direct3D->BeginScene(0.5f, 0.5f, 0.5f, 1.0f);

  // 버퍼의 내용을 화면에 출력합니다
  m_Direct3D->EndScene();

  return true;
}

여기에서 볼 수 있듯이 BeginScene은 백 버퍼를 초기화 시켜주고, 

EndScene은 출력을 시켜줍니다.

 

나중엔 이 중간에 백 버퍼에 그려주는 코드를 넣을 예정입니다.

 

D3DClass Scene: Scene시작

void D3DClass::BeginScene(float red, float green, float blue, float alpha)
{
  // 버퍼를 지울 색을 설정합니다
  float color[4] = { red, green, blue, alpha };

  // 백버퍼를 지웁니다
  m_deviceContext->ClearRenderTargetView(m_renderTargetView, color);

  // 깊이 버퍼를 지웁니다
  m_deviceContext->ClearDepthStencilView(m_depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);
}

RenderTargetView에 있는 요소들은 렌더에 관한 함수를 제공하는 ID3D11Device의 ClearRenderTargetView함수를 통해 단일 색상으로 지울 수 있습니다. 

 

RenderTargetView와 마찬가지로, 깊이/스텐실 뷰도 초기화를 해 줍니다.


D3DClass Scene: Scene표시

void D3DClass::EndScene()
{
  // 렌더링이 완료되었으므로 화면에 백 버퍼를 표시합니다.
  if (m_vsync_enabled)
  {
    // 화면 새로 고침 비율을 고정합니다.
    m_swapChain->Present(1, 0);
  }
  else
  {
    // 가능한 빠르게 출력합니다
    m_swapChain->Present(0, 0);
  }
}

CreateSwapChain을 통해 생성한 IDXGISwapChain의 Present함수를 통해 준비된 백 버퍼로 화면을 갱신 할 수 있습니다. 

 

Present함수의 첫번째 인자를 통해 수직 동기일때 출력 할지 바로 출력할지 설정할 수있습니다.


D3DClass 종료

void D3DClass::Shutdown()
{
    // 종료 전 윈도우 모드로 설정하지 않으면 스왑 체인을 해제 할 때 예외가 발생합니다.
    if (m_swapChain)
    {
    m_swapChain->SetFullscreenState(false, NULL);
  }

  if (m_rasterState)
  {
    m_rasterState->Release();
    m_rasterState = 0;
  }

  if (m_depthStencilView)
  {
    m_depthStencilView->Release();
    m_depthStencilView = 0;
  }

  if (m_depthStencilState)
  {
    m_depthStencilState->Release();
    m_depthStencilState = 0;
  }

  if (m_depthStencilBuffer)
  {
    m_depthStencilBuffer->Release();
    m_depthStencilBuffer = 0;
  }

  if (m_renderTargetView)
  {
    m_renderTargetView->Release();
    m_renderTargetView = 0;
  }

  if (m_deviceContext)
  {
    m_deviceContext->Release();
    m_deviceContext = 0;
  }

  if (m_device)
  {
    m_device->Release();
    m_device = 0;
  }

  if (m_swapChain)
  {
    m_swapChain->Release();
    m_swapChain = 0;
    }
}


초기화 해준 멤버함수들을 모두 메모리 해제를 시켜줍니다.
 

 

IDXGISwapChain을 통해 윈도우 모드로 설정하고 메모리 해제를 해 주어야 합니다.


마무리

이렇게 열심히 하고 실행을 시키면 회색의 화면이 나옵니다.

 

RenderTargetView를 회색으로 초기화를 시켜주었기 때문입니다.

 

D3DClass::BeginScene에서 Color값을 바꾸어주면 색이 바뀝니다.

 

다음엔 도형을 출력해봅시다.

 


2주차 후기

사실 스터디를 할때 Google Docs로 먼저 작성한 뒤 스터디 시간에 서로 확인하는 작업을 거칩니다.

이 스터디 자료는 19년 12월에 작성되었는데 지금에서야 올리네요

 

지금은 되게 부족한 설명이 많지만 후에 서서히 새로운 지식들로 기본기를 채울 예정입니다.

댓글