Ogre3D 삽질란/Basic Tutorial 4

기초 튜토리얼 4-1

n_Sys 2008. 11. 20. 18:23

기초 튜토리얼 4 (번역 : n_Sys)

입문자 튜토리얼 4: Frame Listeners 와 Unbuffered Input

튜토리얼 진행중 문제가 발생한다면 Help 포럼 문의하세요.

 

Contents

                               1 미리 알아두어야

                               2 소개

                               3 시작하기

                               4 FrameListeners

                                       4.1 소개

                                       4.2 FrameListener 등록하기  

                               5 장면(Scene) 설정

                                       5.1

                                       5.2 소스코드

                               6 TutorialFrameListener

                                       6.1 사용되는 변수들

                                       6.2 생성자

                                       6.3 frameStarted 함수

 

미리 알아두어야

튜토리얼은 독자가 C++ 프로그래밍이 가능하고 오우거 어플리케이션 설정 컴파일이 가능하다는 가정하에 진행됩니다. 튜토리얼은 이전 튜토리얼을 기초로 작성되었으며 독자는 이전 튜토리얼들을 거쳐왔다고 가정합니다.

 

소개

튜토리얼에서는 오우거에서 가장 쓸만한 한가지인 FrameListener 소개합니다. 끝날 시점에서 어떻게 프레임마다 업데이트 동작을 하고 버퍼를 쓰지 않는 입력시스템을 사용하는 방법을 이해하게 것입니다.

튜토리얼을 위한 코드는 여기(http://www.ogre3d.org/wiki/index.php/BasicTutorial4Source) 구할 있습니다. 튜토리얼을 천천히 진행함에 따라 프로젝트에 코드를 직접 입력하고 중간중간 생성되는 결과를 확인하세요.

 

시작하기

지난번 튜토리얼처럼 미리 작성된 코드를 기초로 시작할 입니다. 프로젝트를 생성하고 아래의 코드를 입력하세요 :

#include "ExampleApplication.h"

 

class TutorialFrameListener : public ExampleFrameListener

{

public:

    TutorialFrameListener(RenderWindow* win, Camera* cam, SceneManager *sceneMgr)

        : ExampleFrameListener(win, cam, false, false)

    {

    }


    // Overriding the default processUnbufferedKeyInput so the key updates we define
    // later on work as intended.
    bool processUnbufferedKeyInput(const FrameEvent& evt)
    {
         return true;
    }

    // Overriding the default processUnbufferedMouseInput so the Mouse updates we define
    // later on work as intended.
    bool processUnbufferedMouseInput(const FrameEvent& evt)
    {
         return true;
    }

    bool frameStarted(const FrameEvent &evt)

    {

        return ExampleFrameListener::frameStarted(evt);

    }

protected:

    bool mMouseDown;       // Whether or not the left mouse button was down last frame

    Real mToggle;          // The time left until next toggle

    Real mRotate;          // The rotate constant

    Real mMove;            // The movement constant

    SceneManager *mSceneMgr;   // The current SceneManager

    SceneNode *mCamNode;   // The SceneNode the camera is currently attached to

};

 

class TutorialApplication : public ExampleApplication

{

public:

    TutorialApplication()

    {

    }

 

    ~TutorialApplication()

    {

    }

protected:

    void createCamera(void)

    {

    }

 

    void createScene(void)

    {

    }

 

    void createFrameListener(void)

    {

    }

};

 

#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32

#define WIN32_LEAN_AND_MEAN

#include "windows.h"

 

INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)

#else

int main(int argc, char **argv)

#endif

{

    // Create application object

    TutorialApplication app;

 

    try {

        app.go();

    } catch(Exception& e) {

#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32

        MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);

#else

        fprintf(stderr, "An exception has occurred: %s\n",

                e.getFullDescription().c_str());

#endif

    }

 

    return 0;

}


