DirectX는 고성능 그래픽, 사운드 등의 주변 장치와 상호 작용을 필요로 하는 프로그램(특히 게임)을 작성하는 데 사용하는 API이다.

API 모음이라고 생각하면된다.
그렇다면 DirectX를 이용하여 간단한 프로그램을 만들어 볼 것이다.

윈도우즈 프로그래밍 카테고리에서 만들었던 Hello World 창 만드는 코드에서 덧붙여서 작성할 것이다. 

추가할 것은 헤더파일이다.


(graphics.h 추가)


헤더파일을 추가해주고, graphics 포인터를 만들것이다.



(graphics 포인터)


Graphics라는 클래스를 우리는 아직 만들지 않았다. 이제 만들어 볼 것이다.

헤더 파일을 하나 생성해준다. graphics.h



(include, define 부분)


이 헤더를 중복하여 정의가 되면 에러가 난다. 그러므로 컴파일 할 때 이 파일이 포함되어 있다면 다시 포함시켜 중복정의가 일어나지 않도록 첫부분에서 해주었다.

Graphics 클래스를 만들어준다.



(Graphics_private)


private 영역이다. 사용할 변수들과 initD3Dpp 함수를 선언했다.




(Graphics_public)


public 영역이다.
기본적인 생성자와, 소멸자를 선언했고, releaseAll() 함수는 소멸자에서 사용할 것인데 할당 받은 것들을 전부 반환시키기 위한 함수이다.
initialize 함수는 Graphics 클래스를 만든 후 초기화할 때 사용할 함수이다.
그 다음 showBackbuffer 함수를 선언한다.
자세한 함수 내용은 아래서 코드 설명할 때 할 것이다!

기존 Hello World 프로그램의 WinMain 함수에서 추가한다.



(메모리 누수)


위의 두줄은 메모리 누수를 탐지하기 위한 것이다.
디버그 모드로 빌드 시 메모리 누수를 확인한다.

메모리 누수란 프로그램이 종료될 때 할당받았던 메모리를 잘 반납하지 않고 종료가 되어 메모리가 야금야금 줄어드는 현상이다. 그렇기에 생성한 객체들을 종료할 때는 잘 반납시켜주고 종료해야한다.

윈도우를 생성한 뒤 Graphics 객체를 생성한다.



(Graphics 객체 생성)


아까 만들었던 graphics 변수에 Graphics 객체를 생성한다.
그리고 initialize 함수로 초기화 해준다.

이제 그 함수들을 살펴볼 것이다.



(Graphics 생성자)


Graphics 생성자에서는 사용하는 변수들에 대한 초기 설정을 해준다.
direct3d 라는 변수에는 우리가 Direct3D 객체를 만들 것이다.

device3d 라는 변수는 Direct3D 객체의 함수를 이용하여 디바이스 인터페이스를 만들고 저장할 곳이다.



(initialize 함수)


Direct3DCreate9 함수는 Direct3D 객체를 만들고 객체에게 줄 인터페이스를 얻는 함수이다.
함수 인자로는 해당 SDK의 버전을 넘겨줘야한다.
D3D_SDK_VERSION 값은 DirectX 헤더 파일에 의해 설정되는 값이다. 컴파일 하는 동안 사용되는 DirectX의 버전이 대상 시스템에 설치된 런타임 DLL과 일치하는지 확인하는데 사용되는 값이다.

성공적으로 실행되면 IDirect3D9 인터페이스를 가리키는 포인터를 반환하고 그렇지 않으면 NULL을 반환한다.
그 후 디바이스를 생성하는 코드이다.
CreateDevice 함수는 디바이스 인터페이스를 반환한다.
아래 순서대로 인자의 정보이다.

Adapter : 사용할 디스플레이 어댑터 수. D3DADAPTER_DEFAULT는 기본 디스플레이 어댑터를 지정하는데 사용된다.

DeviceType : D3DDEVTYPE_HAL. 하드웨어 레스터 변환을 지정한다. 이 값으로 지정한 후 CreateDevice가 실패하면 어댑터가 하드웨어 그래픽 가속을 지원하지 않는다는 것을 의미한다.

hFocusWindow : 포커스 윈도우는 Direct3D에게 애플리케이션이 포그라운드모드에서 백그라운드 모드로 전환했다고 알려준다. 창화면만 사용하면 NULL로 설정한다. 일반적으로 윈도우에 대한 핸들을 넘겨준다.

BehaviorFlag : 정점 처리를 어떻게 하는지에 대한 설정이다. 일단 소프트웨어를 이용한 정점 처리로 설정한다. 뒷부분에서 하드웨어가 지원 가능한지 체크하는 코드부분을 추가하고 하드웨어 정점 처리 방식을 추가할 것이다.

*pPresentationParameters : 생성할 디바이스에 대한 속성을 지정하는 D3DPRESENT_PARAMETERS 구조체를 가리키는 포인터이다. 이 구조체를 채우기 위해 initD3Dpp 함수를 만들 것이다.

**ppReturnedDeviceInterface : 반환값이다. 이 주소에 생성된 디바이스의 주소가 적힐 것이다.

그렇다면 이제 D3DPRESENT_PARAMETERS 구조체를 채울 initD3Dpp 함수를 볼 것이다.




(initD3Dpp 함수)


d3dpp 구조체의 값을 설정하는 함수이다.

이제 이렇게 디바이스까지 생성을 했다면 WinMain 함수를 이어서 본다.



