본문 바로가기

프로그래밍/MFC

MFC 를 이용한 Direct3D 프로그래밍

1. 소개 [Bottom] [Top]

Direct3D 는 게임에서 주로 많이 사용되고 있으며 MFC 는 응용 프로그램 개발에 많이 사용되고 있다. 이렇게 사용 목적이 전혀 다른 두 가지를 게임 개발에 이용하는 것은 부적합하다. 하지만 게임 자체를 개발하는 것에는 부적합할지 모르나 개발에 필요한 보조 프로그램을 개발하는데는 가장 적합하지 않을까?

아직도 많은 사람들은 MFC 는 덩치가 크고 느리다고 한다. 그러나 그것은 펜티엄 100MHz 를 사용하던 과거의 이야기일뿐, 지금과 같이 거의 모든 CPU 가 GHz 를 넘어가는 경우엔 전혀 다르다. 메모리나 저장매체 또한 GByte 단위를 넘어가고 있는 현시점에서 전혀 영향을 줄 수 없는 것들이다. 그리고 WINAPI 보다 편리한 인터페이스와 프레임워크를 제공하고 있으므로 프로그래밍 속도 또한 빠르다.

MFC 를 이용한 Direct3D 프로그래밍은 게임 프로그램 보다는 보조 프로그램을 개발하는 것이 목적이다. 그리고 Direct3D 프로그래밍을 처음 시작할 때도 유용하다. 우선 Direct3D 를 구동하기 위한 프레임워크 개발이 필요 없으며 여러가지 데이터를 입력하거나 테스트할 경우에는 손쉽게 인터페이스를 만들고 수정할 수 있다. MFC 를 이용한 Direct3D 프로그래밍를 시작하게 된 이유도 편리한 인터페이스 프로그래밍 때문이다.

참고로 Microsoft Visual Studio 2005 를 기준으로 정리한다.

2. 참고도서 [Bottom] [Top]

  • IT EXPERT : 3D 게임 프로그래밍
    • 김용준 저, 한빛 미디어
    • ISBN: 8979142536

    • Direct3D 의 Framework 에 대하여 간략히 소개하고 있음 ( p.142 ~ 158 )

3. 참고링크 [Bottom] [Top]

4. MFC 프로젝트 만들기 [Bottom] [Top]

MFC 프레임워크는 다양한 형태를 제공한다. 기본적으로는 다중 문서 (MDI) 와 문서/뷰 (Doc/View) 구조가 선택되어 있지만, Direct3D 프로그래밍에서는 단일 문서 (SDI) 구조를 사용할 것이다. 그리고 문서/뷰 (Doc/View) 구조는 불필요하기 때문에 단일 뷰 구조만 사용한다. 문서/뷰 구조를 사용하고 싶다면 참고링크 에 소개된 글들을 참고한다.

4.1. 새 프로젝트 만들기 [Bottom] [Top]

  1. Visual Studio 의 새 프로젝트 마법사를 열고, 아래 그림과 같이 MFC 응용 프로그램 을 선택한 후 프로젝트 이름을 입력하고 확인을 클릭한다.

    • 예) 프로젝트 이름: MFC4Direct3D

  2. MFC 응용 프로그램 마법사 가 열리면 다음과 같이 선택한다.

    • 단일 문서 를 선택한다.

    • 문서/뷰 아키텍처 지원 은 체크하지 않는다(단일 뷰 구조).

    • 참고> 유니코드를 사용하지 않는 경우, 유니코드 라이브러리 사용 을 체크하지 않는다.

  3. MFC 응용 프로그램 마법사 의 나머지 과정은 기본값을 사용한다.

  4. 새 프로젝트 만들기 끝나면 다음과 같이 구조가 될 것이다.

4.2. 라이브러리 링크 시키기 [Bottom] [Top]

새로운 프로젝트가 만들어졌다면, Direct3D 를 사용할 수 있도록 Direct3D 라이브러리를 추가한다. 라이브러리 추가는 프로젝트 속성에서 추가하거나 컴파일 지시어를 사용한다.

  • D3D9.lib

  • D3DX9.lib
  • Winmm.lib

