게임 수학의 골칫거리 - 쿼터니언(Quaternion) 쉽게 이해하기

오일러 각의 한계인 짐벌 락(Gimbal Lock) 현상을 해결하기 위해 게임 엔진에서 사용되는 쿼터니언(사원수)의 개념을 직관적으로 설명합니다.

✓ 복사 완료!
챕터게임 수학 & 물리
난이도중급
상태● 검토 완료
대상 엔진
Engine Agnostic
최종 업데이트
게임 수학의 골칫거리 - 쿼터니언(Quaternion) 쉽게 이해하기

3D 게임 프로그래밍을 시작하면 누구나 한 번쯤 회전(Rotation)을 제어하다가 멘붕에 빠지게 됩니다. 오브젝트의 각도를 조금 돌렸을 뿐인데 축이 꼬여 이상하게 돌아가거나 아예 특정 축의 회전이 먹통이 되는 현상을 겪게 되죠.

이것이 바로 짐벌 락(Gimbal Lock)이며 이를 피하기 위해 게임 엔진이 채택한 구원자가 바로 쿼터니언(사원수, Quaternion)입니다.


1. 오일러 각(Euler Angles)과 짐벌 락의 비밀

우리가 일상에서 흔히 사용하는 (X, Y, Z) 회전은 오일러 각 방식입니다.

  • Pitch (X축 회전)
  • Yaw (Y축 회전)
  • Roll (Z축 회전)

오일러 각은 매우 직관적이지만 치명적인 단점이 있습니다. 세 축이 순차적으로 계산된다는 점입니다.

짐벌 락은 오일러 회전의 적용 순서와 중간 회전축에 따라 발생합니다. 예를 들어 Unity의 Z-X-Y 순서에서는 중간 축인 X축 회전이 ±90도에 이르면 첫 번째와 마지막 회전축이 정렬되어 회전 자유도 하나를 독립적으로 제어할 수 없게 됩니다. 이를 짐벌 락이라고 부릅니다. Unity의 Euler 회전 순서


2. 쿼터니언(사원수)이란 무엇인가?

사원수는 실수 성분 1개와 허수 단위 성분 3개로 이루어진 4차원 실수 대수이며 다음과 같이 표현됩니다.

q=w+xi+yj+zkq = w + xi + yj + zk

이를 3D 회전에 매핑하기 위해 “어떤 축(Vector3)을 기준으로 몇 도(Angle)만큼 회전할 것인가?”로 바꾸어 이해하면 훨씬 쉽습니다.

  • (x, y, z): 단위 회전축 방향에 sin⁡(θ/2)\sin(\theta/2)를 곱한 벡터 성분
  • w: 회전 각도 정보 (cos⁡(θ/2)\cos(\theta/2))

쿼터니언은 회전을 표현하고 결합할 때 오일러 각의 짐벌 락을 피할 수 있지만, 회전의 곱셈 순서는 여전히 중요합니다. 두 회전의 결합은 일반적으로 교환법칙을 만족하지 않습니다.

같은 기준 좌표계의 벡터에 적용하면 (q1 * q2) * v는 q1 * (q2 * v)와 같습니다. 회전을 먼저 적용한 뒤 그 결과의 로컬 축을 기준으로 회전하는 설명과 구분해야 하며, 좌표계 없이 적용 순서만 외우면 혼동하기 쉽습니다. 일반적으로 q1 * q2 != q2 * q1입니다. Unity Quaternion 곱셈 문서에서 순서와 좌표계 설명을 확인하십시오.


3. 유니티에서의 쿼터니언 실전 팁

우리가 복잡한 복소수 수학 공식을 전부 외울 필요는 없습니다. 엔진의 빌트인 API를 올바르게 사용하는 법만 알면 충분합니다.

1) 오일러 각도를 쿼터니언으로 변환하기

직관적인 각도 Vector3 값을 엔진용 회전값으로 바꿀 때 씁니다.

Vector3 eulerRotation = new Vector3(0, 90, 0);
Quaternion targetRotation = Quaternion.Euler(eulerRotation);

2) 특정 방향을 바라보게 하기

몬스터가 플레이어를 쳐다보게 만들 때 최적입니다.

Vector3 direction = player.position - monster.position;
Quaternion lookRotation = Quaternion.LookRotation(direction);
monster.rotation = lookRotation;

3) 부드러운 회전 보간 (Slerp)

현재 회전에서 목표 회전으로 일정한 각속도로 회전하려면 Quaternion.RotateTowards를 사용합니다. 반면 매 프레임 현재 회전과 목표 회전 사이를 Slerp하면 각속도가 점차 줄어드는 완화(easing) 효과가 납니다.

// 초당 최대 180도 회전하며, 목표 회전을 넘어가지 않음
float maxDegreesPerSecond = 180f;
transform.rotation = Quaternion.RotateTowards(
    transform.rotation,
    targetRotation,
    maxDegreesPerSecond * Time.deltaTime);

Slerp를 사용할 때 일정한 시간 동안 시작 회전에서 목표 회전까지 보간하려면 시작 회전과 목표 회전을 고정하고, 경과 시간에 따라 0에서 1까지 증가하는 보간 비율을 전달합니다.

⚠️ 절대 하지 말아야 할 행동

쿼터니언의 개별 성분(q.x, q.y, q.z, q.w)을 직접 하드코딩해서 수정하면 안 됩니다. 정규화가 깨져 물체가 화면에서 사라지거나 에러가 납니다. 꼭 수정해야 한다면 곱 연산(q1 * q2)을 통해 회전을 누적하는 방식을 취하세요.