게임엔진 제 14 강사원수와회전 이대현교수 한국산업기술대학교게임공학과
학습목차 오일러회전의개념과특성의이해 사원수의개념 사원수를이용한회전실습 Slerp 구면보간 닌자의부드러운회전실습
오일러 (Euler) 회전 오일러각도 (Euler Angles) 원리를이용한회전 오일러각도 : 3 차원공간에서물체가취할수있는방향을나타내는데사용되는세개의각도값의조합 18 세기수학자오일러의착안점 : 3 차원직교좌표계의좌표축인 x,y,z 축에대한회전을적당히조합하면임의의방향을나타낼수있다. Pitch, yaw, roll 의조합을통해회전을함. Y Y Y Z X Z X Z X
오일러회전의문제점 Pitch, yaw, roll 의순서에따라다른결과가얻어진다. pitch +90 Y yaw +90 Y X X Y Z Z X Z Y pitch +90 Y yaw +90 Z X Z X
오일러회전의문제점 Gimbal Lock 기준회전축이사라지는현상. 여기에빠지게되면, 회전값을결정할수가없게된다. Ex) 로보트팔을제어하거나할때어떤축의회전이하나마나한경우가생김. Ex) 북극점과남극점에서는경도의의미가없슴. 나침반이의미가없어진다. 내가북극점에있는지남극점이있는지알수가없다. 임의의방향을설정할때세축의회전을조합해야하는데이게얼른직관적으로되질않음. 따라서, 현재의특정방향에서다른방향으로바꾸려고할때각회전값을계산하는것이효율적이지않음.
사원수 (Quaternion) 회전 사원수 한개의실수성분과세개의허수성분으로이루어진개념의수. q = w+ xi+ yj+ zk 삼차원공간에서의회전을쉽게나타낼수있고, 여러조합의회전연산을쉽게할수있는특성을지님. Y Z X
사원수의생성자함수 (1) Quaternion (Real fw=1.0, Real fx=0.0, Real fy=0.0, Real fz=0.0) 직접 4 개의값을이용하여생성. 몇가지사원수값의설명 : w x y z Description 1 0 0 0 Identity quaternion, no rotation 0 1 0 0 180' turn around X axis 0 0 1 0 180' turn around Y axis 0 0 0 1 180' turn around Z axis sqrt(0.5) sqrt(0.5) 0 0 90' rotation around X axis sqrt(0.5) 0 sqrt(0.5) 0 90' rotation around Y axis sqrt(0.5) 0 0 sqrt(0.5) 90' rotation around Z axis sqrt(0.5) -sqrt(0.5) 0 0-90' rotation around X axis sqrt(0.5) 0 -sqrt(0.5) 0-90' rotation around Y axis sqrt(0.5) 0 0 -sqrt(0.5) -90' rotation around Z axis
사원수의생성자함수 (2) Quaternion (const Quaternion &rkq) 다른사원수와동일한값을사용. Quaternion (const Matrix3 &rot) 회전행렬을이용하여생생. Quaternion (const Radian &rfangle, const Vector3 &rkaxis) rfangle: 회전각. 라디안값. rfaxis: 회전축을나타내는벡터. 벡터의크기값에따라, 크기변환도함께일어난다.
사원수를이용한노드의회전함수 void Ogre::Node::rotate ( const Quaternion & q, TransformSpace relativeto = TS_LOCAL) 노드를사원수 q 를적용하여회전한다. node->rotate(q); Y n q=(θ, n) θ node Z X
실습 Quaternion 사원수회전실습
PlayState.cpp void PlayState::enter() { 중략 실습 Quaternion z(degree(90), Vector3::UNIT_Z); mninjanode->rotate(z); 후략 +z 축을기준으로 +90 도회전. z
PlayState.cpp void PlayState::enter() { 중략 실습 Quaternion z(degree(90), Vector3::UNIT_Z); Quaternion x(degree(90), Vector3::UNIT_X); Quaternion p = x * z; mninjanode->rotate(p); 후략 z 에의한회전을먼저하고, 그리고 x 에의한회전을수행함. z x
PlayState.cpp void PlayState::enter() { 중략 실습 Quaternion z(degree(90), Vector3::UNIT_Z); Quaternion x(degree(90), Vector3::UNIT_X); Quaternion p = z * x; mninjanode->rotate(p); 후략 x z
PlayState.cpp void PlayState::enter() { 중략 실습 Quaternion q(degree(180), Vector3(1, 1, 0)); mninjanode->rotate(q); 후략 (1, 1, 0) 벡터를축으로하여 180도회전. 벡터의크기 = 2 2 2 만큼확대됨. 1 + 1 + 0 = 2
PlayState.cpp void PlayState::enter() { 중략 실습 Vector3 r = Vector3(1, 1, 0); r.normalise(); Quaternion q(degree(180), r); mninjanode->rotate(q); 후략 벡터의크기를 1 로정규화함.
노드의회전관련함수 const Quaternion& Ogre::Node::getOrientation () 노드에적용된회전 ( 다시말하면사원수 ) 를구한다. void Ogre::Node::setOrientation ( const Quaternion & q ) 노드의초기방향을기준으로하여, 사원수 q 를적용시켜회전한다. rotate(q) == setorientation( q * getorientation() )
사원수와벡터의곱 Vector3 Ogre::Quaternion::operator * ( const Vector3 & rkvector ) 벡터 rkvector 를사원수를이용하여회전한다. Vector3 v = q * u; q=(θ, n) u v Y Z X
두벡터로부터회전값사원수를얻는함수 Quaternion Ogre::Vector3::getRotationTo ( const Vector3 & dest, const Vector3 & fallbackaxis = Vector3::ZERO) 현재벡터로부터 dest 벡터를얻기위해필요한회전을나타내는사원수 q 를구한다. Quaternion q = u->getrotationto(v); 단위벡터가되어야함. u Y v Z X
실습 WalkAroundNinja 임의방향회전실습
bool PlayState::frameStarted(GameManager* game, const FrameEvent& evt) {.. 중략 실습 Vector3 src = mninjanode->getorientation() * Vector3::NEGATIVE_UNIT_Z; Quaternion quat = src.getrotationto(mdirection); mninjanode->rotate(quat); 중략 후략 } Vector3 src = mninjanode->getorientation() * (-Vector3::UNIT_Z); Quaternion quat = src.getrotationto(mdirection); mninjanode->rotate(quat);
닌자이동시바라보는방향을바꾸려면? v r u r (1) 현재로봇이바라보고있는방향을가리키는벡터 u 를구한다. (2) u 벡터를 v 벡터방향으로회전하기위한사원수를구한다. (3) 닌자노드에사원수를적용하여회전시킨다.
(1) 현재닌자가바라보고있는방향구하기 메쉬자체의바라보고있는방향. -z Vector3 src = mninjanode->getorientation( )* Vector3::NEGATIVE_UNIT_Z; 현재까지로봇노드에적용된회전.
(2) u 를회전하여 v 를만들기위한사원수계산 Quaternion quat = src.getrotationto(mdirection); src 벡터를회전하여 mdirection 벡터를만들게하는사원수를구한다.
(3) 로봇노드에사원수적용하여회전 mninjanode->rotate(quat);
방향전환시문제점은? 닌자의방향전환시순간적으로방향이바뀜으로써, 어색하다.
부드러운방향전환을하려면? 방향전환점에, 여러개의이동점을둠으로써부드럽게회전시킨다. 일일이점을지정해야된다는점이문제
Slerp() 함수를사용한부드러운방향전환방법 구면보간 (Spherical Linear Interpolation) 벡터의방향전환을부드럽게하기위해필요한여러개의사원수값을생성. Quaternion Ogre::Quaternion::Slerp ( Real ft, const Quaternion & rkp, const Quaternion & rkq, bool shortestpath = false) ft: 회전비율 (0 부터 1 까지의값으로지정 ) rkp: 시작사원수 rkq: 종료사원수 shortestpath: 가장짧은경로를통해서회전함. 함수의리턴값은시작사원수로부터종료사원수까지회전을할때, ft 로주어진회전비율까지로회전을하고자할때, 필요한사원수가된다.
실습 SlerpRotation 부드러운닌자의회전실습
PlayState.h class PlayState : public GameState { 실습.. 중략 bool mrotating; Ogre::Quaternion morientsrc, morientdest; Ogre::Real mrotprogress; Ogre::Real mrotfactor; 후략 }
PlayState.cpp bool PlayState::frameStarted(GameManager* game, const FrameEvent& evt) {.. 중략 Quaternion delta; if (mrotating) { mrotprogress += mrotfactor; if (mrotprogress > 1) { mrotating = false; } else { delta = Quaternion::Slerp(mRotProgress, morientsrc, morientdest); mninjanode->setorientation(delta); } } 중략 Vector3 src = mninjanode->getorientation() * (-Vector3::UNIT_Z); Quaternion quat = src.getrotationto( mdirection ); mrotating = true; mrotfactor = 1.0f/5; morientsrc = mninjanode->getorientation(); morientdest = quat * morientsrc; mrotprogress = 0; 후략 } 실습
실행결과 : 코너에서부드러운회전
현재구면보간을이용하여회전중인가? bool mrotating; 시작사원수와종료사원수. Quaternion morientsrc, morientdest; Real mrotprogress, mrotfactor; 프레임당회전을몇 % 를진행시킬것인가? 구면보간이용한회전이진행된정도 (0-1 사이의값 )
bool framestarted(const FrameEvent &evt) { Quaternion delta; if (mrotating) { mrotprogress += mrotfactor; if (mrotprogress > 1) { mrotating = false; } else { 회전비율을진행. 노드를 mrotprogress 비율반큼회원하기위한사원수를구함. delta = Quaternion::Slerp(mRotProgress, morientsrc, morientdest); mninjanode->setorientation(delta); } }
} else { Real move = mwalkspeed * evt.timesincelastframe; // 이동량계산 mdistance -= move; // 남은거리계산 if (mdistance <= 0.0f) { // 목표지점에다왔으면 mninjanode->setposition( mdestination ); // 목표지점에로봇을위치 mdirection = Vector3::ZERO; // 정지상태로들어간다. if (! nextlocation( ) ) { manimationstate = ninjaentity->getanimationstate( "Idle1" ); manimationstate->setloop( true ); manimationstate->setenabled( true ); } else { Vector3 src = mninjanode->getorientation() * (-Vector3::UNIT_Z); Quaternion quat = src.getrotationto( mdirection ); mrotating = true; mrotfactor = 1.0f/5; morientsrc = ninjanode->getorientation(); morientdest = quat * morientsrc; mrotprogress = 0; } } else
정리 오일러회전 x,y,z 축을회전축으로한회전. 이해하기쉬우나, gimbal lock 등의문제가있음. 사원수 한개의실수성분과세개의허수성분으로이루어진개념의수. 임의의회전축을중심으로한회전을하나의사원수값으로표현할수있음. Slerp 구면보간 두개의회전값사이를부드럽게보간하는방법.