D3D Tutorial 1: Create a Device

디바이스 생성

//-----------------------------------------------------------------------------
// 전역변수
//-----------------------------------------------------------------------------
LPDIRECT3D9             g_pD3D       = NULL; // D3D 디바이스를 생성할 D3D객체 변수
LPDIRECT3DDEVICE9       g_pd3dDevice = NULL; // 렌더링에 사용될 D3D디바이스


다른 함수들에서 사용할 수 있도록 전역변수로 선언한다.
g_pD3D는 D3D를 초기화하고 릴리즈 할 때만 사용된다.
렌더링과 관련된 부분은 모두 g_pd3dDevice를 이용하게 된다.
LPDIRECT3D9와 LPDIRECT3DDEVICE9 에서 LP는 long pointer를 의미하고
각각은 IDirect3D9* IDirect3DDevice9* 와 같다
여기서 I는 Component Object Model (COM)의 interface 임을 의미한다.


프로그램이 시작될때 가장 먼저 실행되는 함수인 WinMain()을 살펴보자.

//-----------------------------------------------------------------------------
// Name: WinMain()
// Desc: 프로그램 진입점
//-----------------------------------------------------------------------------
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT )
{
  // 윈도우 클래스 정의, 등록
  WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
                     GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
                     "D3D Tutorial", NULL
  RegisterClassEx( &wc );

  // 윈도우 생성
  HWND hWnd = CreateWindow( "D3D Tutorial", "D3D Tutorial 01: CreateDevice",
                             WS_OVERLAPPEDWINDOW, 100, 100, 300, 300,
                             NULL, NULL, wc.hInstance, NULL );

  // Direct3D 초기화
  if( SUCCEEDED( InitD3D( hWnd ) ) )
  {
       // 윈도우 출력
       ShowWindow( hWnd, SW_SHOWDEFAULT );
       UpdateWindow( hWnd );

       // 메시지 루프
       MSG msg;
       while( GetMessage( &msg, NULL, 0, 0 ) )
       {
           TranslateMessage( &msg );
           DispatchMessage( &msg );
       }
  }

  // 등록된 클래스 소거
  UnregisterClass( "D3D Tutorial", wc.hInstance );
  return 0;
}


어떻게 동작하는지 간단히 살펴보면...

1. 윈도우 클래스를 등록하고 윈도우 생성
2. Direct3D를 초기화 한다.
3-2. 초기화에 실패하면 등록된 윈도우 클래스를 없애고
    프로그램이 종료된다.
3-1. 초기화가 제대로 되었으면
    윈도우를 출력하고 메시지루프를 계속 돌면서
    메시지를 처리한다.
    종료메시지가 들어오면 루프를 빠져나오게 된다
4. 등록된 윈도우 클래스를 없애고 프로그램이 종료된다.

대략 이런 순서로 실행이 된다.

대부분의 내용이 윈도우즈 프로그래밍을 해봤다면
이해가 될 것이다. 아니면 API책을 보는것도 괜찮겠다.


InitD3D()부분을 살펴보도록 하자.

//-----------------------------------------------------------------------------
// Name: InitD3D()
// Desc: D3D초기화
//-----------------------------------------------------------------------------
HRESULT InitD3D( HWND hWnd )
{
  // D3D디바이스 생성을 위한 D3D객체 생성
  if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
       return E_FAIL;

  D3DPRESENT_PARAMETERS d3dpp;                  // 디바이스 생성을 위한 구조체
  ZeroMemory( &d3dpp, sizeof(d3dpp) );          // 구조체를 0으로 채움
  d3dpp.Windowed = TRUE;                        // 창모드
  d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;     // 가장 효율적인 swap효과
  d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;      // 현재 바탕화면 모드에 맞추어 백버퍼생성


  // 디바이스 생성
  // 1. 디폴트 비디오카드를 사용 (대부분은 비디오카드가 한 개다)
  // 2. HAL  (HW 가속 장치를 사용하겠다는 의미)
  // 3. 정점 처리는 모든 카드에서 지원하는 SW 처리로 생성한다 (HW로 생성할 경우 더욱 높은 성능을 낸다.)
  if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                     D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                     &d3dpp, &g_pd3dDevice ) ) )
  {
       return E_FAIL;
  }

  // 디바이스 상태 정보를 처리할 경우 여기에서 한다

  return S_OK;
}

1. D3D 객체 생성
g_pD3D = Direct3DCreate9( D3D_SDK_VERSION )
SDK의 버전에 맞게 헤더파일이 사용되고 있음을 알려준다.
버전이 일치하지 않으면 NULL을 리턴하게 된다.
제대로 되었으면 g_pD3D에 d3d객체가 저장되는데
이것은 d3d디바이스를 생성하는데 사용한다.
잊지말고 프로그램을 종료하기 전에 릴리즈해줘야 한다.

