Unity3D Tank/Quaternion

Quaternion in Unity

Soul-Learner 2015. 4. 23. 18:51

유니티 사원수 (Quaternion) 적용 예



Euler Rotation

Euler 라는 수학자가 강체의 회전을 정의할 때 x, y, z  각축을 중심으로 회전량을 표현한 각도 3개를 이용하였다.

오일러 회전법을 사용하여 공간도형의 3축을 중심으로 도형을 회전할 때, 일정범위의 회전각을 넘어서면 Gimbal Lock 이라는 현상이 발생하여 회전을 제대로 표현할 수 없는 한계가 있다


강체의 회전을 표현할 때 3축을 중심으로 한 3개의 회전각을 사용하는 방법을 오일러 회전(Euler Rotation)이라 하고 3개의 각축에 적용할 변환 각을 Euler Angle 라고 한다


도형의 각 정점을 회전하여 새로운 정점좌표를 구하는 방법에는 변환행렬(Matrix)을 이용하는 방법과 사원수(Quaternion)를 이용하는 방법이 있는데 사원수를 이용한 회전변환이 계산량이 적고 성능면에 유리하다. 오일러 회전은 1개의 축을 중심으로 한 회전이 3개 결합된 형태이므로 3개의 회전행렬로 표현이 되며 3개의 회전행렬을 곱하여 최종적으로 1개의 행렬이 산출되는데, 이 행열이 3축 회전변환을 위한 행렬이 된다. 최종 변환행렬을 특정 정점과 곱하여 새로운 회전변환된 새로운 정점좌표를 구할 수 있다. 이 방법은 오일러 회전에 해당하므로 Gimbal Lock 현상이 발생할 수 있다.


유니티 게임엔진은 내부에서 모든 회전변환을 계산할 때 사원수를 사용한다

사원수를 사용하면 오일러 회전시의 Gimbal Lock 현상을 배제할 수 있고 행렬에 비해 성능상 유리한 장점이 있다

사원수를 이용하여 정점을 회전변환할 때는 사원수와 사원수간의 곱셈과 사원수와 벡터의 곱셈이 사용된다



사원수의 곱셈

사원수와 사원수간의 곱셈

사원수는 정점을 회전변환하는 용도로 사용되며 사원수에는 회전축 정보와 회전량(각도)이 포함되어 있다.

두 사원수를 곱하면 두 사원수가 표현하고 있는 회전변환의 내용이 결합되어 한개의 사원수로 리턴된다


사원수와 벡터의 곱셈

사원수와 벡터를 곱하면 벡터(정점)가 사원수에 의해 회전변환된 결과 벡터를 얻을 수 있다



사원수를 이용한 선형보간(Linear Interpolation), 구면 선형보간( Spherical Linear Interpolation)

선형보간

  • 회전 시작 각도와 회전 완료 각도를 알려주고 그 중간 위치를 비율로 알려주면 그 비율에 해당하는 각도를 리턴해준다
  • 구면선형보간 방법보다는 정확도는 약간 낮지만 성능면에서는 유리하다
  • Quaternion.Lerp(Quaternion from, Quaternion to, float rate)


구면선형보간

  • 선형보간 방법보다는 정확하지만 성능면에서는 선형보간 방법보다는 낮다
  • Quaternion.Slerp(Quaternion from, Quaternion to, float rate)



선형보간( Quaternion.Lerp(from, to, rate) ), 구면선형보간 ( Quaternion.Slerp(from,to,rate) )방법을 사용하는 예

using UnityEngine;

using System.Collections;