4.2.1. 방법1: 프로젝트 속성에서 라이브러리 추가 [Bottom] [Top]

4.2.2. 방법2: 컴파일 지시어를 사용하여 라이브러리 추가 [Bottom] [Top]

MFC 응용 프로그램 프로젝트는 미리 컴파일된 헤더 (Precompiled Header) 를 사용하므로 StdAfx.h 헤더 파일에 다음과 같이 추가하면 된다.

  • 줄 번호 보이기/숨기기
    #pragma comment( lib, "D3D9" )
    #pragma comment( lib, "D3DX9" )
    #pragma comment( lib, "Winmm" )
    

5. 프로그램 구조 이해하기 [Bottom] [Top]

기본적인 프로젝트 설정이 끝났다면 구현에 앞서 다음의 그림을 보면서 앞으로 만들게 프로그램에 대하여 대략적인 구조를 파악해 보자.

여러개의 추가적인 클래스와 더 많은 멤버 함수들이 있지만 여기서는 프로그램 초기화, 렌더링, 마무리 작업의 주축을 이루는 부분에 대해서만 정리하였다.

아래에서 설명하겠지만 CChildView 클래스는 C3DBase 클래스를 상속 받은 클래스로 UpdateFrame()Render() 함수는 C3DBase 클래스에 정의된 가상 함수 (Virtual Function) 를 재정의 (Overriding) 한 것이다.

그럼, 프로그램 초기화, 렌더링, 마무리 작업에 대하여 시퀸스 다이어그램을 보면서 처리 과정을 이해해 보자.

5.1. 초기화 과정 [Bottom] [Top]

일반적인 MFC 응용 프로그램 초기화 과정에 3D 장치 초기화/설정 및 3D 객체 생성 과정을 추가한 것으로 CChildView::InitStartup() 함수에서 필요한 초기화 과정을 추가하면 된다.

예를 들어 카메라 및 조명을 초기화 하거나, 격자 (Grid) 객체를 생성한다.

5.2. 렌더링 과정 [Bottom] [Top]

  • 기본 렌더링 과정
    • 루프를 통한 렌더링 과정 수행

  • 화면 갱신에 의한 렌더링 과정
    • 창의 전환이나 크기 조절, 이동 시 발생하는 화면 갱신에 대하여 렌더링 과정 수행

5.3. 마무리 과정 [Bottom] [Top]

프로그램 종료 시 3D 장치 해제와 3D 객체 제거 과정을 수행한다. CChildView::FinalCleanup() 함수에서 필요한 마무리 과정을 추가하면 된다.

예를 들어 CChildView::InitStartup() 함수에서 생성된 객체를 제거한다.

6. Direct3D 에 맞게 MFC 프로그래밍하기 [Bottom] [Top]

먼저, 앞서 그림에서 CMFC4Direct3DApp, CMainFrame, CChildView 그리고 C3DBase 로 4개의 주요 클래스를 볼 수 있다. 이 중에서 CMFC4Direct3DApp 클래스는 프로젝트에 따라 다른 이름으로 생성될 수도 있으며, 각 클래스는 개조하는 과정에서 자세히 설명할 것이다.

6.1. C3DBase 클래스 만들기 [Bottom] [Top]

C3DBase 클래스는 CChildView 클래스가 상속하게 될 Base 클래스로 주요 기능은 다음과 같다.

  • Direct3D 인터페이스와 장치를 얻어서 Direct3D 를 사용할 수 있도록 초기화 한다.
  • Direct3D 인터페이스와 장치를 해제하여 Direct3D 를 종료한다.
  • 3D 렌더링 과정을 수행한다.

