본문 바로가기

프로그래밍/Unity

[Unity3D] 오브젝트 회전 - Quaternion

쩝.. 예전.. DirectX 5.0 정도 즈음에 3D를 잠깐 볼 기회가 있었는데,

그때도 머리아프던 회전이 결국 여기서도 걸린다. 그때는 회전행렬을

만들어 사용했다.

   

얼핏 이해했다고 생각했는데, 벌써 근 10년전의 일이다보니 완전 까막눈이 다 되어있다..쩝

   

객관적 사실이 아닌 감성적 느낌과 다분히 초보적으로

그냥 인터넷에서 주워들은 대로 정리해본다.

   

덧붙여 수학...아... 한숨이...

   

1. 회전 일반

회전에는 오일러회전, 축회전, 쿼터니언회전등이 있는데, 많이 사용하는게

오일러회전과 쿼터니언이다.

   

일반적으로 오일러 회전은, 매번 회전시마다 오일러각이 변화하고, 해당 변화량에다

다시 x, y, z 순서(혹은 그반대)의 각 변환행렬을 곱하여 값을 얻게 되는데,

각각의 회전은 상호간에 독립적인 회전이 이루어진다.

   

x 축회전이 이루어졌다고 y,z축을 다시 보정해서 곱셈을 하는게 아니라

x,y,z 순서의 행렬곱셈을 만들어 한번에 곱셈을 하게 되므로, 연산전의

x,y,z 축이 그대로 사용된다.

   

회전행렬을 곱하게되면 당연 상대되는 축들도 회전이 이루어져야 하는데,

이 오일러각을 이용한 회전은 x,y,z 회전행렬이 곱해지는 시점의 축을

기준으로 곱셈이 이루어지게 되므로, 축의 변화에 대처하지 못하는 증상이

나타나게 된다.

   

특히, 특정 각이 90, 180 등으로 딱!! 접힐때,

오브젝트가 특정 방향으로만 회전하게 되는 증상,

뭐 이걸 짐벌락이니 뭐니 부른다..

   

만약 x축으로 45도, y축으로 90도, z축으로 45도를 기울인다고 생각하면,

원래 의도하는 바는 세각이 각각(pitch, roll, yaw) 회전하는 것이다.

비행기를 띄워놓았다면 세각이 모두 적용되어야 한다.

헌데, 결과는 x, z축이 같은 회전이 들어가게 되어, 축 하나가 소실되는

증상이 나타난다.

   

2. unity3d 의 회전

unity3d에서는 transform.Rotate 메쏘드는 오일러 관련 회전 메쏘드인데

위 장황하게 써논 짐벌락을 만들어 보려고 샘플을 하나 돌렸다. 

   

회전하기 전.

   

회전 후

   

엇?

음.. 그냥 transform.Rotate( 45, 90, 45 ) 하면 원하는 회전이

이루어져버렸다 ... x,z축이 겹치지 않는다...

   

-_-;;; 이러면 안되는데....

   

상세한 설명을 찾기가 힘들어 포기하고, 대강 레퍼런스를 보면

각 축에 대해 z, y, x 순으로 연산이 이루어지는 것 같으나

한번에 연산하지 않고, 매번 축의 오일러 값을 변화시키고, 해당 값을 적용해

짐벌락을 막고 있는 듯 보인다.

   

일종의 축회전을 3번 실시하는 듯..(이건 확실하진 않다..)

혹은 내부적으로 쿼터니언을 사용하고 있는지도 모르겠다.

   

아무튼 찜찜하다... 이거 그냥 써도 되는건가?

   

이에 대한 대안으로 쿼터니언을 사용하는데, unity3d에서 Quaternion 클래스를 제공한다.

   

   

3. Transform

Transform 객체는 말그대로 변형에 관한 여러가지 속성과 함수들을 제공한다.

   

회전과 관련해서는 아래 세가지가 사용된다.

Rotate / RotateAround : 오일러각 회전과 축회전을 위한 메쏘드이다.

rotation : 쿼터니언 어트리뷰트

   

위 쿼터니언 객체에 쿼터니언을 새로 입력해주면 해당 회전이 자동으로 처리된다.

   

Rotate 메쏘드는

Rotate( Vector3 오일러각 , Space.Self );

Rotate( Vector3 축, float 각도, Space.Self);

Rotate( float x각, float y각, float z각, Space.Self);

로 구성된다. 대강 각 축별 각을 사용해 회전을 하는 함수임을 알수있다.

   

RotateAround 메쏘드는

특정 점을 기준으로 축을 잡아 해당 축을 기준으로 회전하는 메쏘드이다.

RotateAround( Vector3 원점, Vector3 축, float 각도 );

   

Quaternion

Quaternion Quaternion.Lookrotation( Vector3 방향 );

해당 방향으로의 쿼터니언 생성

   

float Quaternion.Angle( Quaternion rotation, Quaternion rotation );

두 쿼터니언간의 각도

   

Quaternion Quaternion.Euler( Vector3 오일러각 );

오일러각에 해당하는 쿼터니언 생성

   

Quaternion Quaternion.Slerp( Quaternion , Quaternion, float Time );

시간에 따른 변화값

   

Quaternion Quaternion.FromToRotation( Vector3 from, Vector3 to );

특정 방향에서 다른 방향으로의 쿼터니언

   