public class PhysicsMath : MonoBehaviour {

// Use this for initialization

void Start () {

}


/** 사원수 곱셈의 효과

* 2개의 사원수가 동일축을 대상으로 회전변환하는 경우, 사원수의 곱셈은 2개의 각도가 덧셈이 됨)

* 사원수의 곱셈은 회전변환을 결합하는 효과가 있음

*/

private void QuaternionDemo01() {

Quaternion q1 = Quaternion.Euler ( 0f, 10f, 0f);

Quaternion q2 = Quaternion.Euler ( 0f, 7f, 0f);


// 사원수간의 곱셈은 변환을 결합하는 효과가 있고 포함된 각도는 서로 더해진다

Quaternion q3 = q1 * q2; // 사원수간의 곱셈은 사원수를 리턴한다

print ("곱셈결과="+q3.eulerAngles.y); // 17 (10+7)

}


// 사원수와 벡터의 곱셈은, 사원수에 포함된 회전축과 각도만큼 벡터(정점)을 회전변환한다

private void QuaternionDemo02(){

Quaternion from = Quaternion.Euler ( 0f, 360f, 0f);

Quaternion to = Quaternion.Euler ( 0f, 180f, 0f);

//선형보간법으로 회전변환을 위한 사원수 생성(회전축과 목표각도를 포함한다)

Quaternion q = Quaternion.Slerp (from, to, 0.05f);

print ("변환목표 각도="+q.eulerAngles.y); // 

// 전방벡터를 Y축으로 90도 회전변환하는 사원수를 곱하여 변환된 벡터를 얻는다

Vector3 p2 = q * Vector3.forward;

// 원래의 벡터와 변환된 벡터의 회전각도 차이를 확인한다

float angle = Vector3.Angle( Vector3.forward, p2);

print ("변환후="+angle); // 


Quaternion q2 = q * q; // 사원수의 곱셈은 회전변환을 서로 결합하는 효과가 있다

print ("변환목표 각도="+q2.eulerAngles.y); // 


// 회전변환 결과 벡터를 얻는다

Vector3 p3 = q2 * Vector3.forward;

angle = Vector3.Angle( Vector3.forward, p3);

print ("변환후="+angle);

}


// 동시에 여러 축을 중심으로 회전변환 사원수를 서로 결합하는 경우는 각축의 각도끼리 덧셈한 결과와 일치하지는 않는다

private void QuaternionDemo03() {

Quaternion q1 = Quaternion.Euler ( 5f, 10f, 15f);

Quaternion q2 = Quaternion.Euler ( 20f, 25f, 30f);

// 사원수간의 곱셈은 변환을 결합하는 효과가 있다

Quaternion q3 = q1 * q2; // 사원수간의 곱셈은 사원수를 리턴한다

print ("곱셈결과=" + q3.eulerAngles.x+", "+q3.eulerAngles.y+", "+q3.eulerAngles.z);

// (17.51414, 39.67442, 46.4788)

}


/** Quaternion.Lerp( Quaternion from, Quaternion to, float ratio) : 선형 보간 

*   Lerp( from, to, t) : t=0 이면 from각이 사용됨, t=1이면 to각이 사용됨

*   t=0.5이면 from~to 중간 값이 사용됨, t=0.1이면 from과 to 사이의 10% 지점에 위치한 수가 사용됨

*/

private void QuaternionLerp(){

Quaternion from = Quaternion.Euler ( 0f, 360f, 0f);

Quaternion to = Quaternion.Euler ( 0f, 180f, 0f);


//선형보간법으로 회전변환을 위한 사원수 생성(회전축과 목표각도를 포함한다)

Quaternion q = Quaternion.Lerp (from, to, 0.5f); // 360~180의 중간값인 90이 사용된 Quaternion 리턴

print ("변환목표 각도="+q.eulerAngles.y); // 90


// 전방벡터를 Y축으로 90도 회전변환하는 사원수를 곱하여 변환된 벡터를 얻는다

Vector3 p2 = q * Vector3.forward;


// 원래의 벡터와 변환된 벡터의 회전각도 차이를 확인한다

float angle = Vector3.Angle( Vector3.forward, p2);

print ("변환후="+angle); // 90

}


private void QuaternionSlerp(){

Quaternion from = Quaternion.Euler ( 0f, 360f, 0f);

Quaternion to = Quaternion.Euler ( 0f, 180f, 0f);

//선형보간법으로 회전변환을 위한 사원수 생성

Quaternion q = Quaternion.Slerp (from, to, 0.5f);

print ("변환목표 각도="+q.eulerAngles.y); //90

// 전방벡터를 Y축으로 90도 회전변환하는 사원수를 곱하여 변환된 벡터를 얻는다

Vector3 p2 = q * Vector3.forward;

// 원래의 벡터와 변환된 벡터의 회전각도 차이를 확인한다

float angle = Vector3.Angle( Vector3.forward, p2);

print ("변환후="+angle); // 90

}


//일정량 회전후 멈추는 예

private void QuaternionLerp3(){

Quaternion to = Quaternion.Euler ( 0f, 90f, 0f);  // 90도에서 회전을 멈춘다

//선형보간법으로 회전변환을 위한 사원수 생성(시작 각도로부터 5% 이격된 각도를 포함한 사원수가 생성됨)

Quaternion q = Quaternion.Slerp (transform.rotation, to, 0.05f);

print ("회전목표 각도="+q.eulerAngles.y);


transform.rotation = q;

print ("Y축 현재각="+transform.rotation.eulerAngles.y);

}


// Time.deltaTime 을 이용하는 경우

private void QuaternionLerp4(){

Quaternion to = Quaternion.Euler ( 0f, 90f, 0f);  // 90도에서 회전을 멈춘다

//선형보간법으로 회전변환을 위한 사원수 생성 (시작 각도가 매번 변경되므로 다음 목표각도도 변함)

Quaternion q = Quaternion.Slerp (transform.rotation, to, Time.deltaTime);

print ("회전목표 각도="+q.eulerAngles.y);


transform.rotation = q;

print ("Y축 현재각="+transform.rotation.eulerAngles.y);

}


// 회전시작 각도가 매번 갱신되는 경우 경우

private void QuaternionLerp5(){

Quaternion to = Quaternion.Euler ( 0f, 90f, 0f);  // 90도에서 회전을 멈춘다

//Quaternion to = Quaternion.AngleAxis(90, Vector3.up);

//선형보간법으로 회전변환을 위한 사원수 생성

Quaternion q = Quaternion.Slerp (transform.rotation, to, 0.05f);

print("회전목표 각도="+q.eulerAngles.y);


transform.rotation = q;

print ("Y축 현재각도=" + transform.rotation.eulerAngles.y);

}


// Update is called once per frame

void Update () {

QuaternionDemo03();

}

}