6.1.1. 멤버 함수 [Bottom] [Top]

  • 줄 번호 보이기/숨기기
    HRESULT CreateDirect3D( HWND hWnd, UINT nPresentationInterval );
    void    ReleaseDirect3D();
    void    ResetDirect3D( int nWidth, int nHeight );
    void    RenderDirect3D();
    
    virtual void    UpdateFrame() = 0;
    virtual void    Render() = 0;
    
    void    Pause( BOOL bPause=TRUE );
    
  • CreateDirect3D() 함수
    • Direct3D 장치를 생성한다.

  • ReleaseDirect3D() 함수
    • Direct3D 장치를 해제한다.

  • ResetDirect3D() 함수
    • 화면 (View) 의 크기가 바뀔때 Direct3D 장치를 재설정한다.

  • RenderDirect3D() 함수
    • 렌더링 과정을 수행한다. 내부에서 UpdateFrame()Render() 함수를 호출한다.

  • UpdateFrame() 함수 (가상 함수)

    • 상속 받은 클래스에서 재정의 해야하며 프레임 갱신 처리를 한다.

  • Render() 함수 (가상 함수)

    • 상속 받은 클래스에서 재정의 해야하며 렌더링 처리를 한다.

  • Pause() 함수
    • 다른 창으로 포커스가 이동 시 CPU 점유율을 떨어뜨리기 위해서 사용한다.

6.2. CChildView 클래스 개조하기 [Bottom] [Top]

CChildView 클래스는 C3DBase 클래스를 상속받은 클래스로 실제 3D 렌더링을 수행한다. 또한 Direct3D 의 초기화 및 해제가 실제로 처리되는 곳이다. 즉, 3D 렌더링의 핵심 클래스가 된다.

  • PreCreateWindow() 함수 수정

    • 아래의 코드 중 AfxRegisterWndClass() 함수의 3번째 매개변수 값을 NULL 로 설정한다.

    줄 번호 보이기/숨기기
       1 BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)
       2 {
       3         if (!CWnd::PreCreateWindow(cs))
       4                 return FALSE;
       5 
       6         cs.dwExStyle |= WS_EX_CLIENTEDGE;
       7         cs.style &= ~WS_BORDER;
       8         cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
       9                 ::LoadCursor(NULL, IDC_ARROW), NULL, NULL);
      10 
      11         return TRUE;
      12 }
    
    • 참고> AfxRegisterWndClass() 함수의 3번째 매개변수는 Background Brush 를 설정하는 곳으로 NULL 로 설정하게 되면 프레임마다 화면이 지워지지 않는다. 즉, 화면이 깜박거리는 현상 (Flicker 현상) 을 방지한다.

  • OnPaint() 함수 수정

    • 위에서 설명했듯이 창 변화에 따른 화면 갱신 과정으로 렌더링 과정을 수행한다.
    • WM_PAINT 메세지 핸들러.
    줄 번호 보이기/숨기기
       1 void CChildView::OnPaint()
       2 {
       3         CPaintDC dc(this);      // 삭제하면 안됨.
       4 
       5         if( IS_NOT_NULL( g_pD3DDevice ) )
       6         {
       7                 // 렌더링 수행
       8                 RenderDirect3D();
       9         }
      10 }
    
    • 참고> CPaintDC dc(this); 코드는 삭제하지 말것. 삭제하게 되면 화면 갱신 시 창의 프레임 갱신이 느려지게 된다.

  • InitStartup() 함수 추가

    • 초기화 과정에 필요한 작업을 여기에 추가한다.

    줄 번호 보이기/숨기기
       1 HRESULT CChildView::InitStartup()
       2 {
       3         //----------------------------------------------------------------------
       4         // TODO: 초기화 처리를 수행한다.
       5         //      * 배경색 설정
       6         //      * 카메라 설정
       7         //      * 조명 설정
       8         //      * 렌더링 객체 생성 등
       9         //
      10 
      11         // 배경색 설정
      12         SetBackColor( 0x40, 0x40, 0x40 );
      13 
      14         // 카메라 설정
      15         m_pCamera = new CCamera();
      16         InitCamera();
      17 
      18         // 조명 설정
      19         InitLight();
      20         LightEnable( FALSE );           // 조명 비활성화
      21 
      22         // 격자 생성
      23         m_grid.Create( 20, 4, 20 );
      24 
      25         return S_OK;
      26 }
    
  • FinalCleanup() 추가

    • 초기화 과정이나 실행 과정에서 생성된 3D 객체를 여기서 제거한다.

    줄 번호 보이기/숨기기
       1 void CChildView::FinalCleanup()
       2 {
       3         //----------------------------------------------------------------------
       4         // TODO: 마무리 처리를 수행한다.
       5         //      * 초기화 또는 수행 중 생성된 3D 객체 해제 등
       6 
       7         // 격자 제거
       8         m_grid.Release();
       9 
      10         // 카메라 제거
      11         SAFE_DELETE( m_pCamera );
      12 
      13         // Direct 3D 해제
      14         ReleaseDirect3D();
      15 }
    
  • UpdateFrame() 함수 재정의 (C3DBase 가상 함수)

    • 실제 프레임 갱신 작업을 여기에 추가한다.

    줄 번호 보이기/숨기기
       1 void CChildView::UpdateFrame()
       2 {
       3         if( IS_NOT_NULL( m_pCamera ) )
       4         {
       5                 // 카메라 설정
       6                 m_pCamera->SetCamera();
       7         }
       8 
       9         //--------------------------------------------------------------------------
      10         // TODO: 프레임 갱신 처리를 수행한다.
      11         //
      12 }
    
  • Render() 함수 재정의 (C3DBase 가상 함수)
    • 실제 렌더링 작업을 여기에 추가한다.

    줄 번호 보이기/숨기기
       1 void CChildView::Render()
       2 {
       3         // 격자 렌더링
       4         m_grid.Render();
       5 
       6         //--------------------------------------------------------------------------
       7         // TODO: 3D 오브젝트를 렌더링한다.
       8         //
       9 }
    
  • WM_SIZE 메세지 핸들러 추가
    • 창의 크기가 바뀔 때 Direct3D 장치를 재설정한다.
    • 이 과정이 생략되면 창의 크기가 바뀔 시 3D 화면이 찌그러지거나 도트가 뭉게지는 현상이 발생한다.
    줄 번호 보이기/숨기기
       1 void CChildView::OnSize(UINT nType, int cx, int cy)
       2 {
       3         __super::OnSize(nType, cx, cy);
       4 
       5         if( IS_NOT_NULL( g_pD3DDevice ) )
       6         {
       7                 // Direct3D 재설정
       8                 ResetDirect3D( cx, cy );
       9 
      10                 // 조명 설정
      11                 InitLight();
      12                 LightEnable( FALSE );           // 조명 비활성화
      13         }
      14 }
    