만약 오우거SDK Windows 에서 사용한다면 "[OgreSDK_DIRECTORY]\samples\include" 디렉토리 (ExampleApplication.h 파일이 있는곳) include 가능하도록 프로젝트에 추가해 주세요. 만약 오우거엔진 소스를 직접 사용하신다면 [OgreSource_DIRECTORY]\Samples\Common\include" 추가해 주세요. 다음 진행을 위해서 컴파일이 에러없이 되도록 해두세요. 그러나 실행은 하지마세요! 나중에 코드에 추가할 계획입니다. 만약 컴파일에 문제가 생긴다면 Wiki 페이지에서 컴파일러별 설정 정보를 찾아보시고 문제가 지속된다면 Help 포럼에 문의하세요.

조작컨트롤 부분은 튜토리얼을 진행하면서 완성될 입니다.

 

FrameListeners

 

소개

이전 튜토리얼에서는 createScene 함수에 추가한 내용만 다뤘습니다. 오우거에서는 화면에 프레임이 그려지기 전과 그려진 후의 알림을 전달받을 있는 클래스를 등록할 있습니다. FrameListener 인터페이스는 2개의 함수를 정의합니다 :

bool frameStarted(const FrameEvent& evt)

bool frameEnded(const FrameEvent& evt)

오우거의 메인루프 (Root::startRendering) 다음과 같습니다 :

1.    Root 객체가 등록된 모든 FrameListener들의 frameStarted함수를 호출.

2.    Root 객체가 프레임을 출력.

3.    Root 객체가 등록된 모든 FrameListener 들의 frameEnded 함수를 호출.

반복루프는 어느 FrameListener frameStarted 또는 frameEnded함수가 false 리턴하기 전까지 계속 돕니다. 함수들에 있어서 리턴값의 의미는 간단하게 말하자면 "계속 그려라" 입니다. 이것들 하나가 거짓을 리턴하면 프로그램은 종료됩니다. FrameEvent 객체가 2개의 변수를 가지고 있지만 FrameListener에서는  timeSinceLastFrame값만 유용하게 사용됩니다. 값은 frameStarted 또는 frameEnded 함수가 마지막으로 실행되고나서 얼마나 시간이 흘렀는지를 추적합니다. 참고로 frameStarted 함수에서 FrameEvent::timeSinceLastFrame 마지막 frameStarted 호출된 시간으로부터 지금까지 지난 시간을 가집니다 (frameEnded 마지막으로 호출된 시간이 아닙니다).

오우거에서 다수의 FrameListener 있어서 중요한 속성중 하나는 어떤 순서로 호출될지의 여부는 오우거엔진에게 달려있다는 입니다. 어떤순서로 FrameListener 들이 호출될지 예측할 없습니다. 만약 다수의 FrameListener들의 호출순서를 정하고 싶다면 하나의 FrameListener 등록하고 관련된 모든 객체들의 호출들의 순서를 적절히 설정하면 됩니다.

메인루프는 세가지 단계만 실행된다는 것을 아셨을 겁니다. frameEnded frameStarted 사이에는 아무런 일이 일어나지 않으므로 코드를 바꿔쓰셔도 됩니다. 코드를 작성하는 위치를 정하는것은 전적으로 유저에게 달렸습니다. 하나의 frameStarted frameEnded 몰아서 작성하셔도 되고 두개에 나눠서 작성하셔도 됩니다.

 

FrameListener 등록하기

지금까지 작성한 코드는 컴파일이 되지만 ExampleApplication클래스의 createFrameListener createCamera함수를 재정의 했으므로 실행시킨다면 종료시킬 없게 됩니다. 계속 진행하기전에 여기서 문제점을 해결해야 합니다.

TutorialApplication::createCamera 함수를 찾아서 다음 코드를 추가하세요 :  

        // create camera, but leave at default position

        mCamera = mSceneMgr->createCamera("PlayerCam");

        mCamera->setNearClipDistance(5);

