Device, Context와 SwapChain 생성
Device/Context
렌더링을 위해 첫 번째로 해야 하는 것은 디바이스와 컨텍스트를 초기화하는 것이다.
디바이스(ID3D11Device)는 하드웨어와 상호작용하여 GPU에 리소스를 할당하거나 백 버퍼를 지우게 하는 등의 일을 시킬 수 있게 하는 인터페이스이다.
디바이스 컨텍스트(ID3D11DeviceContext)는 디바이스가 가진 리소스를 이용해 파이프라인 상태를 설정하고 렌더링 명령을 생성하는 데 사용되는 인터페이스이다.
ComPtr<ID3D11Device> _device;
ComPtr<ID3D11DeviceContext> _deviceContext;
인터페이스 이름에서 'I'는 COM 인터페이스를 의미한다. COM이란, Component Object Model로, DirectX가 프로그래밍 언어에 독립적이고 하위 호환성을 가지도록 하는, 여러 기능이 캡슐화된 컴포넌트이다. COM 객체는 직접 new/delete로 만들거나 소멸시킬 수 없고, reference counting 시스템을 사용해 수명을 제어한다. AddRef와 Release 함수를 호출하여 reference counting을 직접 관리할 수 있지만, 오류가 발생하기 쉽고 구현이 어려우므로 스마트 포인터(Microsoft::WRL::ComPtr)를 사용하는 것이 좋다.
SwapChain
버퍼 하나만 가지고 화면을 그리고 출력하게 되면, 버퍼를 지우고 다시 그리는 과정에서 출력되는 경우가 발생해 화면이 깜빡거리는 것처럼 보이는 문제가 발생하게 된다. 이를 해결하기 위해 두 개의 화면을 그리는 용도의 후면 버퍼(back buffer)와, 화면을 출력하는 용도인 전면 버퍼(front buffer)를 활용하는 더블 버퍼링 기법을 이용해야 한다. 후면 버퍼에 화면이 그려지면, 두 버퍼의 역할이 바뀌게 된다. 즉 후면 버퍼는 전면 버퍼가 되고, 전면 버퍼는 후면 버퍼가 된다. 이렇게 두 버퍼의 역할이 전환되는 것(Swapping)을 presenting이라고 한다.
전면 버퍼와 후면 버퍼는 swap chain을 형성하며, IDXGISwapChain 인터페이스로 나타내어진다. IDXGISwapChain은 전면 버퍼와 후면 버퍼를 저장하고, 창 크기를 조정할 수 있는 ResizeBuffers, 후면 버퍼의 내용을 전 버퍼로 전환시키는 Present 등의 메서드를 제공한다.
ComPtr<IDXGISwapChain> _swapChain;
여기서 DXGI는 DirectX Graphics Infrastructure로, 그래픽스에서 잘 변하지 않는 일부 작업들을 관리하여 향후 그래픽 구성요소를 위한 공통 프레임워크를 제공한다. 대표적으로 swap chain이 이에 포함된다.
SwapChain 초기화
DXGI_SWAP_CHAIN_DESC desc;
ZeroMemory(&desc, sizeof(desc));
{
desc.BufferDesc.Width = _clientWidth; //너비
desc.BufferDesc.Height = _clientHeight; //높이
desc.BufferDesc.RefreshRate.Numerator = 60; //Hz당 최대 주사율
desc.BufferDesc.RefreshRate.Denominator = 1; //최소 주사율
desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; //display format
desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; //주사선 그리기 모드(unspecified)
desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; //모니터 해상도에 맞에 늘어나는 방식(unspedified)
if (_enable4xMsaa) //멀티샘플링
{
desc.SampleDesc.Count = 4;
desc.SampleDesc.Quality = _4xMsaaQuality - 1;
}
else
{
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
}
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; //(후면 버퍼를 표면 또는 렌더 타겟으로서 리소스로 사용한다)
desc.BufferCount = 1; //스왑 체인에서 사용할 후면 버퍼 수
desc.OutputWindow = _hMainWnd; //렌더링할 윈도우 핸들
desc.Windowed = true; //true:창모드 false:전체화면 모드
desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; //화면에 표시한 버퍼의 내용을 처리하기 위한 옵션(가장 효율적인 기술 옵션)
desc.Flags = 0; //옵션
}
SampleDesc는 멀티샘플링을 설정하는 부분이다.
https://gamzachips.tistory.com/63
[DirectX11] 멀티샘플링 (안티앨리어싱 기법)
슈퍼샘플링 supersampling 컴퓨터 모니터의 픽셀수는 한정적이기 때문에 선을 완벽하게 표현할 수 없다. 따라서 선이 픽셀단위로 깨져서 계단처럼 보이는 계단 현상(stair-step effect) 또는 앨리어싱(ali
gamzachips.tistory.com
Device/Context/SwapChain 생성
HRESULT hr = ::D3D11CreateDeviceAndSwapChain(
nullptr, // default adapter
_driverType,
nullptr, // no software device
createDeviceFlags,
nullptr,
0,
D3D11_SDK_VERSION,
&desc,
_swapChain.GetAddressOf(),
_device.GetAddressOf(),
nullptr,
_deviceContext.GetAddressOf()
);
CHECK(hr);
여기서 GetAddressOf() 함수는 ComPtr<>이 가지는 포인터에 대한 주소를 반환하는 함수이다. (T**)
포인터 자체를 가져오려면 Get()함수를 사용한다. (T*)
HRESULT는 대부분의 COM 인터페이스 함수들이 반환하는 값으로, 작업의 성공 여부를 나타내는 오류 코드이다.
Depth Stencil View와 Render Target View 생성
Texture
Texture는 이미지 데이터를 저장하는 데 사용되기도 하지만, 특정 종류의 데이터 형식을 저장할 수 있는 행렬로서 normal mapping과 같은 다른 용도로도 사용된다.
texture는 DXGI_FORMAT 타입으로 표현된다. 예를 들어 DXGI_FORMAT_R32G32B32_FLOAT은 각 원소가 세 개의 32비트 float를 구성요소로 가지는 것이다. 이때 R,G,B,A는 red, green, blue, alpha를 나타낸다. RGB는 기본 색상의 조합이며, alpha 채널 또는 alpha 컴포넌트는 일반적으로 투명도를 조절하는 데 사용된다. 하지만 꼭 색상 정보를 저장해야 하는 것은 아니며, float 좌표의 3D 벡터를 저장할 수도 있다.
일단 메모리를 예약해놨다가 텍스처가 파이프라인에 바운딩될 때 어떻게 재해석해야 하는지를 명시하는 typeless 형식도 있다.
Resource View
Texture는 렌더링 파이프라인의 여러 단계에 바운딩될 수 있다. 하지만 texture와 같은 리소스는 직접 바운딩될 수 없으므로, 바운딩될 texture에 대한 resource view를 생성해야 한다. resource view는 Direct3D에게 리소스가 어떻게 사용될지를 알려주고, texture가 typeless로 생성된 경우 이에 대한 view를 생성할 때 타입을 정한다. 즉 같은 texture라도 이로부터 다른 타입의 view가 생성될 수 있는 것이다.
리소스에 대한 특정 종류의 view가 생성되기 위해서는 리소스가 특정한 bind flag로 설정되어야 한다.
Render Target View
Render target view는 렌더링 파이프라인의 output merger 단계에 전면 버퍼를 바인딩하기 위한 view이다. 즉, Direct3D는 여기에 그리게 된다.
ComPtr<ID3D11RenderTargetView> _renderTargetView;
HRESULT hr;
ComPtr<ID3D11Texture2D> backBuffer = nullptr;
hr = _swapChain->GetBuffer( //스왑체인의 후면버퍼를 가져옴
0, //가져올 후면 버퍼의 인덱스. 후면 버퍼가 하나이므로 0번
__uuidof(ID3D11Texture2D), //버퍼의 인터페이스 타입. 보통 항상 2D texture
(void**)backBuffer.GetAddressOf() //후면 버퍼를 가리키는 포인터
);
CHECK(hr);
hr = _device->CreateRenderTargetView( //redner target view 생성
backBuffer.Get(), //그릴 대상이 될 버퍼(후면 버퍼)
nullptr, //ID3D11_RENDER_TARGET_VIEW_DESC의 포인터. 타입이 지정된 형식이면 없어도 됨
_renderTargetView.GetAddressOf() //redner target view를 생성할 객체
);
CHECK(hr);
Depth Stencil View
맨 앞에 있어서 그려져야 할 물체의 픽셀을 결정하기 위해서 depth buffering 또는 z-buffering 기법을 이용한다. 화가가 그림을 그리듯이 멀리 있는 것부터 가까운 것 순서대로 그리면 되지 않을까 싶지만, 큰 데이터셋을 정렬해야 한다는 점과 도형의 충돌 문제가 있다.
깊이 버퍼(depth buffer)는 후면 버퍼의 각 픽셀에 대한 깊이 정보가 저장된 2D texture이다. 화면을 그리기 전에, 후면 버퍼는 디폴트 색상으로 초기화되고, 깊이 버퍼는 보통 1.0으로 초기화된다. 깊이 버퍼의 깊이 값은 [0.0, 1.0] 범위의 값으로 정규화되어있으므로, 가장 먼 1.0으로 초기화하는 것이다. 3D에서는 화면에서의 한 픽셀에 투영되는 픽셀이 여러 개일 수 있다. 따라서 투영되는 각 픽셀에 대해 깊이를 테스트하여 깊이값이 현재 저장된 깊이보다 더 작으면 갱신함으로써, 최종적으로 가장 가까운 깊이값을 저장한다. 이 깊이에 있는 픽셀이 후면 버퍼에 기록될 픽셀이다.
ComPtr<ID3D11Texture2D> _depthStencilBuffer;
ComPtr<ID3D11DepthStencilView> _depthStencilView;
깊이 버퍼로 사용할 텍스처 버퍼를 생성하기 위해 D3D11_TEXTURE2D_DESC를 채워서 CreateTexture2D 메서드를 호출하여 넘겨주어야 한다.
D3D11_TEXTURE2D_DESC desc = { 0 };
ZeroMemory(&desc, sizeof(desc));
desc.Width = _clientWidth; //가로 픽셀 수
desc.Height = _clientHeight; //세로 픽셀 수
desc.MipLevels = 1; //depth/stencil buffer를 만들 때 mipmap level은 1만 필요
desc.ArraySize = 1; //texture 배열의 texture 수. depth/stencil buffer를 만들 때 하나만 필요
desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; //[0,1]범위에 매핑되는 24비트 depth buffer와 8비트 stencil buffer
if (_enable4xMsaa) //멀티샘플링
{
desc.SampleDesc.Count = 4;
desc.SampleDesc.Quality = _4xMsaaQuality - 1;
}
else
{
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
}
desc.Usage = D3D11_USAGE_DEFAULT; //GPU만 read/write. depth/stencil buffer는 GPU가 모두 읽고 쓴다
desc.BindFlags = D3D11_BIND_DEPTH_STENCIL;//파이프라인에 depth/stencil버퍼로 바인딩
desc.CPUAccessFlags = 0; //CPU의 접근을 지정.CPU는 depth/stencil 버퍼를 읽고 쓰지 않으므로 0으로 초기화
desc.MiscFlags = 0; //옵션. 적용하지 않음
HRESULT hr = _device->CreateTexture2D(&desc, nullptr, _depthStencilBuffer.GetAddressOf());
CHECK(hr);
CreateTexture2D 메서드의 두 번째 인자는 texture를 채울 첫 데이터를 가리키는 포인터인데, depth/stencil buffer로 사용할 것이므로 채우지 않는다.
D3D11_DEPTH_STENCIL_VIEW_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
desc.Texture2D.MipSlice = 0;
HRESULT hr = _device->CreateDepthStencilView(_depthStencilBuffer.Get(), &desc, _depthStencilView.GetAddressOf());
CHECK(hr);
참고자료
Introduction to 3D Game Programming with DirectX11 - Frank D. Luna
Rookiss님 인프런 멘토링 강의
Microsoft문서
https://learn.microsoft.com/en-us/windows/win32/api/dxgi/ns-dxgi-dxgi_swap_chain_desc
https://learn.microsoft.com/ko-kr/windows/win32/direct3d11/overviews-direct3d-11-devices-intro
https://learn.microsoft.com/ko-kr/windows/win32/prog-dx-with-com
https://learn.microsoft.com/ko-kr/windows/win32/api/dxgi/nn-dxgi-idxgiswapchain
'그래픽스(DirectX)' 카테고리의 다른 글
[Graphics] Ray와 물체의 충돌 판정 알고리즘(AABB/OBB) (0) | 2023.05.14 |
---|---|
[DirectX11] 멀티샘플링 (안티앨리어싱 기법) (0) | 2023.05.12 |
[DirectX11] Ray와 물체의 충돌 판정 (Intersection) (0) | 2023.05.01 |
[DirectX11] 2D화면 클릭해서 3D물체 선택하기(picking ray) (0) | 2023.04.29 |
[DirectX11] 삼각형 그리기 (렌더링 파이프라인) (0) | 2023.04.29 |
댓글