기초 튜토리얼 4-3 (마지막)

Ogre3D 삽질란/Basic Tutorial 4 2008. 11. 20. 16:19

frameStarted 함수

이제 튜토리얼의 진짜 내용이 시작됩니다 : 모든 프레임마다 일어나는 동작을 설정하는 . 현재 frameStarted 함수에는 다음 코드가 있습니다 :

        return ExampleFrameListener::frameStarted(evt);

한줄짜리 코드가 지금까지 프로그램이 실행될 있도록 유지시켜 줬습니다. ExampleFrameListener::frameStarted 함수는 수많은 동작을 포함합니다 (모든 키에 대한 반응, 모든 카메라에 대한 움직임 등등). 일단 지금은 TutorialFrameListener::frameStarted 함수의 모든내용을 지우세요.

"Open Input System"(OIS) 입력처리를 위해 세가지 클래스를 제공합니다 : 키보드, 마우스, 조이스틱. 튜토리얼에서는 키보드와 마우스객체처리에 대해서만 다룹니다. 오우거에서 조이스틱(또는 패드) 사용하고 싶다면 조이스틱 클래스를 참고하세요.

unbuffered 입력 방식을 사용하기위해서는 일단 키보드와 마우스의 상태를 가로채야 합니다. 마우스와 키보드 객체의 capture 멤버함수를 써서 구현이 가능합니다. 예제 프레임워크에는 이미 mMouse, mKeyboard 변수명으로 객체가 이미 생성되어 있습니다. TutorialFrameListener::frameStarted 멤버함수에 다음 코드를 추가하세요 :

        mMouse->capture();

        mKeyboard->capture();

이제 다음으로 Esc 키를 누르면 종료되게끔 만들어 봅시다. 만약 키가 눌러지게 되면 InputReader isKeyDown함수에 키코드를 매개변수로 전달해 해당 키가 눌러졌는지를 검사하게 됩니다. 만약 Esc키가 눌러졌으면 false 리턴해 프로그램을 종료하게 것입니다 :

        if(mKeyboard->isKeyDown(OIS::KC_ESCAPE))

           return false;

계속해서 렌더링하기 위해서는 frameStarted 함수가 true 값을 리턴해야 합니다. 그러기 위해서 함수의 마지막 부분에 다음 코드를 추가하세요 :

        return true;

함수 내의 모든 코드는 바로 "return true" 라인에 도달해야 합니다.

FrameListener 에서 왼쪽 마우스 버튼을 누르면 조명이 on/off 되는 기능을 구현해 봅시다. 마우스 버튼이 눌러졌는지는 InputReader getMouseButton 함수로 쿼리하여 알아 있습니다. 보통 0 왼쪽버튼, 1 오른쪽, 2 가운데 버튼입니다. 어떤 시스템에서는 1 가운데, 2 오른쪽인 경우도 있습니다. 예상과 다르게 동작된다면 그렇게도 설정해 보세요.

        bool currMouse = mMouse->getMouseState().buttonDown(OIS::MB_Left);

마우스 버튼이 눌러지면 currMouse 변수가 true 값을 가집니다. 조명이 토글될지 여부는 마우스가 이전프레임에서는 눌려지지 않았고 currMouse 값이 true 여야 합니다(마우스가 눌려졌을때 한번만 토글되게끔 하고 싶을때). 조건이 만족될 조명의 setVisible 멤버함수로 on/off 토글하게 됩니다 :

        if (currMouse && ! mMouseDown)

        {

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

            light->setVisible(! light->isVisible());

        } // if

mMouseDown 내용과 currMouse 내용을 똑같이 맞춰줘야 합니다. 다음 프레임때 이전 프레임에서 마우스가 눌려졌었는지 떼어졌었는지를 검사할 있어야 합니다.

        mMouseDown = currMouse;

실행시켜보세요. 왼쪽클릭으로 조명 on/off 토글이 가능합니다! ExampleFrameListener frameStarted 함수호출이 일어나지 않았으므로 카메라이동은 아직 안됩니다.

마지막에 수행된 행동에 연관된 마우스의 이전상태를 저장했습니다. 한가지 단점으로는 누를때마다 반응한다는 것입니다. 그러므로 불리언 변수를 써서 보완해야 합니다. 한가지 방법으로는 마지막에 눌려졌던 시간을 체크하고 일정시간이 지난 다음에야 허용하는 방식입니다. mToggle변수로 이러한 기능을 관리합니다. mToggle값이 0보다 크면 아무런 동작도 실행되지 않으며 0보다 작아야 입력에 따른 동작이 실행됩니다. 2개의 키에 방법을 적용시킬 입니다.

먼저 해야 것은 시간이 지남에 따라 mToggle값을 줄이는 입니다 :

        mToggle -= evt.timeSinceLastFrame;