아직 이걸로는 아무것도 완성된 것이 아닙니다. 함수를 재정의한 이유는 기존의 createCamera함수에서 카메라를 움직이고 방향을 바꿔버리기 때문입니다. 그러한 기능은 튜토리얼에서는 필요없는 부분입니다.

Root 클래스가 프레임을 그릴때 FrameListener 같이 관여합니다. 첫째로 해야할 일은 TutorialFrameListener 인스턴스를 만들고 Root 객체에 등록합니다. TutorialApplication::createFrameListener 찾아서 다음 코드를 추가하세요.

        // Create the FrameListener

        mFrameListener = new TutorialFrameListener(mWindow, mCamera, mSceneMgr);

        mRoot->addFrameListener(mFrameListener);

ExampleApplication 클래스에는 mRoot mFrameListener 변수가 정의되어 있습니다. addFrameListener 함수는 FrameListener 추가하고 removeFrameListener 함수는 FrameListener 제거합니다( 말은 FrameListener 이상 새로운 메세지를 받지 않게 된다는 말입니다). 알아 두실점은 add|removeFrameListener 함수는 FrameListener 포인터만 받습니다 (FrameListener 제거할때 이름이 사용되지 않음을 의미합니다). 그러므로 각각의 FrameListener 포인터를 보관하고 계셔야 나중에 메모리에서 제거할 있습니다.

ExampleFrameListener(TutorialFrameListener 상속받은 클래스) 역시 showDebugOverlay (불리언 리턴) 함수를 제공합니다. 함수는 ExampleApplication 클래스에게 프레임율을 보여주는 작은 박스를 화면 왼쪽 구석에 표시할지 여부를 설정합니다. 다음과 같이 on 으로 설정합니다 :

        // Show the frame stats overlay

        mFrameListener->showDebugOverlay(true);

계속 진행하기전에 컴파일 하고 실행이 가능하도록 해주세요.

 

장면(Scene) 설정

 

소개

코드를 살펴보기 전에 이해를 돕고자 화면에 무엇을 넣을것인지에 대한 간략한 설명을 드리고자 합니다.

닌자 한명과 Point 조명하나를 배치시킬 입니다. 마우스왼쪽버튼 누르면 조명이 on/off 됩니다. 오른쪽 버튼을 누르고 있으면 "Mouse Look" 모드가 됩니다(카메라로 사방을 자유롭게 둘러볼 있습니다). 다수의 SceneNode 장면에 배치하여 서로다른 뷰포트를 가지는 카메라들이 attach 있도록 것입니다. "1", "2" 키로 보고 싶은 뷰포트를 선택합니다.

 

소스코드

TutorialApplication::createScene 함수로 갑시다. 먼저 희미한 주변광(ambient) 설정합니다. 조명이 off 되더라도 객체가 보여야 하며, on 상태와는 구별되어야 합니다 :

        mSceneMgr->setAmbientLight(ColourValue(0.25, 0.25, 0.25));

원점위치에 닌자 Entity 추가합니다 :

        Entity *ent = mSceneMgr->createEntity("Ninja", "ninja.mesh");

        SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode("NinjaNode");

        node->attachObject(ent);

이제 Point조명을 장면에 추가하고 닌자로부터 조금 떨어진 거리에 위치시킵니다 :  

        Light *light = mSceneMgr->createLight("Light1");

        light->setType(Light::LT_POINT);

        light->setPosition(Vector3(250, 150, 250));

        light->setDiffuseColour(ColourValue::White);

        light->setSpecularColour(ColourValue::White);

이제 SceneNode 카메라를 attach 하는일이 남았군요 :

        // Create the scene node

        node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode1", Vector3(-400, 200, 400));

        node->yaw(Degree(-45));

        node->attachObject(mCamera);

 

        // create the second camera node

        node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode2", Vector3(0, 200, 400));

TutorialApplication 클래스에서 해야 할일은 끝입니다. 이제 TutorialFrameListener 들어갑니다...