Unity

[Unity] 쿼터니언(Quaternion), 삼각함수와 역삼각함수

게임 프로그래머 2024. 10. 8. 21:17

Unity의 인스펙터 창에서 나오는 회전값은 Vector3(x, y,z)로 표시됩니다.

이것을 오일러각(Euler Angle)이라고 합니다.

하지만 많은 3D 소프트웨어는 게임 엔진에서는 짐벌락 문제로 인하여 오일러각 대신 쿼터니언(Quaternion)사용하고 있습니다.

 

왜 쿼터니언을 사용하는가?

가장 큰 문제는 짐벌락(Gimbal Lock) 문제를 방지하기 위해서입니다.

오일러 각을 사용하면 극단적인 회전 상태에서 축이 겹쳐져 하나의 회전 축이 사라지는 짐벌락이 발생할 수 있습니다.

이로 인해 정확한 회전 제어가 어려워지고, 예기치 않은 회전 오류가 생길 수 있습니다.

 

쿼터니언(Quaternion)이란?

1) 4가지 복소수를 이용한 회전 표현 방법으로 (x, y, z, w) 형태로 표현합니다.

2) 직관적인 값이 아니기 때문에 직접 수치 조작은 권장되지 않으며 대신 Unity의 내장 함수를 활용해 회전을 제어합니다.

 

유니티에서 제공하는 쿼터니언(Quaternion) 함수

Quaternion.Euler(x, y, z) 

 오일러각을 쿼터니언으로 변환합니다. Quaternion.Euler(0f, 0f, 90f)

여기서 0f, 0f, 90f는 degree 값으로 이 값이 쿼터니언으로 변경된다.

Quaternion.LookRotation(forward [, upwards])

→ 머리를 회전하여 앞과 위를 볼 때처럼, 앞과 위를 특정한 방향으로 회전한다.

Quaternion.Slerp(a, b, t)

→ 두 쿼터니언 사이를 부드럽게 보간합니다.

 

참고: 회전이 없는 초기 상태는 Quaternion.identity로 표현합니다.


 

삼각함수 기초와 아크탄젠트(ArcTangent)

쿼터니언을 활용한 회전 계산 – 수학적 이해

삼각함수란?

각도를 통해 각 변의 비율을 알아낼 수 있는 함수입니다.

이 비율은 좌표값으로도 이해할 수 있는데 그 이유는 아래와 같습니다.

삼각비 정의

 

탄젠트를 예로 들면 탄젠트는 위 정의에 따라 높이/밑변의 길이 이므로

이는 삼각형을 직각좌표계에 올려놓았을 때,

  • 밑변(b)은 X좌표
  • 높이(a)는 Y좌표 

로 해석될 수 있기 때문에, 삼각함수를 통해 각도 → 방향 벡터(좌표) 를 얻을 수 있는 것입니다.

 

아크탄젠트란?

삼각함수의 역함수를 역삼각함수라고 합니다.

삼각함수가 각도 → 비율(좌표) 를 구하는 것이라면,

역삼각함수는 좌표 → 각도를 구합니다.

 

Atan2(y, x) 함수

  • 일반적인 tan⁻¹(y/x) 계산은 사분면을 구분하지 못하지만, Atan2는 x, y의 부호까지 고려해 360도 전체 범위의 각도를 정확하게 반환합니다. (비율 적용이 아닌, 높이/밑변인 탄젠트의 개념을 적용하여 y, x순으로 인수를 넣습니다.)
  • Mathf.Atan2(y, x) 또는 MathF.Atan2(y, x) 함수는
    벡터가 x축과 이루는 각도를 계산해줍니다.
  • 이 함수의 반환값은 라디안(radian) 단위이며, 범위는 -𝝅 ~ 𝝅(-180° ~ +180°)  까지 값을 얻을 수 있습니다.
    • 여기서 각도는 육십분법인 180°를 호도법인 𝝅로 표현한 것입니다.

라디안(radian) → 도(degree) 변환

Unity의 Quaternion.Euler() 함수는 도(degree) 단위를 사용합니다.

따라서 Atan2로 얻은 라디안 결과값을 도(degree)로 변환해줘야 합니다.

이를 위해 Mathf.Rad2Deg 상수를 사용합니다.

 

회전 코드 예시

private void RotateArm(Vector2 direction)
{
    float rotZ = MathF.Atan2(direction.y, direction.x) * Mathf.Rad2Deg; // ArcTangent를 통하여 세타를 구한것이 라디안 값이고 이것을 각도로 변환해야 한다.

    characterRenderer.flipX = Mathf.Abs(rotZ) > 90f; //  90도 이상일 경우 좌우 반전

    armPivot.rotation = Quaternion.Euler(0, 0, rotZ);
}

protected override void HandleAction()
{
    // 현재 마우스 위치를 화면 기준(Screen 좌표계) 로 가져옵니다.
    Vector2 mousePos = Input.mousePosition;
    // 마우스 위치를 월드 좌표계로 변환합니다.
    // 즉, 마우스가 실제 게임 월드 내 어디를 가리키고 있는지를 나타냅니다.
    Vector2 worldPos = _camera.ScreenToWorldPoint(mousePos);
    // 플레이어 위치에서 마우스 위치까지의 벡터를 구합니다.
    // 즉, 플레이어가 마우스를 향해 바라보는 방향 벡터입니다.
    lookDirection = (worldPos - (Vector2)transform.position);
    
    // 마우스가 너무 가까우면 회전하지 않음
    lookDirection = lookDirection.magnitude < 0.9f ? Vector2.zero : lookDirection.normalized;
}

protected virtual void Update()
{
    HandleAction();
    Rotate(lookDirection);
}

정리

Unity에서 쿼터니언을 사용하는 이유는 짐벌락 문제 방지입니다. 

Unity에서는 쿼터니언 관련 여러 내장 함수를 제공하며, 직접 수치 조작보다는 함수 사용을 권장합니다.

방향 벡터 → 각도 계산은 Mathf.Atan2와 Mathf.Rad2Deg 조합으로 수행하며,

이 값을 Quaternion.Euler()로 변환하여 회전에 적용합니다.