(메인 메시지 루프)


처리할 메시지가 없다면 화면을 그려주는 graphics의 showBackbuffer() 함수를 실행한다.




(showBackbuffer 함수)


버퍼를 표시하기 전에 버퍼를 비우는 함수가 나온다.
Clear 함수이다.
인자는 순서대로 다음과 같다.
Count : pRects가 가리키고 있는 배열에 나열돼 있는 지울 사각형의 개수이다. pRects가 NULL이라면 0으로 설정한다.

pRects : 지울 사각형을 기술하는 D3DRECT 구조체 배열을 가리키는 포인터이다. 여기서는 버퍼 전체를 지울것이기 때문에 NULL로 설정한 것이다.

Flags : 비울 표현의 형식을 지정하기 위해 D3DCLEAR에 정의된 하나 이상의 플래그다. 여기서는 D3DCLEAR_TARGET을 사용한다. 이는 버퍼를 비우길 원한다는 것이다.

Color : RGB색상의 값이다.

Z : 깊이 버퍼이다. 0에서 1사이의 부동소수점 숫자로 z값을 정한다. 여기서는 사용하지 않으므로 0.0f로 설정한다.

Stencil : 스텐실 버퍼는 깊이 버퍼와 관련이 있다. 여기서 사용하지 않을 것이므로 0으로 설정한다.

그 후 Present 함수로 화면에 백 버퍼를 표시한다.
아까 우리가 initD3Dpp 함수에서 SwapEffect 변수에 DISCARD라는 값으로 설정했다.
화면에 보이는 버퍼와 화면에 보이지 않는 버퍼(백 버퍼)가 존재한다.
Page Flipping 기술을 쓸 것인데 화면에 표시되고 있을 때 백 버퍼에 그 다음에 출력할 것들을 계산해서 넣어두는 것이다. 그리고 화면에 백버퍼에 있던 것을 보여준다. 그러면 그 전에 화면에 보이던 버퍼가 백버퍼가 된다. 이런식으로 화면을 넘기면서 움직이는 것이다.

DISCARD로 설정한 것은 이전에 보였던 버퍼의 내용을 삭제하라고 하는 것이다. 디스플레이 버퍼의 이전 내용을 삭제하면 페이지 전환에서 최고의 성능을 발휘할 수 있다.

그렇다면 이제 빌드 한 후 프로그램을 실행 시켜보겠다.



(프로그램 실행)


화면처럼 초록색화면이 뜨는 것을 볼 수 있다.

이제는 ESC 키를 추가해 볼 것이다. 전체 화면일 때는 엑스박스가 없으므로 종료 시킬 수가 없다.

윈도우즈 프로그래밍 카테고리에서 키보드 입력 포스팅글에 나온 대로 추가한다.



(ESC키 메시지)


ESC키가 눌리면 프로그램 종료 메세지를 메세지 큐에 보낸다.

다음은 FULLSCREEN 변수를 통해 전체화면으로도 동작 할 수 있도록 만들어 볼 것이다.



(전체화면)


이전에는 WS_OVERLAPPEDWINDOW 로만 설정했었는데, 이제 FULLSCREEN의 값에 따라 전체화면인지 창화면인지 선택 할 수 있다.

더 추가해 볼것은 창 화면의 크기이다.
윈도우를 생성할 때 CreateWindow 함수의 width와 height는 윈도우 타이틀 바와 테두리를 포함하기 때문에 우리가 원하는 정확한 클라이언트 영역의 크기가 아니게 된다. 우리는 게임이 표시되는 테두리 안쪽 부분의 크기를 정확하게 재조정할 필요가 있다.

클라이언트 영역의 크기를 구하기 위해 GetClientRect를 사용한다.
새로 정의될 너비와 높이이다.
새 너비 = 원하는 너비 + ( 원하는 너비 - 클라이언트 너비 )

새 높이 = 원하는 높이 + ( 원하는 높이 - 클라이언트 높이 )



(크기 재조정)


크기를 재조정하는 함수는 MoveWindow 함수를 이용할 것이다.

이제 마지막으로 아까 소프트웨어 정점 처리만 사용한 것을
만약 지원가능한 어댑터를 찾았다면 하드웨어 정점 처리를 사용할 수 있도록 처리할 것이다.

graphics.h 헤더 파일에서 다음을 추가해준다.



(추가)


그리고 isAdapterCompatible 함수를 작성해준다.



(isAdapterCompatible 함수)


이GetAdapterModeCount 함수는 지정된 어댑터에 대해 사용 가능한 디스플레이 모드의 수를 반환하는 함수이다.

그리고 하나씩 돌아보면서 지원가능한 모드를 만나면 true를 반환한다.
EnumAdapterModes 함수를 통해 pMode의 구조체에 하드웨어에 대한 자세한 정보를 얻을 수 있다.

initialize 함수에 추가해 볼 것이다.



(새 initialize 함수)


전체 화면이고 어댑터가 호환이 된다면 refresh 속도를 호환되는 것으로 설정한다.
그리고 하드웨어 정점 처리 기능을 수행할 수 있는 지 체크하기 위해 GetDeviceCaps 메소드를 사용한다. 가능하다면 behavior에 하드웨어 정점처리 플래그를 넣고 가능하지 않다면 소프트웨어 정점처리 플래그를 넣고 behavior를 디바이스 생성할 때 넘겨주면 된다.

(* 참조 - 2D 게임 프로그래밍, 찰스 켈리)

+ Recent posts