Quaternion.Identity();

로테이션 없음.

   

3. 회전예시

오일러각에서 쿼터니언을 얻는 것이나 쿼터니언<=> 회전행렬변환 등 머리아픈 요소는

이걸 업으로 삼는 사람들에게 넘기고, 취미가 목적인 사람은 그냥 예제 소스나...

   

3.1 특정 방향벡터에서 쿼터니언 얻기

Quaternion value = Quaternion.LookRotation( target.position );

   

이값은 rotation 객체에 넣게 되면, 변환 파이프라인에서 자동으로

해당 방향으로 회전을 처리하게 된다.

   

3.2 특정 x,y,z각으로 회전

transform.rotation = Quaternion.Euler( x, y, z);

해당 각으로 회전한다. 오일러 회전처럼 축이 변화하지 않으므로, 동일한 값은

update 루틴에 넣어두어도 동일한 회전으로 유지된다.

(오일러 회전은 계속 변화함.)

   

3.3 두 회전 합치기

위의 방향과 x,y,z 회전을 합치려면?

그냥 곱셈(*) 하면 된다.

   

3.4 특정 지점을 원점으로 회전

하늘에 떠 있는 카메라를 원점인 (0,0,0)을 기준으로 회전시킨다고 생각하면..

오일러 회전에서 RotateAround() 를 사용할 수 있다.

이를 쿼터니온으로 처리하려면 아래와 같은 과정을 거친다.

- 기준점과의 거리 저장

float distance = Vector3.Distance( A, B ); or (A-B).magnitude;

   

- 로컬좌표계의 카메라(GameObject)를 회전하기 위한 쿼터니언 생성

// x축을 기준으로 30도 회전시킬경우

Quaternion quat = Quaternion.Euler( new Vector3( 30.0f, 0, 0 );   

   

- 해당 쿼터니언을 rotation으로 지정

transform.rotation = quat;

   

- 카메라의 위치를 기준점으로 위치

transform.position = Vector3.zero; // 해당 기준점.

   

- 전방을 나타내는 Vector3.forward 벡터를 위의 쿼터니언으로 회전시키고, 해당 축을 기준으로

뒤로 distance 만큼 이동

transform.position -= quat * Vector3.forward * distance;

   

대강 이렇게 이루어진다.

여기서 해당 쿼터니언을 transform.rotation 에 지정하지 않고, 이동 처리한 뒤에

해당 기준점을 바라보게 하면 동일한 효과가 나타난다.

transform.LookAt( Vector3.zero );

   

세부 예제는 아래 3.5 참조.

   

3.5 캐릭터를 따라다니는 스무쓰(?) 카메라

* unity3d 사이트의 샘플 중 하나(CharacterAnimation) 에 포함된 스크립트이며,

변수명만 수정.

   

이 소스는 rotation 변수에 넣어 변환 파이프라인을 타지않고,

직접 포지션을 변경하는 형태로 구성됨.( 해당 벡터를 곱하면 됨)

   

보통 타겟을 따라다니는 카메라는 타겟을 원점으로 Y축을 잡는다.

카메라는 해당 Y축을 주변으로 회전하면 된다.

// 타겟을 지정할 수 있도록 public 으로 선언

public Transform target;

   

// 카메라의 고정값 설정

public float distance = 10.0f;

public float height = 5.0f;

   

// 변경시 변위

float heightDamping = 2.0f;

float rotationDamping = 3.0f;

   

// 모든 업데이트 이후에 호출

void LateUpdate() {

// 현재 카메라의 y축 앵글과 높이

float currAngleY = transform.eulerangles.y;

float currHeight = transform.position.y;

   

// 타겟의 앵글과 높이

float targetAngleY = target.eulerAngles.y;

float targetHeight = target.position.y + height;

   

// 원하는 앵글을 시간변위에 따라 현재 앵글값 얻음

currAngleY = Mathf.LerpAngle( currAngleY, targetAngleY, rotationDamping * Time.deltaTime );

   

// 높이도 마찬가지

currHeight = Mathf.Lerp( currHeight, targetHeight, heightDamping * Time.deltaTime );

   

// 이동할 앵글을 회전으로 변환

Quaternion newRotation = Quaternion.Euler( 0, currAngleY, 0 );

   

// 카메라의 포지션 이동

// 일단 타겟위치에서 회전과, 거리만큼을 빼준다.

transform.position = target.position;

transform.position -= newRotation * Vector3.forward * distance;

   

// 높이설정

transfor.position.y = currHeight;

   

// 타겟을 바라보게 설정

transform.LookAt( target );

   

}

   


'프로그래밍 > Unity' 카테고리의 다른 글

[Unity] 인스펙터에 UI 추가하기  (0) 2011.06.15
2D를 위한 Plane 생성  (0) 2011.05.30
Physics.Raycast  (0) 2011.05.13
2D 게임을 위한 몇가지  (0) 2011.05.09
터치 입력  (0) 2011.04.04
스크립트에서 오브젝트/컴포넌트 접근 및 활성화  (0) 2011.02.11
강체-RigidBody 기본속성  (0) 2011.02.03
FBX 에니메이션 임포트  (0) 2011.01.27
캐릭터 컨트롤 하기  (4) 2011.01.20
기본 클래스 정리  (0) 2011.01.17