기초 튜토리얼 1-2
Ogre3D 삽질란/Basic Tutorial 1 2008. 11. 8. 12:06
오우거 엔진 동작과정
광범위한 주제군요. 앞으로 여러타입의 SceneManager 와 Entity, SceneNode 들과 함께 작업할 것 입니다. 이 3가지 클래스들은 오우거 어플리케이션을 구성하는 중요한 구성원들 입니다..
SceneManager 기초
화면상에 표시되는 모든것은 SceneManager 에 의해 관리됩니다(상상해 보세요). 어떠한 객체들을 화면에 표시하면 SceneManager는 그 객체들의 위치와 움직임을 관리하는 클래스 입니다. 장면을 보기위해 카메라를 생성하면 SceneManager 는 그 카메라(추후 튜토리얼에서 다루게 됩니다)의 위치를 관리합니다. 평면, 빌보드, 빛, 기타등등.. SceneManager는 사용자가 생성한 모든것을 관리합니다.
SceneManager 는 여러가지 타입이 있습니다. 지형을 그리는 SceneManager, BSP 맵을 그리는 SceneManager, 그리고 더 많은것들이 있습니다. 다양한 타입의 SceneManager는 여기서(http://www.ogre3d.org/wiki/index.php/SceneManagersFAQ) 확인할 수 있습니다. 튜토리얼을 진행하면서 점점 다양한 SceneManager 들을 다루게 될 것입니다.
Entity 기초
Entity 는 장면에 그릴 수 있는 객체타입중 하나입니다. 3D 메쉬로 표현할 수 있는 모든것은 Entity 라고 생각하셔도 됩니다. 로봇도 Entity 가 될수 있고 물고기, 걸어다닐 수 있는 넓은 땅떵어리도 넓은 형태의 Entity 가 될 수 있습니다. 그러나 빛, 빌보드, 파티클, 카메라 및 몇가지 들은Entity가 될 수 없습니다.
알아두셔야 할 것은 장면에 그려질 수 있는 객체들은 그들의 위치와 방향 정보와는 별개로 분리되어 있다는 것 입니다. 무슨 의미냐면 하나의 Entity 를 단 한번의 과정만으로는 그릴 수 없다는 뜻 입니다. 그 대신에 Entity 를 SceneNode 객체에 (attach)붙여야 하며 이 SceneNode 가 붙여진 Entity의 위치와 방향에 대한 정보를 가지게 됩니다.
SceneNode 기초
앞서 얘기한대로, SceneNode 는 attach 된 모든 객체들에 대한 방향과 위치정보를 가지고 있습니다. Entity 를 생성했을때 SceneNode 에 attach 하기 전에는 실제적으로 그려지지 않습니다. 추가로 말씀드리자면 SceneNode 자체는 화면에 출력되는 객체가 아닙니다. SceneNode 를 생성하고 Entity 를 attach 해야만 화면상에 뭔가가 그려지게 됩니다.
SceneNode 는 attach 되는 객체의 수에 제한을 받지 않습니다. 예를 들자면 화면을 돌아다니는 캐릭터가 하나 있고 스스로 빛을 내는 후광효과를 내고 싶다고 합시다. 이것을 구현하기위해서 먼저 SceneNode 를 생성하고 캐릭터 Entity 를 생성한 뒤 해당 SceneNode 에 attach 합니다. 다음 빛 객체를 생성한 뒤 아까 생성했던 SceneNode에 attach 합니다. SceneNode 는 다른 SceneNode 에 attach 가능하며 이것은 node 들의 계층적 구도를 구현할 수 있도록 해 줍니다. 나중에 있을 튜토리얼에서 더 나은 SceneNode attachment 사용법에 대해서 다룰 예정입니다.
SceneNode 의 가장 중요한 컨셉중 하나는 SceneNode 의 위치는 항상 부모 SceneNode 에 상대적 이라는 것 과 각각의 SceneManager 들은 최초의 root node를 가지는데 그 root node로 다른 모든 SceneNode 들이 attach 된다는 것 입니다.
첫 오우거 응용프로그램 실행하기
자, 이제 앞서 작성해 둔 코드로 돌아갑시다. TutorialApplication::createScene 멤버함수를 찾으세요. 이 튜토리얼에서는 이 함수의 내용만 수정할 것 입니다. 우리가 먼저 해야 할 일은 우리가 뭘 하고 있는지를 확인할 수 있도록 주변광 (ambient light)를 설정하는 것 입니다. 우리는setAmbientLight 을 호출하고 적용할 컬러를 설정할 것 입니다. 참고로 ColourValue 생성자는 red, green, blue 3가지 색상값을 받는데 각각은 0과 1사이의 실수 범위를 가집니다. 이 라인을 createScene에 추가하세요 :
mSceneMgr->setAmbientLight( ColourValue( 1, 1, 1 ) );
다음으로 해야할 일은 Entity 를 생성하는 것 입니다. SceneManager 의 createEntity 멤버함수호출에서 처리합니다 :
Entity *ent1 = mSceneMgr->createEntity( "Robot", "robot.mesh" );
여기서 잠깐 몇 가지 질문들이 예상되는군요. 가장먼저 mSceneMgr 은 어디서 온것이며 관련함수 호출시 어떤 매개변수를 줘야 하는걸까요? 여기서 mSceneMgr 변수는 현재의 SceneManager 객체(ExampleApplication 클래스에서 이미 생성되어 져 있습니다) 를 가르킵니다. createEntity 의 첫번째 매개변수는 만들고자 하는 Entity의 이름을 의미합니다. 모든 Entity 들은 고유한 이름을 가져야 합니다. 만약 2개의 Entity 들이 같은 이름을 가진 다면 에러를 발생시킬 것 입니다. "robot.mesh" 매개변수는 Entity 로 사용될 메쉬를 의미합니다. 전체적으로 다시말해, ExampleApplication 클래스가 앞으로 사용되어질 메쉬를 미리 메모리에 로드시켜놓은 것 입니다.
지금 우리는 Entity 를 생성했고 이제는 SceneNode 에 attach 해야 합니다. 모든 SceneManager 는 루트 SceneNode를 가지고 있고
우리는 그 루트 SceneNode 로부터 자식 node 를 생성해 나갈 것 입니다.
SceneNode *node1 = mSceneMgr->getRootSceneNode()->createChildSceneNode( "RobotNode" );
이 긴 명령은 먼저 현재 사용되는 SceneManager 통해서 getRootSceneNode 를 부릅니다. 다음 그 결과값 포인터(루트 SceneNode입니다) 로부터 createChildSceneNode 를 호출하게 됩니다. createChildSceneNode 의 매개변수는 만들어질 SceneNode 의 이름입니다. Entity 클래스처럼 2개 의 SceneNode들이 같은 이름을 가질 수는 없습니다.
드디어, 로봇이 그려질 위치를 전달하기위해 Entity 를 SceneNode 로 attach 할 차례입니다 :
node1->attachObject( ent1 );
그리고 끝입니다! 컴파일하고 어플리케이션을 실행하세요. 화면에 서 있는 로봇 하나를 발견하실것 입니다.
참고 : Robot.mesh 는 OgreCore.zip 파일에 포함되어 있지 않습니다. 튜토리얼을 따르면서 지금 이 시점에서 어플리케이션이 실행은 되지만 아무것도 표시되지 않을 수 있습니다. 어플리케이션이 정상적으로 실행되기 위해서 resources.cfg 파일에서 디렉토리를 옳바르게 수정해야 합니다 :
FileSystem=../../media/materials/programs
FileSystem=../../media/materials/scripts
FileSystem=../../media/materials/textures
FileSystem=../../media/models
좌표와 벡터
더 진행하기 전에, 스크린 좌표계와 오우거 벡터 객체에 대해서 알아야 합니다. 오우거(다른 그래픽 엔진들과 마찬가지로)는 x, z 축으로 수평면을 표현하고, y 축은 수직축으로 사용됩니다. 지금 모니터를 바라보는 시점에서 x 축은 왼쪽에서 오른쪽 방향이며 오른쪽 방향이 x축 양의 방향입니다. y 축은 모니터 바닥에서 윗쪽 방향이며 모니터의 상위쪽이 y축 향의 방향입니다. z 축은 모니터 내부에서 바깥쪽 방향이며 스크린 바깥쪽 방향이 z축 양의 방향입니다.
왜 로봇이 x축 양의 방향을 바라보고 있을까요? 이건 메쉬자체의 속성이자, 메쉬가 어떻게 디자인 되었는가에 따라 다릅니다. 오우거는 사용되는 모델의 방향에 대해서는 상관하지 않습니다. 로드되어지는 각각의 메쉬는 "기본 시작방향"이 제각각 다를 수 있습니다.
2차원(Vector2), 3차원(Vector3), 4차원(Vector4) 이렇게 다양한 차원을 위해서 벡터 클래스가 정의되어 있으며 이중 Vector3 가 가장 자주 쓰입니다. 만약 벡터개념에 익숙치 않다면 오우거를 이용해 본격적으로 뭔가를 하기전에 복습 하시기를 권합니다.
(http://en.wikipedia.org/wiki/Vector_%28spatial%29)
수학적인 벡터 지식은 복잡한 프로그램을 다룰때 무척 유용하게 쓰입니다.
추가적인 객체 삽입하기
이제 좌표계가 어떻게 적용되는지 이해했으니 살펴보던 코드로 돌아갑시다. 작성했던 3줄의 코드에서 로봇이 나타날 위치를 설정한 부분은 없습니다. 대부분의 오우거 함수들은 디폴트 매개변수값을 가지고 있습니다. 예를 들면 SceneNode::createChildSceneNode 멤버함수는 3개의 매개변수를 가지는데 SceneNode 의 이름, SceneNode 의 위치, 마지막으로 SceneNode가 바라볼 최초 회전값을 가집니다. 위치값은 보시는것 처럼 (0, 0, 0)이 적용되었습니다. 이번에는 원점에서 떨어진 위치에다가 추가적인 SceneNode 를 만들어 봅시다 :
Entity *ent2 = mSceneMgr->createEntity( "Robot2", "robot.mesh" );
SceneNode *node2 = mSceneMgr->getRootSceneNode()->createChildSceneNode( "RobotNode2", Vector3( 50, 0, 0 ) );
node2->attachObject( ent2 );
예전과 비교해 별반 다를바 없어 보입니다. 2가지 부분을 제외하곤 이전에 했던것과 완전 똑같은 코드를 입력했습니다. 가장 먼저 Entity 와 SceneNode 이름을 조금 다르게 설정했습니다. 두번째는 시작위치를 root SceneNode 로 부터 x축에서 50 단위 거리만큼 떨어지게 설정했습니다(기억해야 할 것은 모든 SceneNode 위치는 그들의 부모와 상대적인 위치를 가집니다). 컴파일하고 데모를 실행시켜 보십시오. 2개의 로봇이 나란히 있습니다.
Entity에 대해서..
Entity 클래스는 매우 광범위 하며, Entity 객체의 모든 사용법을 여기서 설명하지는 않을 것 이지만.. 시작하기에는 충분할 것 입니다. 지금 바로 쓰기에 유용한 몇몇 Entity 멤버함수들을 여기서 집고 넘어가고 싶습니다.
첫번째는 Entity::setVisible 와 Entity::isVisible 입니다. 간단하게 이 함수를 호출하는것만으로 어떠한 Entity 라도 보여지거나 숨겨질 수 있습니다.만약 Entity 를 숨기고 나중에 표시해야 한다면 Entity 를 파괴하고 다시 생성하는것 대신에 이 함수를 호출하세요.참고로 Entity 들을 위해서 "Pool" 을 준비할 필요는 없습니다. 메모리에 읽혀질 때마다 모든 객체의 메쉬와 텍스쳐는 단 한번만 실제적으로 메모리에 적재되기 때문에 메모리공간을 절약하기위해 노력하실 필요가 없습니다. 오직 상대적으로 부담이 적은 Entity 생성과 파괴에 대한 비용부담만 생각하시면 됩니다.
getName 함수는 Entity의 이름을 반환하며, getParentSceneNode 함수는 해당 Entity 가 attach 되어 있는 SceneNode 를 반환합니다.
SceneNodes 에 대해서..
SceneNode 클래스는 매우 복잡합니다. SceneNode 로 할 수 있는 많은것들이 있는데 가장 유용한 몇가지의 기능들을 살펴볼 것입니다. getPosition 과 setPosition 을 이용해 SceneNode 의 위치를 설정하거나 얻어낼 수 있습니다. SceneNode 에 연관된 객체의 위치를 translate 명령을 통해서 이동이 가능합니다. SceneNode 는 객체의 위치를 설정하는것 뿐만 아니라 크기와 회전역시 담당합니다. Scale 함수를 이용해서 객체의 크기를 설정할 수 있습니다. pitch, yaw, 그리고 roll 함수로 객체를 회전시킬 수 있습니다. resetOrientation 를 통해서 수행된 모든 회전을 리셋시킬 수도 있습니다. setOrientation, getOrientation 그리고 rotate 함수들을 이용해서 좀 더 세부적인 회전을 수행할 수도 있습니다. Quaternions(사원수)에 대해서는 많은 튜토리얼을 다루기 전에는 언급하지 않을 예정입니다.
독자는 이미 attachObject 를 본 적이 있을것입니다. 만약 SceneNode 에 attach 된 객체에 변화를 주고 싶다면 여기에 소개되는 연관된 함수들이 매우 유용하게 쓰일 것입니다 : numAttachedObjects, getAttachedObject (여러가지 상황별 함수들이 있음), detachObject (이것 역시 다양한 상황별 함수가 있음), detachAllObjects. 부모 SceneNode와 자식 SceneNode를 다루는 함수들은 서로 동일하게 쌍으로 존재합니다.
모든 위치이동이 부모 SceneNode 에 의해 연관되어 수행되는 이상 2개의 SceneNode 들을 함께 이동시키는건 매우 쉽습니다. 현재 어플리케이션 소스에는 다음코드가 존재합니다 :
Entity *ent1 = mSceneMgr->createEntity( "Robot", "robot.mesh" );
SceneNode *node1 = mSceneMgr->getRootSceneNode()->createChildSceneNode( "RobotNode" );
node1->attachObject( ent1 );
Entity *ent2 = mSceneMgr->createEntity( "Robot2", "robot.mesh" );
SceneNode *node2 = mSceneMgr->getRootSceneNode()->createChildSceneNode( "RobotNode2", Vector3( 50, 0, 0 ) );
node2->attachObject( ent2 );
만약 5번째 줄을 이 상태에서 :
SceneNode *node2 = mSceneMgr->getRootSceneNode()->createChildSceneNode( "RobotNode2", Vector3( 50, 0, 0 ) );
이렇게 고치게 된다면? :
SceneNode *node2 = node1->createChildSceneNode( "RobotNode2", Vector3( 50, 0, 0 ) );
RobotNode 의 자식노드 RobotNode2 를 만들게 됩니다. node1 을 움직이는것은 node2도 같이 움직이게 되지만 node2 의 이동은 node1에 영향을 주지 않습니다. 예를 들어 이 코드는 RobotNode2만 이동시킵니다.
node2->translate( Vector3( 10, 0, 10 ) );
다음의 코드는 RobotNode 를 움직이고 RobotNode2가 RobotNode 의 자식인 이상 RobotNode2 도 동일하게 같이 움직이게 됩니다 :
node1->translate( Vector3( 25, 0, 0 ) );
이런 수행을 하는데 계산하는데 어려움이 있다면 가장 좋은 방법은 최상위 루트 SceneNode 로 시작하여 아래방향으로 진행하는 것 입니다. 예를 들면(본문에서 든 예처럼), node1 로부터 시작하여 (0, 0, 0) 을 (25, 0, 0) 으로 이동시킴으로 node1의 위치는 (25, 0, 0) 만큼 부모의 위치로부터 상대적인 공간에 위치하게 됩니다. node2는 (50, 0, 0) 에서 시작하여 (10, 0, 10) 으로 이동시 켰으므로 새로운 위치는 (60, 0, 10) 만큼 부모의 위치로부터 상대적인 공간에 위치하게 됩니다.
자, 이것들이 실제로 어디에 위치하는지 알아봅시다. 루트 SceneNode 에서 시작합니다. 이것의 위치는 항상 (0, 0, 0) 입니다. 지금 node1 의 위치는 (root + node1) : (25, 0, 0) = (25, 0, 0) 이 되겠죠. 별거 아니군요.
자, 그럼 현재 node2는 node1의 자식이므로 이것의 위치는 (root + node1 + node2): (0, 0, 0) + (25, 0, 0) + (60, 0, 10) = (85, 0, 10) 가 됩니다.
이건 SceneNode 의 계층적인 위치 구조를 설명하기위한 예시에 불과합니다. 아마 아주 가끔씩 노드들의 절대위치를 계산해야 할 경우가 생길겁니다.
마지막으로 알아두실 것 은 SceneNode 와 Entity 의 이름은 getSceneNode 와 getEntity 함수 호출로 얻을 수 있기 때문에 만드는 SceneNode 마다 포인터들을 따로 보관해 둘 필요가 없습니다. 특별히 자주 쓰는것만 쓰기 쉽도록 관리해 주시면 됩니다.