동작실행에 관한 mToggle 갱신되었습니다. 다음 바인딩 키인 "1" 첫번째 SceneNode 카메라를 attach 시킵니다. 전에 mToggle 변수값이 0보다 작은지를 확인해야 합니다 :

        if ((mToggle < 0.0f ) && mKeyboard->isKeyDown(OIS::KC_1))

        {

mToggle 변수값을 1 후에 다시 동작이 실행될 있도록 설정합니다 :

            mToggle = 0.5f;

다음 카메라가 마지막으로 attach 되었던 것을 해제하고 mCamNode 값을 CamNode1으로 설정하고attach 시킵니다.

           mCamera->getParentSceneNode()->detachObject(mCamera);

           mCamNode = mSceneMgr->getSceneNode("CamNode1");

           mCamNode->attachObject(mCamera);

       }

"2" 누르면 CamNode2 동작할 수도 있습니다. 언급한 코드에서 숫자를 1에서 2로만 바꾸면 되기때문에 쉽게 이해 하실수 있으리라 생각하고 설명은 생략합니다. (같은 설명은 지루하기만 하니까요) :

        else if ((mToggle < 0.0f) && mKeyboard->isKeyDown(OIS::KC_2))

        {

           mToggle = 0.5f;

           mCamera->getParentSceneNode()->detachObject(mCamera);

           mCamNode = mSceneMgr->getSceneNode("CamNode2");

           mCamNode->attachObject(mCamera);

       }

실행시켜보세요. "1", "2" 누르면 카메라의 뷰포인트가 바뀔겁니다.

다음으로 해야 일은 WASD 또는 화살표키를 누르면 mCamNode 변환시키는 작업입니다. 지금까지와는 다르게 마지막으로 움직였던 카메라를 추적할 필요가 없습니다. 키가 눌러진 상태라면 계속해서 움직여야 하기 때문이죠. 그래서 간단하게 구현이 됩니다. 움직이고 싶은 방향을 설정할 Vector3 객체를 만듭니다 :

        Vector3 transVector = Vector3::ZERO;

W키와 위쪽 화살표를 누르면 전진합니다. (모니터를 바라보는 방향인 -z 방향입니다) :

       if (mKeyboard->isKeyDown(OIS::KC_UP) || mKeyboard->isKeyDown(OIS::KC_W))

           transVector.z -= mMove;

S 아랫쪽 화살표도 비슷하게 처리합니다. 대신에 이번에는 +z 방향입니다 :

       if (mKeyboard->isKeyDown(OIS::KC_DOWN) || mKeyboard->isKeyDown(OIS::KC_S))

           transVector.z += mMove;

왼쪽과 오른쪽 방향을 위해서 +x -x 설정합니다 :

       if (mKeyboard->isKeyDown(OIS::KC_LEFT) || mKeyboard->isKeyDown(OIS::KC_A))

           transVector.x -= mMove;

       if (mKeyboard->isKeyDown(OIS::KC_RIGHT) || mKeyboard->isKeyDown(OIS::KC_D))

           transVector.x += mMove;

마지막으로 y축을 따라서 위아래로도 움직이게 봅시다. 개인적으로 E/PageDown 아래로, Q/PageUp 키를 위로 사용합니다 :

       if (mKeyboard->isKeyDown(OIS::KC_PGUP) || mKeyboard->isKeyDown(OIS::KC_Q))

           transVector.y += mMove;

       if (mKeyboard->isKeyDown(OIS::KC_PGDOWN) || mKeyboard->isKeyDown(OIS::KC_E))

           transVector.y -= mMove;

이동할 방향을 transVector 저장했고 카메라의 SceneNode 적용할 준비가 되었습니다. 첫번째 주의사항으로는 SceneNode 회전시킨다음에 움직이면 x, y, z 방향이 꼬입니다. 이런 현상을 고치려면 지금까지 회전시킨 방향을 이동시키는데 사용되는 변환노드에도 적용시켜야 한다는 입니다. 어렵게 들릴지 몰라도 생각보다 쉽습니다.

공간좌표변환을 위해서 오우거는 다른 몇몇 그래픽 엔진들 처럼 행렬계산을 사용하지 않습니다. 대신에 사원수(Quaternions) 공간좌표변환을 수행합니다. 사원수계산에는 이해하기 어려운 4차원 선형대수학 계산식이 필요합니다. 고맙게도 오우거를 사용함에 있어서는 이러한 계산을 위해 복잡한 수학 배경지식을 몰라도 됩니다. 아주 간단하게도 벡터회전을 위해 사원수를 쓰기위해선 2개의 벡터를 곱하는게 전부입니다. 경우 매번 회전결과값을 이동변환좌표에다 적용시켜야 합니다. 사원수 회전값을 구하려면 SceneNode::getOrientation() 함수를 사용하시면 됩니다. 결과값으로 이동변환 노드에 곱하면 됩니다.

