일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 프로그래밍 기초
- Cartoon Rendering
- URP
- C언어
- ColorGradingLutPass
- Cell Shader
- AppSW
- Windows Build
- 3d
- OculusMotionVectorPass
- 메모리 누수
- 개인 바이트
- Cell Look
- Private Bytes
- Specular
- working set
- URP로 변경
- 게임 수학
- 작업 집합
- 가상 바이트
- 벡터
- Toon Shader
- Rim Light
- ASW(Application SpaceWarp)
- Virtual Byte
- Three(Two) Tone Shading
- VR
- Today
- Total
WinCNT
Direct3D 11의 초기화 본문
참고 서적) DirectX 11을 이용한 3D 게임 프로그래밍 입문
Direct3D 초기화 과정
4X 멀티 샘플링을 사용하는 전형적인 Direct3D 11 응용 프로그램의 초기화 과정은
크게 8단계로 구성된다
- D3D11CreateDevice 함수를 사용해서 장치(Device)와 장치 문맥(DeviceContext)을 생성한다
장치 = ID3D11Device 인터페이스, 장치 문맥 = ID3D11DeviceContext 인터페이스 - 4X MSAA(멀티 샘플링 안티 알리아싱) 품질 수준 지원 여부를 체크한다
(사용하는 함수는 ID3DllDevice::CheckMultisampleQualityLevels) - 생성할 Swap Chain의 특성을 서술하는 DXGI_SWAP_CHAIN_DESC 구조체를 초기화한다
- ICSGISwapChain 인스턴스를 생성한다
(장치를 생성하는데 사용했던 IDXGIFactory인터페이스에 질의해서 생성) - Swap Chaind의 후면 버퍼에 대한 Render Target View를 생성한다
- 깊이 / 스텐실 버퍼와 그에 따른 깊이 / 스텐실 뷰를 생성한다
- Render Target View와 깊이 / 스텐실 뷰를 렌더링 파이프라인의 출력 병합기 단계에 Binding한다
(Direct3D가 사용할 수 있게 하기 위해 필요한 작업) - 뷰포트를 설정한다
장치(Device)와 장치 문맥(DeviceContext)을 생성
Direct3D 초기화의 시작은 Direct3D 11의 장치(ID3D11Device)와 그 문맥(ID3D11DeviceContext)을 생성하는 것!
Device와 DeviceCotext 인터페이스는 Direct3D의 주된 인터페이스로,
물리적인 그래픽 장치 하드웨어에 대한 소프트웨어 제어기라도 보면 된다
어플리케이션은 Device와 DeviceCotext 인터페이스를 통해서 하드웨어에게 할 일을 지시한다
구체적인 역할은 다음과 같이 나눌 수 있다
- ID3D11Device 인터페이스
- 기능 지원 체크
- 자원 할당
- ID3D11DeviceContext 인터페이스
- Render Target를 설정
- 자원을 그래픽 파이프라인에 Binding
- GPU가 수행할 렌더링 명령들을 지시
Device와 DeviceCotext를 생성하는 함수는 다음과 같다
https://docs.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-d3d11createdevice
HRESULT D3D11CreateDevice(
[in, optional] IDXGIAdapter *pAdapter,
D3D_DRIVER_TYPE DriverType,
HMODULE Software,
UINT Flags,
[in, optional] const D3D_FEATURE_LEVEL *pFeatureLevels,
UINT FeatureLevels,
UINT SDKVersion,
[out, optional] ID3D11Device **ppDevice,
[out, optional] D3D_FEATURE_LEVEL *pFeatureLevel,
[out, optional] ID3D11DeviceContext **ppImmediateContext
);
다음은 사용 예제이다
#include <d3d11.h>
/// <summary>
/// D3D11CreateDevice 함수 정리
/// https://docs.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-d3d11createdevice
/// </summary>
/// <param name="pAdapter">디스플레이 어댑터 지정</param>
/// <param name="DriverType">드라이브 타입 설정
/// D3D_DRIVER_TYPE_HARDWARE: 하드웨어에서 Direct3D 기능을 구현하는 하드웨어 드라이버
/// 하드웨어 가속이 적용된다
/// D3D_DRIVER_TYPE_REFERENCE: 모든 Direct3D 기능을 지원하는 소프트웨어 구현인 참조 드라이버
/// 속도보다는 정확성에 중점을 준 설정
/// D3D_DRIVER_TYPE_SOFTWARE: 소프트웨어에 Direct3D 기능을 구현하는 소프트웨어 드라이버</param>
/// D3D_DRIVER_TYPE_WARP: 고성능 소프트웨어 래스터라이저인 WARP 드라이버
/// <param name="Software">소프트웨어 구동기를 지정</param>
/// <param name="Flags">추가적인 장치 생성 플래그들(OR로 결합 가능)을 지정
/// D3D11_CREATE_DEVICE_DEBUG: 디버그 계층 활성화 플래그
/// Direct3D가 출력 창에 디버그 메시지를 보냄
/// D3D11_CREATE_DEVICE_SINGLETHREADED: 단일 스레드에서 성능을 향상시키는 플래그
/// Direct3D가 여러 개의 스레드에서 호출되지 않는다는 보장이 필요< / param>
/// <param name="pFeatureLevels">기능 수준의 순서를 결정하는 D3D_FEATURE_LEVEL 배열(포인터)</param>
/// <param name="FeatureLevels">pFeatureLevels의 원소 개수</param>
/// <param name="SDKVersion">항상 D3D11_SDK_VERSION를 사용</param>
/// <param name="ppDevice">생성한 장치를 반환</param>
/// <param name="pFeatureLevel">pFeatureLevels 배열에서 처음으로 지원되는 기능을 반환</param>
/// <param name="ppImmediateContext">생성된 장치 문맥을 반환</param>
/// <returns></returns>
HRESULT hr = D3D11CreateDevice(
nullptr, // NULL은 기본 디스플레이
D3D_DRIVER_TYPE_HARDWARE, // 기본적으로 하드웨어 가속이 되는 하드웨어 드라이버를 설정함
nullptr, // 하드웨어를 사용해서 렌더링하므로 소프트웨어 구동기는 Null로 설정함
createDevieceFlags, // 디버그 모드에서는 디버그 계층 활성화함
nullptr, 0, // Null이면 디폴트 기능 수준 배열을 사용한다
D3D11_SDK_VERSION, // 항상 D3D11_SDK_VERSION를 사용
&m_D3DDevice, // Device
&m_FeatureLevel, // 적용된 기능 수준
&m_D3DDeviceContext // DeviceContext
);
그 다음으로는 4X MSAA 품질 수준 지원을 체크한다
hr = m_D3DDevice->CheckMultisampleQualityLevels(
DXGI_FORMAT_R8G8B8A8_UNORM, // 텍스처 형식
4, // 다중 샘플링 중 샘플 수(4X이므로 4를 지정)
&m_4xMassQuality // 어댑터에서 지원하는 품질 수준의 수
);
// HR()은 dxerr.lib에 포함되어 있다가 DX SDK가 Windows10 SDK로 통합되면서 사라졌으므로 대신 다음이 처리를 추가
if (FAILED(hr))
{
LPWSTR output;
FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&output, 0, NULL
);
MessageBox(NULL, output, _T("Error"), MB_OK);
}
assert(m_4xMassQuality > 0); // 반환된 품질 수는 0보다 커야해서 assert로 강제
그 다음으로는 생성할 Swap Chain의 특성을 서술하는
DXGI_SWAP_CHAIN_DESC 구조체를 초기화한다
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory(&sd, sizeof(sd));
// DXGI_MODE_DESC BufferDesc: 후면 버퍼의 속성들을 서술하는 개별적인 구조체
// 창의 클라이언트 영역의 크기(0이면 출력 창 크기로 설정)
sd.BufferDesc.Width = m_ScreenWidth;
sd.BufferDesc.Height = m_ScreenHeight;
// 디스플레이 모드갱신율
sd.BufferDesc.RefreshRate.Numerator = 60; // 최고 유리수
sd.BufferDesc.RefreshRate.Denominator = 1; // 최저 유리수
// 후면 버퍼 픽셀 형식
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
// 디스플레이 스캔라인 모드
sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; // 미지정
// 4X MSAA 사용 여부 체크
if (m_Enable4xMsaa)
{
// 픽셀당 멀티샘플 수
sd.SampleDesc.Count = 4;
// 원하는 품질 수준 지정
// 품질 수준의 범위는 텍스처 형식과 픽셀당 샘플 수에 의존한다
// 유요한 품질 수준의 범위는 0에서 CheckMultisampleQualityLevels로 얻은 값 -1까지이다
sd.SampleDesc.Quality = m_4xMassQuality - 1;
}
else
{
sd.SampleDesc.Count = 1; // 픽셀당 멀티샘플 수
sd.SampleDesc.Quality = 0; // 이미지 품질 수준
}
// 후면 버퍼에 대한 사용 용도 설정(셰이더 입력, 혹은 렌더 타겟 출력)
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 후면 버퍼를 Render Target으로 사용
// Swap Chain에서 사용할 후면 버퍼의 개수
sd.BufferCount = 1; //(1: 이중 버퍼링, 2: 삼중 버퍼링ㄴ)
// 렌더링 결과를 표시할 윈도우의 핸들
sd.OutputWindow = m_hWnd;
// 창 모드, 전체 화면 모드 설정
sd.Windowed = true;
// 표시한 후, 전면 버퍼의 내용을 처리하기 위한 옵션
// DXGI_SWAP_EFFECT_DISCARD: 디스플레이 드라이버가 Swap Chain에 대해 가장 효율적인 제시 기술을 선택함
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
// 스왑 체인 동작에 대한 추가 옵션
// 0: 창 => 전체 화면 시, 현재 데스크톱 디스플레이 모드(해상도?)가 사용
// DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH: 창 => 전체 화면 시, 후면 버퍼 설정에 부합하는 모드가 자동 선택
sd.Flags = 0;
Swap Chain의 특성을 서술하는 구조체를 만들었다면,
IDXGIFactory 인터페이스의 CreateSwapChain 함수로 Swap Chain 인터페이스를 생성한다
(참고로 장치 생성과 동시에 Swap Chain도 생성하려면 D3D11CreateDeviceAndSwapChain를 사용하면 된다)
// COM 인터페이스를 안전하게 해제하기 위한 매크로 함수
#define ReleaseCOM(x) \
{ \
if(x) \
{ \
x->Release(); \
x = 0; \
} \
}
// IDXGIFactory 인터페이스의 CreateSwapChain 함수로 Swap Chain 인터페이스를 생성한다
// Swap Chain을 만들기 위해서는 장치 생성에 사용된 IDXGIFactory 인스턴스를 사용해야 한다
// 다음은 장치 생성에 사용된 IDXGIFactory 인스턴스를 얻기 위한 일련의 과정이다
IDXGIDevice* _DxgiDevice = 0;
HR(m_D3DDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&_DxgiDevice));
IDXGIAdapter* _DxgiAdapter = 0;
HR(_DxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&_DxgiAdapter));
IDXGIFactory* _DxgiFactory = 0;
HR(_DxgiAdapter->GetParent(__uuidof(IDXGIFactory), (void**)&_DxgiFactory)); // IDXGIFactory 인스턴스
// Swap Chain을 생성한다
HR(_DxgiFactory->CreateSwapChain(m_D3DDevice, &sd, &m_SwapChain));
// 다 사용한 COM 인터페이스을 해제한다
ReleaseCOM(_DxgiDevice);
ReleaseCOM(_DxgiAdapter);
ReleaseCOM(_DxgiFactory);
참고로 DXGI(DirectX Graphics Infrastructure)는 Direct3D와는 개별적인 API
Swap Chain 설정이나 그래픽 하드웨어 나열, 창 모드와 전체화면 모드 전환 등 그래픽에 관련된 작업을 처리함
(Direct3D와 분리한 이유는 다른 그래픽 API(Direct2D 등)에도 공통으로 필요한 그래픽 관련 작업이기 때문)
Swap Chain을 생성했으면 그 다음으로는 렌더 타겟 뷰를 생성해야 한다
리소스는 반드시 그에 대한 뷰를 생성하고 그 뷰를 파이프라인의 단계에 바인딩해야 한다
(리소스 자체를 파이프라인의 단계에 바운딩하는 것이 아님)
좀 더 구체적으로, Direct3D가 버퍼에 그리기 처리를 하기 위해서는
후면 버퍼를 파이프라인의 출력 병합기(Output Meger) 단계에 바운딩을 해야 하는데,
Direct3D에서는 리소스 자체를 파이프라인의 단계에 바운딩하지 않으므로
우선 후면 버퍼에 대한 뷰를 생성하고, 그 뷰를 출력 병합기(Output Meger) 단계에 바운딩해야 한다는 뜻이다
다음은 렌더 타겟 뷰를 생성하는 예제 코드이다
// ==================== 렌더 타겟 뷰의 생성 ====================
// 리소스에 대한 뷰를 생성하고 그 뷰를 파이프라인 단계에 Binding해야 한다
// Direct3D가 버퍼에 그리기 처리를 하기 위해서는 후면 버퍼가
// 파이프라인의 출력 병합기(Output Merger) 단계에 Binding되어야 하는데,
// 그러기 위해서는 우선 후면 버퍼에 대한 렌더 타겟 뷰를 생성해야 한다
ID3D11Texture2D* _BackBuffer = nullptr;
// Swap Chain의 포인터를 획득
HR(m_SwapChain->GetBuffer(
0, // 후면 버퍼의 인덱스(후면 버퍼가 1개이므로 0을 설정)
__uuidof(ID3D11Texture2D), // 버퍼의 인터페이스 형식을 지정(일반적으로 ID3D11Texture2D를 설정)
reinterpret_cast<void**>(&_BackBuffer)) // 후면 버퍼의 포인터 반환
);
if (_BackBuffer)
{
/// <summary>
/// 렌더 타겟 뷰를 생성하는 함수
/// </summary>
/// <param name="pResource">렌더 타겟 뷰를 만들 리소스</param>
/// <param name="pDesc">렌더 타겟 뷰로 접근 가능한 하위 리소스 설정하기 위한 구조체</param>
/// <param name="ppRTView">생성한 렌더 타겟 뷰를 반환</param>
m_D3DDevice->CreateRenderTargetView(
_BackBuffer, // 여기선 후면 버퍼
nullptr, // 무형식 리소스가 아니면 nullable, 리소스의 첫 번째 밉맵 레벨에 대한 뷰 생성한다
&m_RenderTargetView // 생성한 렌더 타겟 뷰를 반환
);
// 사용한 COM 인터페이스을 해제(Reference count를 줄인다)
ReleaseCOM(_BackBuffer);
}
그 다음으로는 깊이/스텐실 버퍼와 그에 대한 뷰를 생성해야 한다
깊이/스텐실 버퍼는 깊이와 스텐실의 정보를 담은 2차원 텍스처이므로
D3D11_TEXTURE2D_DESC 구조체와 ID3D11Device::CreateTexture2D 함수로 생성할 수 있다
// ==================== 깊이 / 스텐실 버퍼와 그에 대한 뷰 생성 ====================
// 깊이 / 스텐실 버퍼는 깊이 / 스텐실 정보를 담는 2차원 텍스트
// 2차원 텍스트를 생성하기 위해서는 우선 D3D11_TEXTURE2D_DESC 구조체를 작성한다
D3D11_TEXTURE2D_DESC _DepthStencilDesc;
// 텍스처의 가로 세로(텍셀 단위)
_DepthStencilDesc.Width = m_ScreenWidth;
_DepthStencilDesc.Height = m_ScreenHeight;
// 밉맵 레벨(깊이/스텐실 버퍼의 텍스처는 밉맵 레벨이 하나만 있으면 됨)
_DepthStencilDesc.MipLevels = 1;
// 텍스처 배열의 텍스처 개수(깊이/스텐실 버퍼는 텍스처 하나만 필요)
_DepthStencilDesc.ArraySize = 1;
// 텍셀의 형식( 열거형 DXGI_FORMAT)
_DepthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; // 깊이 24비트, 스텐실 8비트
// 4X MSAA 사용 여부 체크(Swap Chain의 설정과 일치해야 함)
if (m_Enable4xMsaa)
{
_DepthStencilDesc.SampleDesc.Count = 4; // 픽셀당 멀티샘플 수
_DepthStencilDesc.SampleDesc.Quality = m_4xMassQuality - 1; // 원하는 품질 수준 지정
}
else
{
_DepthStencilDesc.SampleDesc.Count = 1; // 픽셀당 멀티샘플 수
_DepthStencilDesc.SampleDesc.Quality = 0; // 이미지 품질 수준
}
// 텍스처의 용도(깊이/스텐실 버퍼는 일반적으로 Default를 설정한다)
_DepthStencilDesc.Usage = D3D11_USAGE_DEFAULT; // GPU가 리소스를 읽고 쓰기(CPU는 불가능)
// 리소스의 파이프라인 단계에 바인딩하기 위한 플래그
_DepthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; // 깊이/스텐실 버퍼일 경우 지정하는 플래그
// CPU의 리소스 액세스 방식 결정
_DepthStencilDesc.CPUAccessFlags = 0; // 0: CPU 액세스 필요 없음
// 기타 플래그들(깊이/스텐실 버퍼에는 적용 안 됨)
_DepthStencilDesc.MiscFlags = 0; // 0: 지정 안 함
// 작성한 D3D11_TEXTURE2D_DESC 구조체로 깊이/스텐실 버퍼와 그에 대한 뷰를 생성한다
// 깊이/스텐실 버퍼 생성
HR(m_D3DDevice->CreateTexture2D(
&_DepthStencilDesc, // 생성할 텍스처를 서술하는 구조체
nullptr, // 덱스처에 채울 초기 데이터의 포인터(깊이/스텐실 버퍼에는 불필요)
&m_DepthStencilBuffer) // 깊이/스텔실 버퍼 반환
);
// 깊이/스텐실 버퍼에 대한 뷰 생성
if (m_DepthStencilBuffer)
{
HR(m_D3DDevice->CreateDepthStencilView(
m_DepthStencilBuffer, // 뷰를 생성하고자 하는 리소스(여기선 깊이/스텐실 버퍼)
nullptr, // D3D11_DEPTH_STENCIL_VIEW_DESC 구조체(무형식이 아니라면 null 가능)
&m_DepthStencilView) // 깊이/스텔실 뷰 반환
);
}
그 다음으로는 생성한 후면 버퍼의 뷰(렌더 타겟 뷰)와 깊이/스텐실 버퍼에 대한 뷰를
파이프라인의 출력 병합기(Output Merger) 단계에 바운딩을 하면 된다
이 과정을 거쳐야 리소스들이 파이프라인의 렌더 타겟과 깊이/스텐실 버퍼로 작용하게 된다
출력 병합기(OM, Output Merger) 단계란 Direct3D 그래픽 파이프라인의 마지막 단계로,
다양한 유형의 출력 데이터(픽셀 셰이더 값, 깊이 및 스텐실 정보)를
렌더 타겟 및 깊이/스텐실 버퍼의 내용과 결합하여 최종 파이프라인 결과를 생성한다
// ==================== 생성한 뷰를 출력 병합기(Output Merger) 단계에 Binding ====================
m_D3DDeviceContext->OMSetRenderTargets(
1, // 바인드할 렌더 타겟의 수
&m_RenderTargetView, // 렌더 타겟 뷰들(포인터)의 배열에 대한 포인터(첫 번째 요소)
m_DepthStencilView // 파이프라인에 바인딩할 깊이/스텐실 뷰의 포인터
);
마지막으로는 뷰포트를 설정하면 된다
뷰포트(Viewport)란 그리기 처리를 하고자 하는 후면 버퍼의 부분직사각형 영역이며,
뷰포트를 이용하면 후면 버퍼의 특정 부분에만 그리기 처리를 할 수 있다
(2인용 모드나 UI 등에 사용하는 등)
뷰포트는 D3D11_VIEWPORT 구조체로 설정할 수 있다
// ==================== 뷰포트 설정 ====================
// 뷰포트에 대한 설정을 D3D11_VIEWPORT 구조체에 서술한다
D3D11_VIEWPORT vp;
vp.TopLeftX = 0.0f; // 뷰포트의 시작 X좌표
vp.TopLeftY = 0.0f; // 뷰포트의 시작 Y좌표
vp.Width = static_cast<float>(m_ScreenWidth); // 뷰포트의 넓이
vp.Height = static_cast<float>(m_ScreenHeight); // 뷰포트의 높이
vp.MinDepth = 0.0f; // 최소 깊이 버퍼 값
vp.MaxDepth = 1.0f; // 최대 깊이 버터 값
// 파이프라인의 래스터라이저 단계에 뷰포트 배열을 바인딩한다
m_D3DDeviceContext->RSSetViewports(
1, // 바인딩할 뷰포트의 수
&vp // D3D11_VIEWPORT 구조체
);
이상으로 Direct3D의 초기화는 완료되지만,
화면을 특정색으로 클리어하기 위해서는 다음과 같은 처리가 필요하다
// 후면 버퍼를 클리어한다(색상은 DirectXColors.h의 값을 사용)
m_D3DDeviceContext->ClearRenderTargetView(
m_RenderTargetView,
//reinterpret_cast<const float*>(&DirectX::Colors::Crimson)
DirectX::Colors::DarkTurquoise // DirectXColors.h
);
// 깊이 / 스텐실 버퍼를 클리어한다
m_D3DDeviceContext->ClearDepthStencilView(
m_DepthStencilView,
D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL,
1.0f, // 깊이 버퍼를 1.0f로 클리어
0 // 스텐실 버퍼를 0으로 클리어
);
// 후면 버퍼를 화면에 Present한다
HR(m_SwapChain->Present(0, 0));
기타 참고 사이트)
https://alexjadczak.wordpress.com/2014/05/18/updating-directx-11-to-windows-8-0-sdk/
'게임 프로그래밍(학습 내용 정리) > 3D Game Programming' 카테고리의 다른 글
Primitive (0) | 2022.03.25 |
---|---|
렌더링 파이프라인 (0) | 2022.03.24 |
Transform(변환) 3 (0) | 2022.03.23 |
Transform(변환) 1 (0) | 2022.03.16 |
Direct3D 11의 초기화하기 전에 알아야 할 기본 지식 (0) | 2022.03.10 |