2. Presentation 셋팅
D3DPRESENT_PARAMETERS d3dpp;
디바이스를 어떻게 생성할지 정보를 담는 구조체이다
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp 안에는 여러가지 설정해줄 수 있는것들이 많으므로 0으로 초기화 한다

d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
백버퍼는 렌더링될 화면(컬러)이 저장되는 버퍼로
D3DFMT_UNKNOWN로 하면 포맷을 바탕화면 모드에 맞게 한다.
바탕화면 모드는 디스플레이 등록정보에 보면
색품질에 무슨컬러 몇비트 이렇게 나오는걸 이야기 하는 듯하다
창모드로 띄울경우에 그렇고.. 풀스크린으로 하고싶으면
d3dpp.Windowed = FALSE;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
이와같이 A,R,G,B 각 8비트씩 32비트로 해주거나 혹은 용도에 맞게
설정해주면 될 것이다.
그러나 역시 게임을 개발할때에는 창모드로 해놓고 개발하는것이 편하다.

d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
컬러를 나타내는 버퍼에는 역할상 백버퍼와 프론트버퍼로 나눌 수 있다.
백버퍼는 렌더링하는 버퍼이고 프론트 버퍼는 현재 화면에 보여지는 버퍼이다
버퍼를 한개 이용하는 경우 한장 렌더링하고 다 지우고 다시 렌더링하게 되므로
깜빡깜빡하는 것 처럼 보이게 된다. 그러나 버퍼를 두개를 두어서(더블버퍼링)
실제 렌더링은 화면에 보이지 않는 백버퍼에 그린후에 렌더링이 다 되면
프론트 버퍼에 옮겨서(swap) 화면에 보이게 하는것이다.
스왑하는 방법에는 크게 두가지 있는데 백버퍼의 값을 프론트 버퍼에
카피(D3DSWAPEFFECT_COPY)하는 방법과 백버퍼와 프론트 버퍼의 역할을
서로 바꾸는 것(D3DSWAPEFFECT_FLIP)이다.
D3DSWAPEFFECT_DISCARD로 해주면 상황에 맞게 알아서 잘 해준다.
자세한 내용은 DirectX 도움말을 보자.
d3dpp.BackBufferCount를 이용하면 생성되는 백버퍼의 개수를
설정해 줄 수 있다.이때 백버퍼의 집합을 스왑체인(swap chain)이라고 한다.
일반적으로 백버퍼 개수라고 하면 프론트 버퍼의 개수까지 포함한다.
그냥 컬러버퍼의 개수라고 생각하면된다.
백버퍼중 하나가 화면에 보여지게 될 경우 그것을 프론트버퍼이다

프레임수가 고정되어있는 경우, 대개 60..
이 경우는 화면의 주사율(60Hz)에 맞추어서 화면을 갱신(스왑)하기 때문이다.
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
이렇게 해주면 화면 주사율과 상관없이 기다리지 않고 바로바로 그려지게된다.
풀스크린에서 볼 때는 주사율에 맞추어서 화면이 갱신되는게
오히려 더 자연스럽게 보인다.

이외에도 설정할 수 있는것이 더 많이 있으니
DirectX 도움말을 참고하는것이 좋겠다.

3. D3D 디바이스 생성
g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice )
D3DADAPTER_DEFAULT - 디폴트 비디오카드를 사용 (대부분은 비디오카드가 한 개다)
D3DDEVTYPE_HAL - (HW 가속 장치를 사용하겠다는 의미)
D3DCREATE_SOFTWARE_VERTEXPROCESSING - 정점 처리는 모든 카드에서 지원하는 SW 처리로 생성한다
(D3DCREATE_HARDWARE_VERTEXPROCESSING 로 생성할 경우 더욱 높은 성능을 낸다.)
디바이스 생성이 제대로 되면 g_pd3dDevice 에 저장이 된다.
앞으로 렌더링과 관련된 부분은 모두 g_pd3dDevice를 이용하게 된다.
프로그램을 종료하기 전에 g_pd3dDevice를 반드시 릴리즈 해줘야함을 잊지 말자.


여기까지 무사히 다 성공했다면 S_OK를 리턴한다.
이제 다시 WinMain()으로 돌아가면..

  // Direct3D 초기화
  if( SUCCEEDED( InitD3D( hWnd ) ) ) // 성공!
  {
       // 윈도우 출력
       ShowWindow( hWnd, SW_SHOWDEFAULT );
       UpdateWindow( hWnd );

       // 메시지 루프
       MSG msg;
       while( GetMessage( &msg, NULL, 0, 0 ) )
       {
           TranslateMessage( &msg );
           DispatchMessage( &msg );
       }
  }