두번째 주의사항은 시간이 흐른만큼 이동수치가 조절되어야 합니다. 안그러면 프레임율에 따라서 움직이는 속도가 달라질 것입니다. 절대로 그렇게 되서는 안됩니다. 이러한 문제점을 해결하기위한 변환과정 호출부분입니다 :

        mCamNode->translate(transVector * evt.timeSinceLastFrame, Node::TS_LOCAL);

새로운 매개변수가 보이는군요. 노드를 이동시키거나 축에대한 회전을 수행할때 어떠한 공간을 기준으로 수행될지를 설정할 있습니다. 평상시 객체이동을 할때 이러한 매개변수는 설정하지 않습니다. 기본값은 TS_PARENT이며 부모노드의 공간좌표를 중심으로 이동된다는 의미입니다. 경우 부모노드는 root scene 노드 입니다. W 키를 누르면 (전진) z 방향값에 뺄셈을 수행하며 -z 방향으로 움직인다는 의미 입니다. 만약 방금전의 코드에서 TS_LOCAL 값을 매개변수로 넣지 않았다면 global -Z 방향으로 카메라가 이동할 것입니다. 하지만 W 누르면 앞으로 나아가게 하고 싶으므로 노드가 실제로 바라보는 방향으로 가게해야 합니다. 그래서 "Local" 좌표변환공간을 쓰는 입니다.

다른방법으로 구현할 수도 있습니다(약간 간접적인 방법으로). 사원수로 노드의 방향을 방향벡터에 곱하면 동일한 결과를 얻을 있습니다. 결과값은 완전 동일합니다 :

       // Do not add this to the program

       mCamNode->translate(mCamNode->getOrientation() * transVector * evt.timeSinceLastFrame, Node::TS_WORLD);

이것역시 로컬공간에서 카메라노드위치를 변환시킵니다. 경우 특별한 이유는 없습니다. 오우거는 3가지의 변환공간을 제공합니다 : TS_LOCAL, TS_PARENT, TS_WORLD. 3가지 공간을 제외한 또다른 벡터공간에서의 변환이 필요할지도 모릅니다. 이런경우 바로 앞에서 코드와 비슷하게 처리하시면 됩니다. 벡터공간을 표현하는 사원수를 구하시고 (또는 매치시키려는 객체의 방향), 올바른 변환벡터를 구하기 위해 곱셈과정을 거친후, TS_WORLD 공간 기준으로 움직이면 됩니다. 당분간 그럴일은 별로 없을겁니다. 그리고 나중에 있을 어떤 튜토리얼에서도 나오지 않을 입니다.

지금까지 키보드를 눌렀을때를 처리했습니다. 이제는 마우스 오른쪽 버튼을 누르고 있으면 자유자재로 사방을 둘러볼 있는 기능을 추가해 봅시다. 먼저 마우스 버튼이 눌러졌을때의 상태를 체크해야 합니다 :

        if (mMouse->getMouseState().buttonDown(OIS::MB_Right))

        {

시간이 지난만큼 마우스가 움직인거리를 고려해서 카메라의 yaw pitch 동작을 처리합니다. 구현하기 위해서 X, Y 상대적인 변화량을 구한 다음 pitch yaw 함수로 넘깁니다 :

           mCamNode->yaw(Degree(-mRotate * mMouse->getMouseState().X.rel), Node::TS_WORLD);

           mCamNode->pitch(Degree(-mRotate * mMouse->getMouseState().Y.rel), Node::TS_LOCAL);

        }

yaw 동작에 있어서는 TS_WORLD 벡터공간을 사용했습니다(따로 설정하지 않는이상 회전함수는 TS_LOCAL 값이 기본치입니다). pitch동작이 yaw 영향받지 않도록 고려한 것입니다. yaw동작은 항상 같은축으로 회전하도록 했습니다. 이것이 바로 세번째 주의사항입니다. 회전간의 상호작용을 까먹을 있습니다. yaw 동작을 TS_LOCAL에서 한다면 다음과 같은 상황이 발행될 있습니다.

 

사용자 삽입 이미지

컴파일 실행시켜 보세요.

튜토리얼에서 회전과 사원수에 대한 모든것을 다룬건 아닙니다(튜토리얼 관련내용만 다루기에 충분한 내용입니다). 다음 튜토리얼에서는 키가 눌려졌는지 프레임마다 검사하는방식 대신에 쓰이는 버퍼방식 마우스입력에 대한 내용이 이어질 것입니다.

: