[MFC,OGRE] MFC에서 OGRE 사용하기
원문 1 : http://blog.naver.com/xtar/70000770469
원문 2 : http://blog.naver.com/xtar/70000770626
원문 3 : http://blog.naver.com/xtar/70000770830
10년전 기준 코드이므로, 현재 버전에서는 다를 것 같다.
Ogre는 잘 사용하지 않지만, 기록 차원에서 정리한다.
Ogre를 초기화 하는데 필요한 과정을 정리해 보면 다음과 같다.
1. Ogre::Root 객체 생성 및 초기화
1.1. Ogre::Root 객체 생성
1.2. Resource 경로 설정
1.3. Ogre::RenderSystem 선택 (DirectX or OpenGL 같은 것들)
1.4. Ogre::Root 초기화: initialise() 호출
2. Ogre::RenderWindow 생성 - 윈도우 핸들(m_hWnd)과 영역 정보(CRect) 필요
3. Ogre::SceneManager 선택
4. 선택된 SceneManger로부터 Ogre::Camera 객체 생성, 또는 만들어져 있는 객체 얻어오기.
5. Ogre::Viewport 생성
6. Resource 읽어 들이기
7. 필요한 장면 설정
8. 윈도우 active로 설정하고 update 하기
이 과정을 MFC 초기화 과정과 연결시켜보면,
1번 과정은 어플리케이션당 한번만 실행되면 되기 때문에,
보통은 CWinApp::InitInstance()에서 해 주는 것이 좋다.
하지만 Main View가 한개라면 CView::OnInitialUpdate() / CDialog::OnInitDialog()에서 해 주어도 상관없을 것 같다.
2번~8번 과정은 뷰(CView 또는 CDialog)가 생성될 때마다 실행되어야 하는 부분이다.
또한, 뷰 객체마다 해 주어야 하는 작업이 있다.
따라서, 이곳에서는 크게 3단계로 나누어서 소스와 함께 설명할 것이다.
1단계: Ogre::Root 객체 초기화하기
2단계: RenderWindow, SceneManager, Camera, Viewport 설정하기
3단계: MFC에서 설정 방법
1. Ogre::Root 객체 초기화하기
Ogre::Root *m_OgreRoot;
void setupResources(String resourceFileName)
{
//
// Setup paths to all resources
//
Ogre::ConfigFile cf;
cf.load(resourceFileName);
// Go through all sections & settings in the file
Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();
Ogre::String secName, typeName, archName;
while (seci.hasMoreElements())
{
secName = seci.peekNextKey();
Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();
Ogre::ConfigFile::SettingsMultiMap::iterator i;
for (i = settings->begin(); i != settings->end(); ++i)
{
typeName = i->first;
archName = i->second;
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
archName, typeName, secName);
}
}
}
//
// 윈도우에서 사용가능한 RenderSystem 이름
// "Direct3D9 Rendering SubSystem"
// "OpenGL Rendering Subsystem"
//
void selectRenderSystem(Ogre::String searchString)
{
Ogre::RenderSystemList::iterator pRend = m_OgreRoot->getAvailableRenderers()->begin();
while (pRend != m_OgreRoot->getAvailableRenderers()->end())
{
Ogre::String rName = (*pRend)->getName();
if (rName == searchString) // match found
return (*pRend);
++pRend;
}
}
void setupRenderSystem(const Ogre::RenderSystem *pRenderSystem)
{
ASSERT(pRenderSystem);
//
// Some standard rendering system configurations
//
pRenderSystem->setConfigOption("Full Screen", "No");
pRenderSystem->setConfigOption("VSync", "Yes");
// Set the rendering system.
m_OgreRoot->setRenderSystem(pRenderSystem);
}
BOOL InitOgreRoot(void)
{
// Create Ogre::Root object
m_OgreRoot = new Ogre::Root("plugins.cfg", "Ogre.cfg", "Ogre.log");
setupResources("resources.cfg");
Ogre::RenderSystem *rsys = selectRenderSystem("Direct3D9 Rendering SubSystem");
if (rsys == NULL)
{
// if the proper one is not found
return FALSE;
}
setupRenderSystem(rsys);
//
// Initialize the system, but don't create a render window.
// 인자가 false인 점이 중요.
//
m_OgreRoot->initialise(false);
return TRUE;
}
2단계: RenderWindow, SceneManager, Camera, Viewport 설정하기
이 부분이 수행되는 시점이 CView::OnInitialUpdate() 또는 CDialog::OnInitDialog() 가 적당할 것 같은데 실제로 OnInit... 함수에서 이 과정을 수행하면, Direct X Device Lost 같은 에러가 생기고 후에 복구는 되지만 텍스처가 정상적으로 보이지 않는 문제점이 있다.
그래서, OnDraw() 또는 OnPaint()에서 최초 1회에만 해 주도록 하면 일단 문제는 없다.
맨 아래 SetupOgre() 함수가 메인에 해당하는 함수임.
Ogre::Root *m_OgreRoot;
Ogre::SceneManager *m_SceneManager;
Ogre::Camera *m_Camera;
Ogre::RenderWindow *m_Window;
void chooseSceneManager(void)
{
m_SceneManager = m_OgreRoot->getSceneManager(Ogre::ST_GENERIC);
}
void createCamera(const String &cameraName)
{
// Create the camera
m_Camera = m_SceneManager->createCamera(cameraName);
m_Camera->setNearClipDistance(5);
}
void createViewport(const CRect &rect)
{
// Create one viewport, entire window
Ogre::Viewport* vp = m_Window->addViewport(m_Camera);
vp->setBackgroundColour(Ogre::ColourValue(0,0,0));
ASSERT(rect.Height());
// Alter the camera aspect ratio to match the viewport
m_Camera->setAspectRatio(Ogre::Real(rect.Width()) / Ogre::Real(rect.Height()));
}
void createScene(void)
{
// Set ambient light
m_SceneManager->setAmbientLight(ColourValue(0.5, 0.5, 0.5));
Ogre::Entity *robotEntity = m_SceneManager->createEntity("robot", "robot.mesh");
// Add entity to the scene node
// Place and rotate to face the Z direction
Vector3 robotLoc(0, 0, 0);
Quaternion robotRot(Degree(-90), Vector3(0, 1, 0));
m_SceneManager->getRootSceneNode()->createChildSceneNode(robotLoc, robotRot)->attachObject(robotEntity);
// Give it a little ambience with lights
Light* l;
l = m_SceneManager->createLight("BlueLight");
l->setPosition(-200,-80,-100);
l->setDiffuseColour(0.5, 0.5, 1.0);
l = m_SceneManager->createLight("GreenLight");
l->setPosition(0,0,-100);
l->setDiffuseColour(0.5, 1.0, 0.5);
// Position the camera
m_Camera->setPosition(0, 50, 200);
m_Camera->lookAt(0, 50, 0);
}
void SetupOgre(String title, String cameraName, long WindowHandle, const CRect &rect)
{
ASSERT(m_OgreRoot);
// 윈도우 핸들을 OGRE로 넘겨주는 방법임.
NameValuePairList parms;
parms["externalWindowHandle"] = StringConverter::toString(WindowHandle);
//
// Create a render window
//
m_Window = m_OgreRoot->createRenderWindow(title, rect.Width(), rect.Height(), false, &parms);
//
// Choose a scene manager
//
chooseSceneManager();
//
// Create a camera
//
createCamera(cameraName);
//
// Create a viewport
//
createViewport(rect);
// Set default mipmap level (NB some APIs ignore this)
Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5);
//
// Load resources
//
Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
//
// Create the scene
//
createScene();
// Make window active and post an update
m_Window->setActive(true);
m_Window->update();
}
3단계: MFC에서 설정 방법
아래 링크는 참조할 만한 Ogre Forum의 게시물과 누군가가 만들어 놓은 참고용 프로젝트이다. OgreMFC.zip은 실제로 컴파일 되고 실행가능하다.
참조게시: http://www.ogre3d.org/phpBB2/viewtopic.php?t=14211&highlight=mfc
참조소스: http://www.cse.msu.edu/~cbowen/ogre/OgreMFC.zip (툴바 부분이 제대로 동작하지 않는 문제가 있긴 합니다)
이제 여기서는 실제로 MFC 내부에서 앞서 작성한 함수들을 어디에서 사용할 것인가와 추가로 설정해 주어야 하는 내용에 대해서 정리해 보겠다. MFC 프로젝트는 기본적으로 SDI를 가정할 것이지만, 내용을 잘 이해한다면 MDI나 Dialog를 베이스로 하는 경우에 응용하는 것은 어렵지 않을 것이라고 생각된다.
3.1. Ogre::Root 객체를 생성하고 초기화 하는 루틴인 InitOgreRoot()는 CWinApp::InitInstance()에서 사용하는 것이 좋을 것 같다. 실제로 OgreMFC 예제에서도 InitInstance()에서 Root 객체를 생성하고 초기화 하였다. 여기서 한가지 주의할 점이 있다. 아는 사람은 알겠지만, InitInstance() 내부에서 ProcessShellCommand() 함수가 불리면 내부에서 CView::OnInitalUpdate()가 호출된다. 따라서 InitInstance() 내부에서 InitOgreRoot()를 사용하려면 ProcessShellCommand() 이전에 호출해 주어야만 혹시 나중에 CView::OnInitialUpdate()에서 다른 초기화를 하는 경우에 문제가 생기지 않게 된다.
BOOL CTestApp::InitInstance()
{
CWinApp::InitInstance();
// 이쯤에서 InitOgreRoot() 를 호출해 준다. 대충 이런 식으로...
if (FALSE == InitOgreRoot())
return FALSE;
// 표준 초기화
// 이들 기능을 사용하지 않고 최종 실행 파일의 크기를 줄이려면
// 아래에서 필요 없는 특정 초기화 루틴을 제거해야 합니다.
// 해당 설정이 저장된 레지스트리 키를 변경하십시오.
// TODO: 이 문자열을 회사 또는 조직의 이름과 같은
// 적절한 내용으로 수정해야 합니다.
SetRegistryKey(_T("로컬 응용 프로그램 마법사에서 생성된 응용 프로그램"));
LoadStdProfileSettings(4); // MRU를 포함하여 표준 INI 파일 옵션을 로드합니다.
// 응용 프로그램의 문서 템플릿을 등록합니다. 문서 템플릿은
// 문서, 프레임 창 및 뷰 사이의 연결 역할을 합니다.
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CCloudEditorDoc),
RUNTIME_CLASS(CMainFrame), // 주 SDI 프레임 창입니다.
RUNTIME_CLASS(CCloudEditorView));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);
// 표준 셸 명령, DDE, 파일 열기에 대한 명령줄을 구문 분석합니다.
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// 명령줄에 지정된 명령을 디스패치합니다. 응용 프로그램이 /RegServer, /Register, /Unregserver 또는 /Unregister로 시작된 경우 FALSE를 반환합니다.
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// To create the main window, this code creates a new frame window
// object and then sets it as the application's main window object
// 창 하나만 초기화되었으므로 이를 표시하고 업데이트합니다.
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
// 접미사가 있을 경우에만 DragAcceptFiles를 호출합니다.
// SDI 응용 프로그램에서는 ProcessShellCommand 후에 이러한 호출이 발생해야 합니다.
return TRUE;
}
3.2. RenderWindow, SceneManager, Camera, Viewport 설정하기
이 과정은 앞서 얘기한 대로 새로운 창을 만들 때마다 해 주어야 하는 부분이기 때문에, CView::OnInitalUpdate() 또는 CDialog::OnInitDialog()에서 해 주면 좋을 것 같은데, 실제로 해 보면 문제가 있어서 OnDraw() 또는 OnPaint()에서 최초 1회 실행 때 수행해 준다.
대충 어설픈 코드로 표현하면
void CTestView::OnDraw(CDC *pDC)
{
if (최초 1회)
{
CRect rect;
GetClientRect(&rect);
// 여기서 세번째 인자가 바로 윈도우 핸들이다. 네번째 인자 rect는 클라이언트 영역
m_pOgreWindow->SetupOgre("MFC Window", "Camera01", (long)m_hWnd, rect);
}
m_OgreRoot->renderOneFrame()
}
3.3. 기타 설정
* WM_ERASEBKGND 메세지 핸들러 만들기
원래 OnDraw()나 OnPaint()에서는 배경을 한번 지우도록 되어 있는데, 배경을 지우게 되면 화면이 깜빡거리게 된다. 따라서, 위의 메세지 핸들러를 만들어서 배경을 지우지 않도록 해야 한다. 아래와 같이 원래 있는 코드는 삭제하고 그냥 TRUE를 리턴하면 된다.
BOOL CTestView::OnEraseBkgnd(CDC* pDC)
{
// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
return TRUE;
//return CView::OnEraseBkgnd(pDC);
}
* WM_SIZE 메세지 핸들러 만들기
void CCloudEditorView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
// TODO: 여기에 메시지 처리기 코드를 추가합니다.
m_Window->windowMovedOrResized();
// Adjust camera's aspect ratio, too
CRect rect;
GetClientRect(&rect);
if (rect.Height() != 0 && m_Camera != 0)
m_Camera->setAspectRatio((Ogre::Real)m_Window->getWidth() / (Ogre::Real)m_Window->getHeight());
m_Camera->yaw(Radian(0));
}