6.3. CMainFrame 클래스 개조하기 [Bottom] [Top]

CMainFrame 클래스는 MFC 프레임워크에서 창의 기본 골격을 이루는 클래스로 창을 열거나 닫기, 메뉴, 툴바, 상태바 등을 관리하며 주로 사용자와의 인터페이스 처리를 담당하게 된다. 따라서 3D 렌더링에는 별로 중요하지 않다.

  • InitDirect3D() 함수 추가
    • 프로그램 시작 시 3D 초기화 과정을 수행한다.
    줄 번호 보이기/숨기기
       1 HRESULT CMainFrame::InitDirect3D()
       2 {
       3         // View 영역 크기 설정
       4         SetSize( 800, 600 );
       5 
       6         // Direct 3D 초기화
       7         m_wndView.CreateDirect3D( m_wndView.m_hWnd, D3DPRESENT_INTERVAL_IMMEDIATE );
       8 
       9         // 사용자 초기화 수행
      10         m_wndView.InitStartup();
      11 
      12         return S_OK;
      13 }
    
  • SetSize() 함수 추가

    • 프로그램 시작 시 3D 화면의 크기를 설정하는 곳으로 3D 화면에 맞게 창의 프레임 크기를 재계산하고 창 크기를 변경한다.
    • 매개변수는 실제 3D 화면의 크기를 설정한다.
    줄 번호 보이기/숨기기
       1 void CMainFrame::SetSize( int nWidth, int nHeight )
       2 {
       3         RECT client, frame;
       4 
       5         GetWindowRect( &frame );
       6         m_wndView.GetClientRect( &client );
       7 
       8         // View 영역 크기에 맞게 창크기 변경
       9         frame.right  = nWidth  + frame.right  - client.right;
      10         frame.bottom = nHeight + frame.bottom - client.bottom;
      11 
      12         MoveWindow( &frame );
      13 }
    
  • DestroyWindow() 함수 재정의 (CWnd 가상 함수)

    • 프로그램 종료 과정의 시작점으로 마무리 과정을 수행한다.
    줄 번호 보이기/숨기기
       1 BOOL CMainFrame::DestroyWindow()
       2 {
       3         // 마무리 작업
       4         m_wndView.FinalCleanup();
       5 
       6         return CFrameWnd::DestroyWindow();
       7 }
    
  • WM_ACTIVATE 메세지 핸들러 추가
    • 창의 포커스 이동 유무에 따라 3D 렌더링 속도를 조절한다. 즉, CPU 점유율을 조절한다.
    줄 번호 보이기/숨기기
       1 void CMainFrame::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
       2 {
       3         CFrameWnd::OnActivate(nState, pWndOther, bMinimized);
       4 
       5         m_wndView.Pause( nState == WA_INACTIVE );
       6 }
    

6.4. CMFC4Direct3DApp 클래스 개조하기 [Bottom] [Top]

CMFC4Direct3DApp 클래스는 가장 먼저 실행되므로 실행 순서상으로 본다면 최상위에 위치한다고 볼 수 있다. 주요 기능은 다른 객체들을 생성하고 실행시키는 역활을 한다. 그리고 가장 중요한 기능은 CChildView 클래스에게 3D 렌더링하도록 RenderDirect3D() 함수를 호출한다.

  • InitInstance() 함수 수정

    • 프로그램 초기화 과정으로 MFC 응용 프로그램 마법사 에서 생성된 코드에 3D 초기화 과정만 추가한다.

    줄 번호 보이기/숨기기
       1 BOOL CMFC4Direct3DApp::InitInstance()
       2 {
       3         ...     // 생략
       4 
       5         // 창 하나만 초기화되었으므로 이를 표시하고 업데이트합니다.
       6         pFrame->ShowWindow(SW_SHOW);
       7         pFrame->UpdateWindow();
       8         // 접미사가 있을 경우에만 DragAcceptFiles를 호출합니다.
       9         // SDI 응용 프로그램에서는 ProcessShellCommand 후에 이러한 호출이 발생해야 합니다.
      10 
      11         // 초기화 수행
      12         pFrame->InitDirect3D();
      13 
      14         return TRUE;
      15 }
    
  • OnIdle() 함수 재정의 (CWinApp 가상 함수)

    • 반복 루프로 CChildView::RenderDirect3D() 함수를 호출한다.

    줄 번호 보이기/숨기기
       1 BOOL CMFC4Direct3DApp::OnIdle(LONG lCount)
       2 {
       3         CWinApp::OnIdle(lCount);
       4 
       5         // 렌더링 수행
       6         static_cast< CMainFrame * >( m_pMainWnd )->m_wndView.RenderDirect3D();
       7 
       8         return TRUE;
       9 }
    

7. 다운로드 (준비중) [Bottom] [Top]

  • 다운로드: /!\ 코드는 준비중 - <!> 준비되면 업로드 예정


'프로그래밍 > MFC' 카테고리의 다른 글

MFC 메모리 릭 체크시 유용한 방법 2개  (0) 2011.07.20
MFC 다이얼로그 스크롤 사용 소스  (0) 2010.10.29
[MFC] CWinAppEx::CleanState()  (0) 2010.08.25
CView  (0) 2010.05.24
차일드 사용  (0) 2010.05.20