20240126_사원수(쿼터니언)

2024. 1. 27. 12:13IT/TIL

오늘의 TIL은 수학에서 사용되는 사원수(quarternion - 쿼터니언)에 대한 내용이다.

 

이 쿼터니언을 공부해야되는 이유는

 

게임을 개발하는 과정에서 3차원을 표현할 때 오일러 각을 사용했을 때,

 

발생하는 짐벌락을 해결할 수 있는 방법으로 쿼터니언을 사용할 수 있는데,

 

유니티에서는 기본적으로 쿼터니언을 사용하여 게임 오브젝트의 3차원 방향을 저장하기 때문이다.

 

쿼터니언에 대한 유니티 문서 링크

https://docs.unity3d.com/kr/2021.3/Manual/class-Quaternion.html

 

중요 클래스 - Quaternion - Unity 매뉴얼

Unity는 Quaternion 클래스를 사용하여 게임 오브젝트의 3차원 방향을 저장하고, 이를 통해 한 방향에서 다른 방향으로의 상대 회전을 설명합니다.

docs.unity3d.com

 

 

 

1. 쿼터니언(사원수)란

유니티에서 사용하는 쿼터니언에 대한 대략적인 개념을 정리하고 가려고 한다.

 

쿼터니언이란 3차원에서 회전을 표시하는 방법 중 하나로,

 

4개의 숫자를 사용하여 3D에서 단위 축을 중심으로 회전 방향과 각도를 표시하는 방법이다.

 

이 4개의 숫자를 x, y, z, w로 이루어져있지만, 복소수를 사용하므로 알고 있는 오일러 각과는 다른 숫자이다.

 

 

2. 오일러 각이란?

오일러 각(Euler Angles)는 우리가 일반적으로 회전을 표시할 때 사용하는 방법으로,

 

서로 수직인 3개의 축(x, y, z)을 이용하여 해당 오브젝트의 회전도를 0 ~ 360의 범위로 표시하는 방법이다.

 

매우 직관적이지만 오일러 각을 적용시키는 과정이 3개의 축을 순서대로 회전시키기 때문에

 

짐벌락(Gimbal-Lock) 문제가 발생한다.

 

 

3. 짐벌락(Gimbal-Lock)이란?

짐벌락이란 오일러 각을 적용시켜 3개의 축을 순서대로 회전시키는 경우에 발생할 수 있는 문제로

 

두번째 고리가 도는 것으로 인하여 첫번째 고리와 세번째 고리가 겹치면서 한 축이 사라지게 되는 현상을 말한다.

 

아래의 영상에서 짐벌락에 대한 설명이 자세히 나와있다.

 

 

이러한 짐벌락이 발생하여 3D 공간에 있는 오브젝트가 2차원으로만 회전할 수 있게되므로

 

이를 해결하기위해 쿼터니언을 사용하게 되었다고 할 수 있다.

 

 

4. 쿼터니언의 특징

쿼터니언은 오일러 각에서 생길 수 있는 문제를 해결할 수 있는 방안으로

 

각 축을 순서대로 회전시키는 오일러 각과는 달리 모든 축을 한 번에 계산하기 때문에 짐벌락이 발생하지 않는다.

 

다시 말해서, 각 축의 회전도를 표시하는 오일러 각과 다르게

 

특정 회전 상태를 4개의 값을 이용하여 표현한 것이 쿼터니언이라고 할 수 있다.

 

 

4-1. 쿼터니언의 장단점(오일러 각과 비교)

쿼터니언과 오일러 각의 장단점을 비교하면

 

오일러 각은 x, y, z축을 기반으로 한 데이터로 아래의 장점이 있다.

 

1. 매우 직관적인 데이터이므로 개발자가 조작하기에 쉽다.

2. 0 ~ 360의 회전을 표현할 수 있다

 

단점으로는 아래와 같은 문제점이 있다.

 

1. 짐벌락 현상이 발생한다.

2. 보간 문제가 발생한다.

(보간 문제란 오일러 각을 이용했을 때, 회전의 중간 값을 결정할 수 없는 문제이다 - 아래에서 설명)

 

 

이와 반대로 쿼터니언의 장점은 아래와 같다.

 

1. 모든 축의 회전을 한 번에 계산하기 때문에 짐벌락 문제가 생기지 않는다.

2. 방향과 회전을 모두 표현할 수 있다. 따라서 중간 값을 결정할 수 있다(보간 문제가 발생하지 않는다)

 

하지만 쿼터니언의 단점도 존재하는데,

 

1. 비직관적으로 개발자가 원하는대로 데이터를 수정하기 어렵다(함수를 이용해야한다).

2. 쿼터니언의 회전은 하나의 방향에서 다른 방향으로 측정되기 때문에 180도보다 큰 회전은 표현할 수 없다.

 

 

4-2. 보간(Interpolation) 문제란?

보간(interpolation)이란 처음과 끝의 값을 가지고 중간의 값을 계산하는 것이다.

