간단한 Direct3D Program을 만들어보자. 물론 '간단'이라는 말이 우습기는 하다만.... '간단'해 지려면 우선 API를 알아야 하고 C, C++를 알아야 하며 COM에 대해서도 감을 잡고 있어야 한다.
이게 '간단'인가? =.=; 헐
글쓴이가 말한대로 간단한 D3D (Direct3D) 프로그램은 다음과 같은 4단계 구조를 가진다.
2. D3D를 초기화 하는 함수 생성 및 D3D device 생성
3. 한개의 화면을 그려주는 함수 생성
4. D3D 닫기
1. 전역 변수 선언과 함수들의 프로토 타입 선언
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
// global declarations
LPDIRECT3D9 d3d; // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev; // the pointer to the device class
// function prototypes
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); // renders a single frame
void cleanD3D(void); // closes Direct3D and releases memory
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
헐헐, 갑작스레 등장하는 API 코드들에 난감스럽다.>.<
글쓴이는 친철하게 하나 하나 설명해 주고 있는데, 우선 #include <d3d9.h>는 Direct3D 9의 헤더 파일을 포함시키는 것이다.
#pragma comment(lib, "d3d9.lib") 요놈은 Direct3D 9의 라이브러리 파일을 포함시키라는 명령인데 첫번째 인자가 어떤 형태의 파일을 추가 할 것인지 그리고 두번째 놈이 파일 이름이다.
LPDIRECT3D9 d3d; 앞에 붙은 타입 명, 고녀석 이름도 참 거창하고 길다. 저런 생전 처음 보는 타입을 두려워 할 필요 없다. 저것은 보통 클래스나 구조체를 가리키는 포인터 타입일뿐 이해 불가능한 것이 아니다. 이 변수는 나중에 저 클래스 타입을 가지는 클래스의 객체를 선언한 후 그 놈을 가리키게 될것이다.
LPDIRECT3DDEVICE9 d3ddev; 이놈은 더 길다. 젠장. 이놈은 사실 Direct3D Device interface 라는 놈인데 이것도 사실 알고보면 분명히 클래스일 것이고 좀더 자세히 말해서 COM 컴포넌트 일것이다. 혹은, 추상 클래스 정도라고 추측할수 있다. 어쨌건 간에 이놈은 그래픽 장치나 비디오 카드등 그래픽과 관련된 하드웨어에 대한 일괄된 정보를 관리한다고 보면 된다.
그 아래에 3개의 함수 원형이 선언 되어 있는데 이것은 옆의 주석 설명대로 초기화 함수, 화면 그려주기 함수, 메모리 해제 함수다. 이로써 1단계가 끝났다.
2. D3D를 초기화 하는 함수 생성 및 D3D device 생성
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION); // create the Direct3D interface
D3DPRESENT_PARAMETERS d3dpp; // create a struct to hold various device information
ZeroMemory(&d3dpp, sizeof(d3dpp)); // clear out the struct for use
d3dpp.Windowed = TRUE; // program windowed, not fullscreen
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // discard old frames
d3dpp.hDeviceWindow = hWnd; // set the window to be used by Direct3D
// create a device class using this information and information from the d3dpp stuct
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);
return;
}
D3D 코딩의 첫파트는 사실 interface를 만들고 graphic device를 초기화 하는 것이다. 여기서 interface의 의미가 와닿지 않는다면 김상형씨께서 쓰신 혼자연구하는 C/C++ 책 29장 상속편을 읽어보면 참 좋을것이다.
사실 이부분에서는 크게 설명할 원리는 없고 다만 뭐가 뭐다라는 식의 설명만 가능한데, 이것은 예전에 OpenGL을 배울때와 유사하다.
첫 프로그램을 짤때는 이렇게 '그렇다더라...'는 식의 코드를 많이 접하는데 이는 어쩔수가 없다. 특히 Direct3D 처럼 거대한 라이브러리 함수를 가지고 노는 프로그램의 경우는 더더욱 심하다.
원문을 쓰신 분이 나름 추가의 글을 적어 놨기에 나도 옮겨 적어야 할것 같다.... -_-
d3d = Direct3DCreate9(D3D_SDK_VERSION);
이것이 사실상 최초로 접하는 D3D function이다. 이 함수의 목적은 D3D interface를 만드는 것이다. 저 함수가 리턴하는 것은 바로 만들어진 interface의 주소다. (고로 포인터죠) 사실 객체 지향 언어인 C++를 사용하면서 전역으로 함수를 선언하는 것은 조금 모순된다.
C++가 원래 지향하는것이 부품 조립형 프로그래밍인데 전역 변수는 이에 예외를 둠으로써 그런 규칙을 어느정도는 깨고 있기 때문이다. 하지만 D3D에서 선언한 전역 변수가 중요한 이유는 이렇게 호출한 함수가 리턴하는 주소값을 프로그램 여기저기서 수시로 사용하기 때문인데 만약 그 주소값을 저장하는 포인터 변수가 지역 변수여서 함수를 넘나들지 못하면 이 프로그램은 제구실을 할수가 없다.
이건 순전히 내 여담이고.... -_-; 뒤에 인자로 들어가는 값은 항상 저걸로 고정되어 있는데 이값은 버전에 따라서 변하는 모양이다. 어쨌든 저걸 넣어 줌으로써 어떤 버전의 DirectX로 프로그래머가 프로그램을 짰는지 알려주는 모양이다. 고로 이식성이 좋다나 뭐래나...
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp)); // clear out the struct for use
d3dpp.Windowed = TRUE; // program windowed, not fullscreen
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // discard old frames
d3dpp.hDeviceWindow = hWnd; // set the window to be used by Direct3D
요거 다섯줄은 사실 뭉태기로 보면 더 쉽다. 사실 API에도 CreateWindow 함수인가를 쓸때 보면 뒤에 따라붙는 함수 인자가 대략 20가지는 되어 보인다.
졸라게 긴 이런 입력 인자들을 위해서 하나의 구조체를 만들었으니 바로 D3DPRESENT_PARAMETERS와 같은 놈이다. 이런 구조체를 선언하고 그 값중 필요한 것만 골라서 지정한 다음 바로 다음 명령인,
d3d->CreateDevice(....);
를 호출할때 인자로써 이놈을 넣어주면 함수 인자갯수가 확 준다. 좋지 아니한가?
d3dpp.SwapEffect = ... 명령을 보면 세가지 가능한 값이 오는데 우선 우리가 지정한 DISCARD가 제일 빠르다. 이것외에 FLIP과 COPY가 있는데 이 두가지는 첫번째 방법에 비해 느리며 전문가가 특별한 용도로 사용을 주로 한단다. 테이블은 다음과 같다.
Value | Description |
---|---|
D3DSWAPEFFECT_DISCARD | This type of swap chain is used to get the best speed possible. However, if you later want to look at the previous back buffer (which can be useful for various effects), you cannot guarantee the image will still be intact. |
D3DSWAPEFFECT_FLIP | This type is similar to discarding, but is reasonably slower, because it has to take the time to ensure your previous back buffer(s) are protected and unchanged. |
D3DSWAPEFFECT_COPY | This last type is the one I least recommend. Instead of switching pointers like the other two, this method copies the image, pixel by pixel, from the back buffer to the front buffer. This is not prefered, although required for some advanced techniques. |
드디어 나오는데 CreateDevice() 함수다.
UINT Adapter,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS *pPresentationParameters,
IDirect3DDevice9 **ppReturnedDeviceInterface
);
여기서 각각의 인자에 대해서 알아보면,
UINT Adapter : 비 부호 정수형 값을 저장하는데 어떤 그래픽 어댑터나 비디오 카드를 사용할지 알려주는 번호를 저장하나보다.
D3DDEVTYPE DeviceType : 일단은 하나만 쓴다. D3DDEVTYPE_HAL.
D3DDEVTYPE_HAL 는 Direct3D에게 Hardware Abstraction Layer 쓰라고 알려준덴다. The Hardware Abstraction Layer 또는 HAL은 D3D에게 Hardware를 쓰라고 알려주는 것이란다. 뭐 요즘 하드웨어가 다들 고속 프로세싱을 해주니 당연히 HAL을 써야 하겠죠?
HWND hFocusWindow : This is the handle to our window. We can just put 'hWnd' in here as we passed the value from WinMain().
DWORD BehaviorFlags : D3DCREATE_SOFTWARE_VERTEXPROCESSING, D3DCREATE_MIXED_VERTEXPROCESSING, D3DCREATE_HARDWARE_VERTEXPROCESSING.
주로 요 세가지를 다루는데 (물론 많은 값이 들어갈수 있다만) 이름을 보면 대강 설명이된다. 소프트웨어적으로 처리, 하드웨어적으로 처리, 그리고 둘다 합쳐서 처리.
D3DPRESENT_PARAMETERS *pPresentationParameters : This is a pointer to the d3dpp struct that we filled out earlier. We just fill this with '&d3dpp'.
IDirect3DDevice9 **ppReturnedDeviceInterface : graphics device interface를 가리키는 포인터, 미리 선언한대로 d3ddev죠. 주소 전달을 위해서 &d3ddev 전달.
3. 한개의 화면을 그려주는 함수 생성
void render_frame(void)
{
// clear the window to a deep blue
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0);
d3ddev->BeginScene(); // begins the 3D scene
// do 3D rendering on the back buffer here
d3ddev->EndScene(); // ends the 3D scene
d3ddev->Present(NULL, NULL, NULL, NULL); // displays the created frame
return;
}
이제 위 함수는 한개의 장면을 렌더링 하는 함수인데 매우 간단하다. OpenGL에서 보던것과는 좀 다른데, 일단 그리는 것이 아무것도 없어서 그럴수도 있지만 Clear 하는 방법과 Rendering 하는 것이 좀 더 간단해 보인다.
d3ddev->Clear() 함수는 지정된 색으로 윈도우 배경을 지우는 함수다.
d3ddev->BeginScene() 함수는 D3D에게 그림 그릴 준비 하라는 함수다. 이 함수가 불러져야 하는 두가지 이유는 내가 D3D에게 메모리를 관리하고 있음을 알려줘야 하기 때문이고 둘째로 locking 이라는 과정을 통해서 빠르게 비디오 메모리에 접근하는 방식을 설정해주기 때문이다.
d3ddev->EndScene() 이름만 봐도 알겠다. unlocking을 한다.
d3ddev->Present() 이건 뭐 display하라는 명령인가 보다. 자세한 설명은 없군.
4. D3D 닫기
void cleanD3D(void)
{
d3ddev->Release(); // close and release the 3D device
d3d->Release(); // close and release Direct3D
return;
}
이것은 꼭 빼먹지 말아야 하는데 만약 이걸 안하면 프로그램 종료후에도 댕글링 메모리가 남게 되어 리부팅 할때까지 남아있는 문제 발생한다.
'프로그래밍 > DirectX' 카테고리의 다른 글
[DX Tutorial] Lesson 5: Transforming Vertices (0) | 2008.02.07 |
---|---|
[DX Tutorial] Lesson 4: Drawing a Triangle (0) | 2008.02.06 |
[DX Tutorial] Lesson 3: An Overview of the Third Dimension (0) | 2008.02.06 |
[DX Tutorial] Lesson2: Going Fullscreen (0) | 2008.02.02 |
DirectShow란? (0) | 2008.01.15 |