Search
Duplicate

Directx11 초기화

이제 본격적으로 Directx11을 이용해서 렌더링을 시작해보자.
DirectX11 을 초기화하기 이전에 우리 프로젝트의 구서을 간단하게 설명해보겠습니다.
Application 클래스는 우리 프로그램의 실행프로그램을 클래스로 래핑 해둔 클래스입니다.
class Application {public: virtual ~Application() = default; // Runs the main engine loopvoid Run(); // This is where the critical initializations happen (before any rendering or anything else)virtual void Initialize(); // This is where application-wide updates get executed once per frame.// RenderPath::Update is also called from here for the active componentvirtual void Update(float dt); // This is where application-wide updates get executed in a fixed timestep based manner.// RenderPath::FixedUpdate is also called from here for the active componentvirtual void FixedUpdate(); // This is where application-wide rendering happens to offscreen buffers.// RenderPath::Render is also called from here for the active componentvirtual void Render(); // You need to call this before calling Run() or Initialize() if you want to rendervoid SetWindow(HWND hwnd, UINT width, UINT height); UINT GetWidth() { return mWidth; } UINT GetHeight() { return mHegith; } HWND GetHwnd() { return mHwnd; } private: bool initialized = false; std::unique_ptr<ya::graphics::GraphicsDevice> graphicsDevice; HWND mHwnd; UINT mWidth; UINT mHegith; };
C++
복사
그리고 Application 안에 GrapchicDevice(DirectX11) 을 가지고 있습니다.
Graphic device class
#pragma once #include <d3d11.h> #include <d3dcompiler.h> #pragma comment(lib, "d3d11.lib") #pragma comment(lib, "d3dcompiler.lib") #include "CommonInclude.h" //https://github.com/kevinmoran/BeginnerDirect3D11 // namespace ya::graphics { class GraphicDevice_DX11 { public: GraphicDevice_DX11(); ~GraphicDevice_DX11(); void Initialize(); void Draw(); private: Microsoft::WRL::ComPtr<ID3D11Device> mDevice; Microsoft::WRL::ComPtr<ID3D11DeviceContext> mContext; Microsoft::WRL::ComPtr<ID3D11Texture2D> mRenderTarget; Microsoft::WRL::ComPtr<ID3D11RenderTargetView> mRenderTargetView; Microsoft::WRL::ComPtr<ID3D11Texture2D> mDepthStencil; Microsoft::WRL::ComPtr<ID3D11DepthStencilView> mDepthStencilView; Microsoft::WRL::ComPtr<IDXGISwapChain> mSwapChain; Microsoft::WRL::ComPtr<ID3D11SamplerState> mSamplers; }; }
C++
복사
ComPtr 객체는 스마트 포인터 처럼 일일히 delete를 직접 해줄 필요 없이 자동으로 delete해주는 포인터를 담을수 있는 객체이다. Directx11에서는 Comptr을 사용하면 조금더 편하게 프로그래밍을 진행할수 있다.
우선적으로 Dx11에서는 우선 세가지의 오브젝트를 만들어야 합니다.
Device, Immediate Context, Swap Chain

ID3D11Device

3D 그래픽 카드와 연결되는 기본 디바이스 객체

ID3D11DeviceContext (레스터라이제이션 파이프라인 객체)

Dx11에서는 디바이스 객체에 직접 접근하지 않고, 이 객체를 이용해서 디바이스에 명령을 내립니다.
Dx11에서는 멀티코어/멀티스레드를 최적화 하기 위해서 Device의 하위에 Context를 분리하여 사용합니다.

IDXGISwapChain

화면에 렌더링할 백버퍼(Frame Buffer)를 관리하고, 실제로 화면에 렌더링 하는 역할을 담당하는 오브젝트 입니다.
이 오브젝트를 이용하여 백버퍼의 렌더타겟(frame Buffer)를 얻어내고 프론트 버퍼와 백버퍼를 스왑해주면서
화면에 렌더링 하는 역할을 합니다.
기본적인 Directx11 초기화 순서는 아래와 같습니다.
1. ID3D11Device 와 SwapChain을 생성한다.
2. 백버퍼에 실제 렌더링할 '렌더 타겟 뷰'를 생성
3. 뷰포트를 생성한다.
4. 매 프레임마다 위에서 생성한 렌더 타겟 뷰에 게임화면을 렌더링한다.
5. SwapChain을 이용하여 디바이스에 화면을 그린다.
제일 중요한 작업은 Render Target View를 생성하는 겁니다.
이 렌더타겟뷰가 실제로 렌더링할 렌더타겟(Frame Buffer)에 바인딩되어 렌더링을 진행하게 됩니다.
렌더타겟뷰는 리소스뷰의 일종으로 디바이스의 백버퍼에 바운딩되면서 생성됩니다. 그래서
렌더 타겟뷰에 게임화면을 렌더링 한다는 것은 백버퍼에 렌더링 하는 것과 같습니다. 그리고 백버퍼에 모든 화면을
렌더링 하고 난 다음에는 SwapChain을 이용해서 백버퍼를 화면에 그려줍니다.
정확히는 백버퍼를 프론트 버퍼와 바꾸는 스와핑이 일어나겠죠.
SwapChain 생성 -> RenderTarget (백버퍼 또는 FrameBuffer)를 꺼내오고, 꺼내온 백버퍼를 가지고 렌더타겟뷰라는 오브젝트에 바운딩 합니다.
그리고 실제 내부에서는 SwapChain이 백버퍼라는 리소스를 가지고 있고, 개발자는 그 백버퍼를 렌더 타겟뷰에 바인딩하여 사용합니다.

Dx11 초기화 순서

1. Directx Device 생성하기
// Device , Device Context UINT DeviceFlag = D3D11_CREATE_DEVICE_DEBUG; D3D_FEATURE_LEVEL FeatureLevel = (D3D_FEATURE_LEVEL)0; ID3D11Device* pDevice = nullptr; HRESULT hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr , DeviceFlag, nullptr, 0 , D3D11_SDK_VERSION , mDevice.GetAddressOf() , &FeatureLevel , mContext.GetAddressOf());
C++
복사
Directx11 의 피터레벨에 의해 여러단계로 나누어져 있다.
피처레벨은 GPU가 서포트 하는 기능셋의 엄밀한 정의로 기본적으로는 상위의 피처레벨은 하위의 피처레벨의 기능을 포함하고 있다.
디바이스를 생성할 때 낮은 피처레벨을 지정하는 것으로 Directx9, 10에서도 11을 사용 할 수 있다.
반드시 어플리케이션에서 요구하는 피처레벨을 지정한다.
typedef enum D3D_FEATURE_LEVEL { D3D_FEATURE_LEVEL_1_0_CORE = 0x1000, D3D_FEATURE_LEVEL_9_1 = 0x9100, D3D_FEATURE_LEVEL_9_2 = 0x9200, D3D_FEATURE_LEVEL_9_3 = 0x9300, D3D_FEATURE_LEVEL_10_0 = 0xa000, D3D_FEATURE_LEVEL_10_1 = 0xa100, D3D_FEATURE_LEVEL_11_0 = 0xb000, D3D_FEATURE_LEVEL_11_1 = 0xb100, D3D_FEATURE_LEVEL_12_0 = 0xc000, D3D_FEATURE_LEVEL_12_1 = 0xc100, D3D_FEATURE_LEVEL_12_2 = 0xc200 } D3D_FEATURE_LEVEL;
GLSL
복사

피처레벨의 주요 기능적 차이

2. Swap Chain  생성하기
bool GraphicsDevice_DX11::CreateSwapChain(const SwapChainDesc* desc, HWND hWnd, SwapChain* swapchain) { DXGI_SWAP_CHAIN_DESC dxgiDesc = {}; dxgiDesc.OutputWindow = hWnd;// 버퍼를 출력할 윈도우 dxgiDesc.Windowed = true;// 윈도우, 전체화면 모드 dxgiDesc.BufferCount = desc->buffer_count; //백버퍼 개수 dxgiDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;// 이전 프레임 장면을 유지하지 않는다. dxgiDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; //백버퍼를 어떤 용도로사용할 것인지.. dxgiDesc.BufferDesc.Width = desc->width; //백버퍼 가로 세로 dxgiDesc.BufferDesc.Height = desc->height; dxgiDesc.BufferDesc.Format = (DXGI_FORMAT)desc->format; //백버퍼 포맷 : RGBA 8 비트이며 값의 범위0.0~1.0 dxgiDesc.BufferDesc.RefreshRate.Numerator = 144; //화면의 주사율 dxgiDesc.BufferDesc.RefreshRate.Denominator = 1; dxgiDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; dxgiDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; dxgiDesc.SampleDesc.Count = 1; //멀티 샘플링 수 dxgiDesc.SampleDesc.Quality = 0; //멀티 샘플링 퀄리티 Microsoft::WRL::ComPtr<IDXGIDevice> pDXGIDevice = nullptr; Microsoft::WRL::ComPtr<IDXGIAdapter> pAdapter = nullptr; Microsoft::WRL::ComPtr<IDXGIFactory> pFactory = nullptr; if (FAILED(mDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)pDXGIDevice.GetAddressOf()))) return false; if (FAILED(pDXGIDevice->GetParent(__uuidof(IDXGIAdapter), (void**)pAdapter.GetAddressOf()))) return false; if (FAILED(pAdapter->GetParent(__uuidof(IDXGIFactory), (void**)pFactory.GetAddressOf()))) return false; if (FAILED(pFactory->CreateSwapChain(mDevice.Get(), &dxgiDesc, mSwapChain.GetAddressOf()))) return false; return true; }
C++
복사
directx11 을 사용하려면 [ 디바이스 ] [ 디바이스 컨텍스트 ] [ DXGI 의 스왑 체인 ]인터페이스를 얻어야 한다.
QueryInterface, GetParent, CreateSwapChain, 함수를 사용하면 위에서 언급한 3개의 인터페이스를 얻을 수 있다.
스왑 체인은  DXGI_SWAP_CHAIN_DESC  구조체를 사용하여 설정 할 수 있으며
백버퍼 설정, 스왑체인과 관련할 윈도우 멀티샘플 설정, 화면모드 설정등을 지정한다.
3. swap chain 으로 부터  FrameBuffer 가져오기
// Get render target by Swapchain hr = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)mFrameBuffer.GetAddressOf());
C++
복사
디바이스, 디바이스 컨텍스트, 스왑체인을 생성하였지만
스왑체인의 백버퍼는 Direct3D의 렌더타겟에 설정되어 있지 않다.
그래서 스왑체인으로부터 백버퍼를 취하여 디바이스의 렌더타겟으로 설정해야 한다.
IDXGISwapChain::GetBuffer 함수로 백버퍼의 포인터를 얻어 낼수 있다.
4. Render Target View 생성하기
// Create Rendertarget view hr = mDevice->CreateRenderTargetView(mFrameBuffer.Get(), nullptr, mRenderTargetView.GetAddressOf());
C++
복사
텍스처는 파이프라인으로부터 뷰를 통하여 액세스 할수 있으며
렌더타겟에는 렌더타겟 뷰를 사용한다 따라서 백버퍼를 얻어왔으면 해당 백버퍼에 접근할수 있게 렌더 타겟 뷰를 생성한다
렌더타겟 뷰는 D3D11_RENDER_TARGET_VIEW_DESC 구조체로 설정하는데 디폴트 설정으로는 NULL을 넘긴다
5. 깊이 버퍼 생성과 깊이버퍼 뷰 생성
// Create Depth Stencil Bufferif (FAILED(mDevice->CreateTexture2D(&dxgiDesc, nullptr, mDepthStencilBuffer.GetAddressOf()))) return false; // Create Depth Stencil Buffer Viewif (FAILED(mDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), nullptr, mDepthStencilView.GetAddressOf()))) return false;
C++
복사
깊이/스텐실 버퍼 설정
3D 그래픽스 기능을 사용하는 어플리케이션을 만들때 대부분은 보이지 않는 부분을 숨기는 [ 컬링 ]을 수행하는데에
[ 깊이 버퍼 ](Z버퍼, 뎁스 버퍼)를 사용한다.
3D 그래픽스의 컬링
3D를 올바르게 렌더링하려면 앞쪽에 있는 물체가 뒤에 있는 물체를 가릴수있는 처리가 필요한데
이 처리에는 [Z 소팅] 방식과 [ Z 버퍼 기법]이 있다.

Z-Sort

Z-bufffer

Z 소트 기법은 간단하고 심플하지만 물체끼리가 교차하는 전후관계를 간단하게 결정하기 어려운 물체가있을때 제대로 렌더링되지 않는 다는 점과 소팅하는데 시간이 걸린다라는 단점이 있다.
Z 버퍼 기법은 픽셀 단위로 거리를 기록해두기 위한 [ 깊이 버퍼 ]가 필요하지만 전후관계를 픽셀단위로 판단할 수 있어 일반적으로는 Z 버퍼 기법을 사용하지만 렌더링하는 물체중간의 반투명인 물체가 있으면 깊이 버퍼만으로는 제대로 렌더링할수 없다.
이경우에는 불투명 물체만을 먼저 렌더링해두고 반투명 물체를 뒤에서 부터 소팅하여 렌더링한다 (알파 소팅)
이 외에 여러가지 효과를 표현하는데 쓰이는 [ 스텐실 버퍼 ]를 사용하는 경우도 있다
DirectX 11에 깊이 버퍼와 스텐실 버퍼는 1개의 리소스를 공유하므로 프로그램에서는 [ 깊이/스텐실 버퍼] 라고 묶어서 취급한다
또 깊이/스텐실 버퍼는 백버퍼나 렌더가능한 텍스처 등인 [ 렌더 타겟 ] 과조합해서 사용한다
렌더타겟은 파이프라인에서 최대 8개까지 설정 할수 있지만 깊이/스텐실 버퍼는 하나만을 설정 가능하다.
DirectX 11 에서 깊이/스텐실 버퍼는 텍스처 리소스의 한종류이다.
따라서 깊이/스텐실 텍스처로써의 텍스처 구조체를 생성한다.
예시
// Create depth stencil texture D3D11_TEXTURE2D_DESC descDepth; ZeroMemory( &descDepth, sizeof(descDepth) ); //초기화 descDepth.Width = width; descDepth.Height = height; descDepth.MipLevels = 1; //밉맵 레벨 개수 descDepth.ArraySize = 1; // 배열 사이즈 descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; // 포맷 (깊이버퍼 24bit, 스텐실 8 bit) descDepth.SampleDesc.Count = 1; //멀티 샘플링 수 descDepth.SampleDesc.Quality = 0; //멀티 샘플링 퀄리티 descDepth.Usage = D3D11_USAGE_DEFAULT; //디폴트 사용법 descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL; //깊이/스텐실 버퍼로써 사용 descDepth.CPUAccessFlags = 0; //CPU로부터는 액세스 하지 않음 descDepth.MiscFlags = 0; //그 외 설정 없음 hr = g_pd3dDevice->CreateTexture2D( &descDepth, NULL, &g_pDepthStencil ); //텍스처 생성
JavaScript
복사
위에서 생성한 텍스처는 텍스처 리소스의 한 종류 이므로 파이프라인에 설정하려면
일반 텍스처와 마찬가지로 [ 뷰 ]를 사용한다
예시
ID3D11DepthStencilView* g_pDepthStencilView = NULL; // Create the depth stencil view D3D11_DEPTH_STENCIL_VIEW_DESC descDSV; ZeroMemory( &descDSV, sizeof(descDSV) ); descDSV.Format = descDepth.Format; //깊이/스텐실텍스처에서 사용한 포맷 그대로 사용 descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; //2D 텍스처 descDSV.Texture2D.MipSlice = 0; hr = g_pd3dDevice->CreateDepthStencilView( g_pDepthStencil, &descDSV, &g_pDepthStencilView );
JavaScript
복사
6. RenderTargetView 와 DepthStencilView 설정하기
mContext->OMSetRenderTargets(1, mRenderTargetView.GetAddressOf(), mDepthStencilView.Get());
C++
복사
깊이/스텐실 뷰를 설정했으면 렌더타겟 뷰와 깊이스텐실 뷰를 파이프라인의 Output Merger 스테이지에 설정한다
7. 렌더링
모니터에 그린물체를 그려주기전에 화면을 지워주는 역할도 진행한다.
void GraphicsDevice_DX11::Draw() { FLOAT backgroundColor[4] = { 0.2f, 0.2f, 0.2f, 1.0f }; mContext->ClearRenderTargetView(mRenderTargetView.Get(), backgroundColor); mContext->ClearDepthStencilView(mDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.f, 0); // draw object mSwapChain->Present(1, 0); }
C++
복사
위 소스에서는 깊이값만을 클리어 하고 있으며 스텐실 값도 클리어 하고 싶을때는
D3D11_CLEAR_STENCIL플래그를 OR 연산하여 플래그 인수로 넘겨준다
클리어 처리는 [ 뷰 ](렌더타겟뷰,깊이/스텐실뷰)를 대상으로 수행되며, Output Merger 스테이지에 렌더타겟으로써 등록한 버퍼가 클리어 되는 것은 아니다.
렌더링 처리가 끝나면 스왑체인의 있는 백버퍼에 렌더링 처리를 수행한 후 렌더링 결과를 화면에 표시하려면
IDXGISwapChain::Present 함수를 호출한다

파이프라인에서 자주 사용되는 단어

Vertex Buffer 와 Index Buffer : 각 프리미티브 정보는 [ 버텍스 버퍼 ] 와 [ 인덱스 버퍼 ] 에 저장하여 파이프라인에 넘긴다.
[ 버텍스 버퍼 ] 에는 프리미티브의 각 버텍스의 좌표 나 매터리얼 정보 (색 등) 을 저장한다.
버텍스 버퍼는 동시에 최대 16 ( 또는 32 )개까지 사용 할 수 있다.
Input Assembler : 파이프라인에서 첫 스테이지 이다. 리소스로부터 데이터를 읽어 들여 파이프라인에 데이터를 제공하는 일을 한다.
동시에 [ 프리미티브 ID ] [ 인스턴스 ID ] [ 버텍스 ID ] 등의 [시스템 생성값]을 생성하여 파이프라인에 제공한다.
Input Assembler 스테이지에는 [ 버텍스 버퍼 ], [ 인덱스 버퍼] , [ 입력 레이아웃 오브젝트] [프리미티브 타입] 등을 설정한다
Vertex Shader : 셰이더 입력으로 버텍스 데이터를 1개 취해, 좌표 변환등을 수행한 후, 버텍스 데이터 1개를 출력한다
Hull Shader : DirectX 11 에서 새로이 추가 된 테셀레이터 기능을 구성하는 셰이더의 하나이다.
셰이더 입력으로 1~32 개의 [ 컨트롤 포인트 ] 를 취해,  1~32개의 [컨트롤 포인트] [배치 정수] [테셀레이션 계수] 를 출력한다.
출력 데이터는 테셀레이터 스테이지와 도메인 셰이더에 넘겨진다
Tessellator : DirectX 11 에서 새로이 추가 된 테셀레이터 기능을 구성하는 스테이지이다
입력으로써 [ 쿼드 배치 ] [ 삼각형 배치 ] [ 선 ]을 취해, 보다 정밀한 다수의 [삼각형] [선] [점]을 출력한다
Domain Shader : DirectX 11 에서 새로이 추가 된 테셀레이터 기능을 구성하는 셰이더의 하나이다.
입력으로써 [ Hull Shader ] 와 Tessellator에 출력을 취해, 셰이더 출력으로써 배치 내의 각 버텍스 좌표를 출력한다
Geometry Shader : 셰이더 입력으로써 프리미티브데이터 1개를 취해 프리미티브의 변형이나 새로운 프리미티브의 생성을
수행 한 후 셰이더 출력으로써 0개 또는 1개 이상의 프리미티브 데이터를 출력한다. 프리미티브를 줄이거나 늘리거나 할 수 있다.
Stream Output  : GS 나 VS 로부터 출력을 리소스 내의 버퍼에 쓰는 스테이지이다
버퍼에 쓰여진 데이터는 CPU 에서 읽어들여 사용하거나, 파이프라인에의 입력으로써 사용할 수 있다.
Rasterize : 보이지 않는 프리미티브를 없애거나(컬링), 버텍스 값을 프리미티브 전체로 보완하여 프리미티브를 픽셀 데이터로 분해한다
래스터라이저 스테이지에 입력되는 버텍스 데이터의 좌표(x,y,z,w)는 모두 같은 Ciip 공간내의 좌표라고 간주된다.
Pixel Shader : 셰이더 입력으로써 픽셀 데이터 1개를 취해 텍스처 처리나 라이팅등을 수행한 후
셰이더 출력으로써 픽셀 데이터 1개를 출력한다
Output Merger : 픽셀 셰이더로부터 출력된 픽셀 데이터나 깊이/스텐실 버퍼의 값을 사용하여
최종적으로 렌더링될 색을 결정한다.
이때 깊이/스텐실 테스트를 수행하여 실제로 렌더링할지 안할지를 결정한다
Texture 와 Sampler : 리소스에는 텍스처 리소스와 버퍼 리소스 가 있다
버퍼리소스는 1차원 배열이다.
버퍼리소스는 구조화되어 있지 않으며 필터 처리의 대상도 되지 않으며, 서브 리소스를 가질수 있다.
멀티샘플되는 것도 되지 않는다. 본질적으로는 단순한 커다란 메모리라고 할 수 있다.
버텍스 버퍼 와 인덱스 버퍼가 대표적인 버퍼 리소스이다
텍스처 리소스는 화상 데이터인 [ 텍스처 ]를 보존하기 위한 구조화된 컬렉션이다.
텍스처는 셰이더에서 읽어들일때에 샘플러에 의해 필터 처리할 수 있다.
도 1개이상의 밉맵 레벨을 가질수 있다
Constant Buffer : DirectX 11 에서는 셰이더로 사용하는 정수값을 설정하는데 [ 정수 버퍼 ]를 사용한다
이것은 개념적으로는 버텍스 버퍼와 완전 똑같이 취급한다
각 스테이지에는 D3D11_COMMON_SHADER_CONSTANT_BUFFER_API_SLOT_COUNT(14)의 정수 버퍼를 할당할수 있으며
각 정수 버퍼에는 최대 4096개의 정수를 설정할 수 있다.
Render Target : DirectX 11 에서는 최대 8개까지 렌더타겟을 설정할 수 있다
또 지오메트리 셰이더를 사용하여 프리미티브를 렌더링하는 렌더타겟을 설정할 수 있다.
이 기능을 사용하여 큐브 텍스처의 6개 면을 1패스로 렌더링 할 수 있다.
Depth/ Stencil Buffer : 깊이 버퍼와 스텐실 버퍼는 깊이/스텐실 버퍼라는 1개의 리소스로써 묶어서 취급 된다
깊이/스텐실 버퍼의 사용은 옵션이며 2D그래픽스 렌더링 같은데에서는 깊이 /스텐실 버퍼를 사용하지 않을 수도 있다.