초기화를 성공했으므로 윈도우를 출력하고
메시지 루프를 계속 돌면서 들어오는 메시지를 처리한다.
DispatchMessage()가 MsgProc()을 호출하여 메시지를 처리하게된다.
어떻게 메시지를 처리하는지 MsgProc()을 따라가 보자.


//-----------------------------------------------------------------------------
// Name: MsgProc()
// Desc: 윈도우 메시지 핸들러
//-----------------------------------------------------------------------------
LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
  switch( msg )
  {
       case WM_DESTROY:
           Cleanup();
           PostQuitMessage( 0 );
           return 0;

       case WM_PAINT:
           Render();
           ValidateRect( hWnd, NULL );
           return 0;
  }

  return DefWindowProc( hWnd, msg, wParam, lParam );
}


WM_DESTROY 메시지는 윈도우가 종료될때, 종료버튼을 클릭하였을때 발생한다
그러면, Cleanup() 함수를 호출한다. 그런후에 PostQuitMessage()를 호출함으로
다음 메시지 루프가 실행되지 않고 프로그램이 종료된다.

WM_PAINT 메시지는 다시 그려져야할 필요가 있을때,
다른창에 가려졌다 나타날때, 최소화했다가 다시 창을 띄울때 등 발생한다
그러면, Render() 함수를 호출한다.

지금과 같은 경우, WM_PAINT가 발생할때 한 번 렌더링되고 화면이 전혀 갱신되지 않는다.
그러나 게임에서는 WM_PAINT 메시지와 상관없이 화면이 계속 갱신되어야 한다.
따라서 이 위치에 Render()가 들어가는 것은 적절하지 않다.
WinMain()의 루프 안에 위치하는 것이 적절하며 다음과 같은 스타일로 바꾸면 좋겠다.


InitD3D( hWnd ); // 초기화

// 메시지 루프 for game
while(1) {
    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // 메시지 처리
          if(msg.message == WM_QUIT)
               break;
          TranslateMessage(&msg);
          DispatchMessage(&msg);
    } else {
          Render();  // 렌더링
    }
}

Cleanup(); // 릴리즈


어쨌든 Render()함수와 Cleanup()함수를 마저 따라가 보자

//-----------------------------------------------------------------------------
// Name: Render()
// Desc: 화면 그리기
//-----------------------------------------------------------------------------
VOID Render()
{
  if( NULL == g_pd3dDevice )
       return;

  // 백버퍼를 파란색으로 지운다.
  g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );
 
  // 렌더링시작
  if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
  {
       // 렌더링할 오브젝트들이 이곳에
 
       // 렌더링 끝
       g_pd3dDevice->EndScene();
  }

  // 백버퍼를 보이는 화면으로 스왑
  g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}

g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );
위의 코드는 현재 렌더타겟으로 지정되어있는 D3DCLEAR_TARGET 버퍼(백버퍼,컬러버퍼)를
컬러는 D3DCOLOR_XRGB(0,0,255) 파란색으로 클리어하게 한다.

조금 더 일반적으로는 백버퍼와 함께 Z버퍼(깊이버퍼)를 같이 클리어한다.
D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER 이와같이 써주면
백버퍼는 뒤따라 이어나오는 인자인  D3DCOLOR_XRGB(0,0,255)로 클리어하고
Z버퍼는 1.0f로 클리어한다(값의 범위는 0.0f~1.0f).
Z버퍼에 관하여 자세한 내용은 이후 튜토리얼에 나오므로 그 부분에서 다루어 질 것이다.

D3DCOLOR_XRGB(0,0,255) 는 전처리기에 의하여 0xFF0000FF과 같이 바뀌어 컴파일된다.
따라서 D3DCOLOR_XRGB(0,0,255) 대신 0xFF0000FF를 사용하여도 좋다.
0xFF0000FF의 타입은 DWORD 로 이는 다시 D3DCOLOR 로 타입이 재정의 되어있다.
typedef DWORD D3DCOLOR
0xFF0000FF는 앞에서부터 두자리씩 끊어서 알파(A),레드(R).그린(G),블루(B)의 16진수 값을
의미한다.
색깔과 관련한 매크로로 자주 사용하는 것은
D3DCOLOR_XRGB(r,g,b)이외에도 D3DCOLOR_ARGB(a, r, g, b) 정도가 있다.
a,r,g,b 각각의 값은 0~255까지의 정수로 표현하며 0.0f~1.0f의 실수로 표현 할 경우에는
D3DCOLOR_COLORVALUE(r,g,b,a) 를 사용하면 된다.

각각의 매크로 정의는 다음과 같다.
#define D3DCOLOR_ARGB(a,r,g,b) \
  ((D3DCOLOR)((((a)&0xff)<<24)|(((r)&0xff)<<16)|(((g)&0xff)<<8)|((b)&0xff)))
#define D3DCOLOR_RGBA(r,g,b,a) D3DCOLOR_ARGB(a,r,g,b)
#define D3DCOLOR_XRGB(r,g,b)   D3DCOLOR_ARGB(0xff,r,g,b)

#define D3DCOLOR_COLORVALUE(r,g,b,a) \
  D3DCOLOR_RGBA((DWORD)((r)*255.f),(DWORD)((g)*255.f),(DWORD)((b)*255.f),(DWORD)((a)*255.f))

g_pd3dDevice->BeginScene() 는 렌더링의 시작을 그래픽카드(GPU)에게 알려주고
g_pd3dDevice->EndScene() 는 렌더링의 끝을 알려준다.
BeginScene()과 EndScene()은 반드시 쌍으로 이루어지며
어느하나가 빠져서도 안된다.
렌더링 코드들은 BeginScene()과 EndScene() 사이에 위치해야 제대로 렌더링이 되며
BeginScene()과 EndScene() 사이에는 되도록이면 계산하는 코드들을
넣지 않는것이 좋다.

지금 살펴보고 있는 함수는 Render()함수이다. 렌더링에 관련된 코드들이 모아져있는
함수임을 이름만으로도 알수 있다. 이부분에서 실제적으로 렌더링코드와 관련이 없는
게임등에서의 여러가지 처리와 계산이 필요한 것들은 별도로 함수를 만들어서
Render()함수를 호출하기 전에 그 함수를 호출하면 될것이다.

예를들면 아래와 같은 스타일로 할 수 있겠다.
while(1) {
         // 메시지처리
         Process();  // 매프레임 계산이나 처리가 필요한 것들은 모두 이곳에서 한다.
         Render();  // 실제 렌더링 코드들이 여기에 있다.
}
이렇게 따로 나눈 이유는 이후에 다시 살펴보기로 하자.

마지막으로 Cleanup()함수를 살펴보자.
이 함수는 종료메시지가 들어왔을때 실행되는데 InitD3D()함수와 짝을이루어
InitD3D()에서 생성한 것들을 릴리즈해주고 있다.

//-----------------------------------------------------------------------------
// Name: Cleanup()
// Desc: 초기화된 객체들을 릴리즈
//-----------------------------------------------------------------------------
VOID Cleanup()
{
  if( g_pd3dDevice != NULL)
       g_pd3dDevice->Release();

  if( g_pD3D != NULL)
       g_pD3D->Release();
}

초기화 할때 g_pD3D, g_pd3dDevice의 순으로 초기화 해주었으므로
이것의 역순으로 릴리즈해줘야 문제가 없다.

SAFE_RELEASE(g_pd3dDevice);
SAFE_RELEASE(g_pD3D);

SAFE_RELEASE 매크로가 정의 되어 있다면 위와같이 쓰는것이 편하고 좋다.
자주 유용하게 쓰는 매크로는 다음과 같다..

#define SAFE_DELETE(p)       { if(p) { delete (p);     (p)=NULL; } }
#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p);   (p)=NULL; } }
#define SAFE_RELEASE(p)      { if(p) { (p)->Release(); (p)=NULL; } }

장황하게 길게 설명했다.. 쓸모없는 이야기들도 많고.
잘못된 부분도 있을것이다.
크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by hans

2006/05/16 02:48 2006/05/16 02:48
, , , ,
Response
A trackback , No Comment
RSS :
http://hansnara.pe.kr/blog/rss/response/75

Trackback URL : http://hansnara.pe.kr/blog/trackback/75

Trackbacks List

  1. Direct3D Tutorial List

    Tracked from hansObsession 2006/05/16 03:35 Delete

    Direct3D Tutorial List Basic > D3D Tutorial 1: Create a Device ▶ http://blog.naver.com/narsislegend/20023882433 ▶ http://hansnara.pe.kr/lifelog/hans/75 D3D Tutorial 2: Rendering Vertices D3D Tutorial 3: Using Matrices D3D Tutorial 4: Creating and Usin

Leave a comment
[Login][OpenID?]
« Previous : 1 : ... 27 : 28 : 29 : 30 : 31 : 32 : 33 : 34 : 35 : ... 59 : Next »