오브젝트의 애니메이션을 수행하는 경우, 매 상태(위치와 회전 등)을 받아와서 이어주는 것이 아니라

처음과 끝의 상태를 받아와 보간을 사용하여 부드럽게 수행한다.

오일러 각을 이용하여 회전을 표시하는 경우에는 특정 순간의 회전 값을 정확하게 계산할 수 없다.

 

예를 들어서 (0, 180, 0)으로 표현되는 방향은 (180, 0, 180)과 같은 방향을 표현한다.

 

 

좌측이 (0, 180, 0), 우측이 (180, 0, 180)의 회전을 갖고 있는 물체이지만,

 

이 방향을 보간하여 절반인 (0, 90, 0)과 (90, 0, 90)을 구하면 아래와 같이 표현된다.

 

 

좌측의 경우에는 y축(녹색축)으로 90도 회전해도 화면 상으로 회전이 바뀌지 않았지만,

 

우측의 경우에는 화면 상으로 보면 90도 회전하여 넘어진 것으로 보이게 바뀌었다.

 

 

오일러 각을 이용하여 보간을 작업하면, 이렇게 두 가지 값으로 표현되는 경우가 있으므로

 

특정 순간의 값을 정확하게 표현할 수 없는 보간 문제가 발생하게된다.

 

따라서 이를 해결하기 위해서는 쿼터니언을 사용하여야한다.

 

 

5. 쿼터니언 사용 시의 주의점

쿼터니언은 개발자가 사용하기에 매우 좋은 방법인 것은 맞지만, 매우 비직관적이므로 이 값을 직접 수정해서는 안된다.

 

(직접 수정하는 경우에 원하는 결과를 얻지 못할 가능성이 크다)

 

따라서 쿼터니언을 사용하는 경우에는

 

1. 쿼터니언의 값은 직접 수정하지 않는다.

2. 특정 각도를 입력하는 경우에는 오일러 각을 쿼터니언으로 변경하는 함수를 이용한다.

 

예를 들어서

void Update()
{
    var rot = transform.rotation;  // Quaternion
    rot.x += Time.deltaTime * 10;
    transform.rotation = rot;
}

 

이 코드는 쿼터니언을 직접 수정하는 코드로 rot가 쿼터니언이므로,

이 값에 직접 수치를 더하는 것은 원하는 결과를 얻지 못할 가능성이 크다.

 

void Update()
{
    var angles = transform.rotation.eulerAngles;
    angles.x += Time.deltaTime * 10;
    transform.rotation = Quaternion.Euler(angles);
}

 

또, 이 코드도 수정한 것처럼 보이지만 똑같이 문제가 있다.

쿼터니언을 오일러 각으로 변경한 angles의 값이 개발자가 예상한 값이 아닐 수 있기 때문이다.

각 시점의 오일러 각도는 다르게 변환될 수 있다. (위의 4-2 예시에서 (0, 180, 0)과 (180, 0, 180)이 같은 표현인 경우 등)

따라서 angles가 개발자가 예상한 값이 아닌 경우에

angles.x에 10을 더하게 되면 결과도 예상하지 못한 값이 나오기 때문이다.

 

 

따라서 쿼터니언을 이용하는 경우에는 아래와 같이 사용해야 한다.

float x;

void Update()
{
    x += Time.deltaTime * 10;
    transform.rotation = Quaternion.Euler(x, 0, 0);
}

 

x의 값을 정확하게 선언하고(개발자가 원하는 값을 바로 선언할 수 있다)

원하는 만큼의 x값 변환을 적용시키기위해 Quaternion.Euler(x, 0, 0)으로 확실하게 x축의 회전을 만들어 줄 수 있다.

 

 

추가로 쿼터니언을 직관적으로 이해하려고 하면 안되는 예시를 들면

using UnityEngine;

public class EulerAnglesProblemExample : MonoBehaviour
{
	private void Start()
	{
		Quaternion myRotation = Quaternion.identity;
		myRotation.eulerAngles = newVector3(150, 35, 45);
		
		Debug.Log(myRotation.eulerAngles);
		// 결과값 : (30.0, 215.0, 225.0)
	}
}

 

위와 같이 코드를 작성한 뒤, Debug를 해보면 결과값은 입력했던 오일러 각과는 다른 값이 나오게 된다.

즉, 우리가 오일러 값으로 입력한 vector3는 컴퓨터가 알고 있는 값과 다르기 때문에 쿼터니언을 바로 사용해서는 안된다.

 

 

 

오늘은 유니티에서 사용하는 방향을 표시하는 방법인 쿼터니언에 대해 TIL을 작성했는데,

 

해당 내용이 직관적이지 않고 수학적인 부분이 많기 때문에 작성하는데도 오래 걸렸고

 

정확한 이해가 되지 않아 작성한 내용에 오류가 있을 수 있을 것 같다.

 

이는 추후에 더 공부를 한 뒤에 확인하여 오류가 있다면 수정할 예정이다.