반응형

http://www.viper.pe.kr/cgi-bin/moin.cgi/MFC_%EB%A5%BC_%EC%9D%B4%EC%9A%A9%ED%95%9C_Direct3D_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D#head-67b3c40633d83bbb112a1d972088a12b35a7fd35

 

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 헤더 파일에 다음과 같이 추가하면 된다.

  • function isnumbered(obj) { return obj.childNodes.length && obj.firstChild.childNodes.length && obj.firstChild.firstChild.className == 'LineNumber'; } function nformat(num,chrs,add) { var nlen = Math.max(0,chrs-(''+num).length), res = ''; while (nlen>0) { res += ' '; nlen-- } return res+num+add; } function addnumber(did, nstart, nstep) { var c = document.getElementById(did), l = c.firstChild, n = 1; if (!isnumbered(c)) if (typeof nstart == 'undefined') nstart = 1; if (typeof nstep == 'undefined') nstep = 1; n = nstart; while (l != null) { if (l.tagName == 'SPAN') { var s = document.createElement('SPAN'); s.className = 'LineNumber' s.appendChild(document.createTextNode(nformat(n,4,' '))); n += nstep; if (l.childNodes.length) l.insertBefore(s, l.firstChild) else l.appendChild(s) } l = l.nextSibling; } return false; } function remnumber(did) { var c = document.getElementById(did), l = c.firstChild; if (isnumbered(c)) while (l != null) { if (l.tagName == 'SPAN' && l.firstChild.className == 'LineNumber') l.removeChild(l.firstChild); l = l.nextSibling; } return false; } function togglenumber(did, nstart, nstep) { var c = document.getElementById(did); if (isnumbered(c)) { remnumber(did); } else { addnumber(did,nstart,nstep); } return false; } document.write('줄 번호 보이기/숨기기<\/a>'); 줄 번호 보이기/숨기기
    #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]

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

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

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

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

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

  • document.write('줄 번호 보이기/숨기기<\/a>'); 줄 번호 보이기/숨기기
    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 로 설정한다.

    document.write('줄 번호 보이기/숨기기<\/a>'); 줄 번호 보이기/숨기기
       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 메세지 핸들러.
    document.write('줄 번호 보이기/숨기기<\/a>'); 줄 번호 보이기/숨기기
       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() 함수 추가

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

    document.write('줄 번호 보이기/숨기기<\/a>'); 줄 번호 보이기/숨기기
       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 객체를 여기서 제거한다.

    document.write('줄 번호 보이기/숨기기<\/a>'); 줄 번호 보이기/숨기기
       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 가상 함수)

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

    document.write('줄 번호 보이기/숨기기<\/a>'); 줄 번호 보이기/숨기기
       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 가상 함수)
    • 실제 렌더링 작업을 여기에 추가한다.

    document.write('줄 번호 보이기/숨기기<\/a>'); 줄 번호 보이기/숨기기
       1 void CChildView::Render()
       2 {
       3         // 격자 렌더링
       4         m_grid.Render();
       5 
       6         //--------------------------------------------------------------------------
       7         // TODO: 3D 오브젝트를 렌더링한다.
       8         //
       9 }
    
  • WM_SIZE 메세지 핸들러 추가
    • 창의 크기가 바뀔 때 Direct3D 장치를 재설정한다.
    • 이 과정이 생략되면 창의 크기가 바뀔 시 3D 화면이 찌그러지거나 도트가 뭉게지는 현상이 발생한다.
    document.write('줄 번호 보이기/숨기기<\/a>'); 줄 번호 보이기/숨기기
       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 초기화 과정을 수행한다.
    document.write('줄 번호 보이기/숨기기<\/a>'); 줄 번호 보이기/숨기기
       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 화면의 크기를 설정한다.
    document.write('줄 번호 보이기/숨기기<\/a>'); 줄 번호 보이기/숨기기
       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 가상 함수)

    • 프로그램 종료 과정의 시작점으로 마무리 과정을 수행한다.
    document.write('줄 번호 보이기/숨기기<\/a>'); 줄 번호 보이기/숨기기
       1 BOOL CMainFrame::DestroyWindow()
       2 {
       3         // 마무리 작업
       4         m_wndView.FinalCleanup();
       5 
       6         return CFrameWnd::DestroyWindow();
       7 }
    
  • WM_ACTIVATE 메세지 핸들러 추가
    • 창의 포커스 이동 유무에 따라 3D 렌더링 속도를 조절한다. 즉, CPU 점유율을 조절한다.
    document.write('줄 번호 보이기/숨기기<\/a>'); 줄 번호 보이기/숨기기
       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 초기화 과정만 추가한다.

    document.write('줄 번호 보이기/숨기기<\/a>'); 줄 번호 보이기/숨기기
       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() 함수를 호출한다.

    document.write('줄 번호 보이기/숨기기<\/a>'); 줄 번호 보이기/숨기기
       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

 

반응형

+ Recent posts