다음 글을 참고하여 작성하는 글입니다. 개인적으로 공부하는 내용이므로 틀린 부분이 있을 수 있습니다. 있다면 알려주세요 :)
http://3dapi.com/bs11_2d_basic/
https://docs.microsoft.com/en-us/windows/win32/direct3d9/direct3d-devices
[디바이스란?]
이전 포스팅에서 간단한 윈도우 프로그램을 만들었고, 기존 코드에서 DirectX 관련 내용을 추가하여 디바이스(Device)를 생성해 볼 것이다. 디바이스란 Direct3D의 가장 기초적이고 핵심적인 객체로 그래픽 카드를 추상화한 객체이다. 하나의 프로그램은 최소 하나의 디바이스 객체를 가지는데, 대부분 1개를 가진다. 우리가 그래픽 카드의 도움을 받아 화면에 그림을 그리기 위해선 디바이스가 꼭 필요하다. 디바이스의 정확한 이름은 Direct3D Device이며, 디바이스는 윈도우 핸들(HWND)과 Direct3D 객체를 통해 생성할 수 있다.
따라서 디바이스 생성 전에 반드시 윈도우 핸들을 생성하고, Direct3D 객체를 생성해야 한다. 이러한 DirectX와 관련된 모든 객체는 COM(Common Object Model)이라는 인터페이스를 상속받는다. 때문에 모든 객체가 공통적으로 Release()라는 가상 함수를 통해 객체를 소멸시킨다. 이러한 객체들의 소멸은 다음과 같이 생성의 역순으로 해야만 한다.
Create 윈도우 핸들 → Create Direct3D 객체 → Create Device 객체 → Release Device 객체 → Release 3D 객체
다음은 Direct3D 그래픽 파이프 라인과 윈도우 애플리케이션과 Direct3D, 하드웨어와의 관계를 나타낸 다이어그램이다. 우선은 간단하게 참고만 하고 넘어가자.
https://docs.microsoft.com/en-us/windows/win32/direct3d9/direct3d-architecture
[디바이스 객체 생성]
우선 DirectX의 Direct3D 라이브러리를 사용하기 위해서 다음과 같이 "d3d9.lib"를 연결해주고, <d3d9.h> 헤더파일을 포함해주어야 한다.
#pragma comment(lib, "d3d9.lib") /* Direct3D를 사용하기 위한 라이브러리 연결 */
#include <d3d9.h>
다음으로 디바이스 객체를 생성하기 위해 윈도우 핸들과 Direct3D 객체가 필요한데 윈도우 핸들은 이미 기존 코드에 존재한다. 따라서 Direct3D 객체만 생성한다. Direct3D 객체는 LPDIRECT3D9 타입이며 디바이스 객체는 LPDIRECT3DDEVICE9 타입이다. 다음과 같이 두 변수를 만들고 nullptr로 초기화한다. 두 변수는 구조체를 만들어 넣어도 되고, 전역 변수로 선언해도 된다. 여기선 구조체를 통해 두 변수를 관리한다.
LPDIRECT3D9 D3D = nullptr; /* Direct3D 객체 */
LPDIRECT3DDEVICE9 D3D_Device = nullptr; /* 그래픽 카드 장치를 추상화한 디바이스 객체 */
다음으로 Create 함수 내에 Direct3DCreate9() 함수를 통해 LPDIRECT3D9 타입의 인스턴스를 생성하여 D3D에 저장한다. 실패할 경우 NULL을 반환한다. 실패 시 -1 대신 E_FAIL이란 값을 반환해도 된다.
/* SDK 버전에 따른 사용 가능 여부를 확인하고 Direct3D 객체를 생성합니다. */
mD3DInfo->D3D = Direct3DCreate9(D3D_SDK_VERSION);
if (!mD3DInfo->D3D)
{
return -1; // return E_FAIL;
}
디바이스를 생성하는데 사용되는 구조체가 하나 있다. D3DPRESENT_PARAMETERS라는 이름의 구조체인데, 이 구조체 변수를 생성하고 값을 설정해준다. 다양한 멤버 변수가 있지만 값을 모두 0으로 설정 후 일부 옵션만 값을 추가로 설정해준다. Windowed는 TRUE로 설정하여 윈도우 내에 영역을 잡도록 한다.(창모드) SwapEffect은 전면 버퍼와 후면 버퍼를 교체하는 스왑체인(swap chain) 방식을 설정하는 것인데 discard로 설정하는 것이 가장 효율적이다.
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = mD3DInfo->bWindowMode;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
전면버퍼와 후면 버퍼(백버퍼)는 더블 버퍼링(Double buffering)과 관련된 내용이다. CPU가 임시 저장 공간인 버퍼에 데이터를 연산하여 저장하면 모니터는 이 버퍼의 데이터를 출력하는데, 출력하는 속도가 CPU의 연산속도보다 훨씬 느리다. 게다가 모니터가 출력하는 도중엔 버퍼를 갱신하면 안되기 때문에 두 작업을 동시에 수행할 순 없다. 때문에 CPU 입장에선 모니터가 모든 데이터를 출력할 때까지 기다리기란 매우 비효율적인 일이다. 그래서 2개의 버퍼를 사용하여, 하나의 버퍼를 출력하는 동안 나머지 버퍼에 데이터를 저장할 수 있도록 하는 것을 더블 버퍼링이라 한다. 2개의 버퍼는 각각 전면 버퍼(출력)와 후면 버퍼(저장)라는 사실을 기억해두자.
DirectX에선 더블 버퍼링과 비슷하게 스왑 체인이라는 방식을 사용한다. 더블 버퍼링에선 후면 버퍼는 데이터를 저장하는 용도로만 사용하고, 이 데이터를 전면 버퍼에 전송하여 전면 버퍼는 화면에 출력하는 용도로만 사용하였다. 스왑 체인 방식은 Presenting이라는 교체 방식을 통해 전면 버퍼의 출력이 완료되면 후면 버퍼와 교체(swap)를 해버린다. 좀 더 정확히 말하면 더블 버퍼링은 값의 복사이고, 스왑체인은 포인터(주소)의 swap이다. 잠시 후 보겠지만, 렌더링 한 데이터를 전면 버퍼와 교체하는 작업을 Present() 함수를 통해서 수행한다.
이제 디바이스 객체를 생성할 수 있다. 디바이스는 Direct3D 객체의 멤버 함수인 CreateDevice()를 통해 생성한다. 첫 번째 인자는 어댑터에 관련된 인자로 특별한 상황이 아닌 이상 디폴트 값을 사용한다. 두 번째 인자는 하드웨어의 가속을 받을 것을 요구하는 값이다. 세 번째 인자는 윈도우 핸들이다. 네 번째 인자는 버텍스 처리에 관한 것으로 지금은 크게 신경쓰지 않아도 된다. 나머지는 위에서 생성한 구조체 변수와 결과를 저장할 디바이스 객체이다.
if (FAILED(mD3DInfo->D3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL
, mhWnd, D3DCREATE_MIXED_VERTEXPROCESSING
, &d3dpp, &mD3DInfo->D3D_Device)))
{
mD3DInfo->D3D->Release();
return -1;
}
이렇게 생성한 디바이스를 통해 장면(그림)을 그릴 수 있으며, 그 과정은 다음과 같다.
- 디바이스의 멤버 함수 Clear()를 통해 후면 버퍼를 초기화한다.
- 디바이스의 멤버 함수 BeginScene()를 통해 장면의 시작을 알린다.
- 장면을 그린다(렌더링).
- 디바이스의 멤버 함수 EndScene()를 통해 렌더링의 종료를 알린다.
- 디바이스의 멤버 함수 Present()를 통해 렌더링된 데이터가 저장된 후면 버퍼를 전면 버퍼와 교체한다.
- 1~5 과정을 반복한다.
위 과정을 구현하면 다음과 같다.
mD3DInfo->D3D_Device->Clear
(
0
, NULL
, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER /* 색상버퍼와 깊이버퍼를 채움 */
, D3DCOLOR_XRGB(0, 120, 160)
, 1.0f
, 0
);
if (FAILED(mD3DInfo->D3D_Device->BeginScene()))
{
return -1;
}
/*
*********************************
이곳에 렌더링 작업을 구현합니다.
*********************************
*/
mD3DInfo->D3D_Device->EndScene();
/* 다음 장면의 픽셀 데이터가 저장된 후면 버퍼를 이전 장면이 담긴 전면 버퍼와 교체합니다. */
mD3DInfo->D3D_Device->Present(0, 0, 0, 0);
렌더링은 반드시 BeginScene()과 EndScene() 사이에 해야 한다. 모든 장면은 후면 버퍼에 그려야 하며 Present()를 통해 전면 버퍼와 교체함으로써 그린 장면을 보여준다.
이번 포스팅은 여기까지며, 다음 포스팅은 스프라이트(Sprite) 객체를 통해 BeginScene()과 EndScene() 사이를 채우는 작업을 할 것이다.
소스 코드: https://github.com/wooPedia/DirectX_Studying/tree/master/SimpleDevice
'게임 공부 > DirectX' 카테고리의 다른 글
DirectDraw 학습 (1) - Initialization (0) | 2020.11.15 |
---|---|
행렬 변환에 대해 알아보자 (0) | 2020.07.05 |
D3DXCreateTextureFromFileEx 함수의 color key (0) | 2020.06.23 |
Direct3D 라이브러리를 이용하여 간단한 게임 화면을 만들어 보았다. (0) | 2020.06.16 |
2D 게임 프로그래밍 (1) | 간단한 윈도우 만들기 (0) | 2020.06.14 |
댓글